Skip to content

Commit 7ddb5ed

Browse files
committed
fix C901 too complex linting errors
1 parent 0d8f478 commit 7ddb5ed

3 files changed

Lines changed: 317 additions & 192 deletions

File tree

src/xulbux/data.py

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,9 @@ def get_path_id(
290290

291291
data = cls.remove_comments(data, comment_start, comment_end)
292292
if isinstance(value_paths, str):
293-
return cls._get_path_id(value_paths, path_sep, data, ignore_not_found)
293+
return _DataGetPathIdHelper(value_paths, path_sep, data, ignore_not_found)()
294294

295-
results = [cls._get_path_id(path, path_sep, data, ignore_not_found) for path in value_paths]
295+
results = [_DataGetPathIdHelper(path, path_sep, data, ignore_not_found)() for path in value_paths]
296296
return results if len(results) > 1 else results[0] if results else None
297297

298298
@classmethod
@@ -512,51 +512,6 @@ def _sep_path_id(path_id: str) -> list[int]:
512512

513513
raise ValueError(f"Path ID '{path_id}' is an invalid format.")
514514

515-
@staticmethod
516-
def _get_path_id(path: str, path_sep: str, data_obj: DataStructure, ignore_not_found: bool) -> Optional[str]:
517-
"""Internal method to process a single data-path and generate its path ID."""
518-
keys = path.split(path_sep)
519-
path_ids, max_id_length = [], 0
520-
current_data: Any = data_obj
521-
522-
for key in keys:
523-
if isinstance(current_data, dict):
524-
if key.isdigit():
525-
if ignore_not_found:
526-
return None
527-
raise TypeError(f"Key '{key}' is invalid for a dict type.")
528-
529-
try:
530-
idx = list(current_data.keys()).index(key)
531-
current_data = current_data[key]
532-
except (ValueError, KeyError):
533-
if ignore_not_found:
534-
return None
535-
raise KeyError(f"Key '{key}' not found in dict.")
536-
537-
elif isinstance(current_data, IndexIterableTypes):
538-
try:
539-
idx = int(key)
540-
current_data = list(current_data)[idx] # CONVERT TO LIST FOR INDEXING
541-
except ValueError:
542-
try:
543-
idx = list(current_data).index(key)
544-
current_data = list(current_data)[idx]
545-
except ValueError:
546-
if ignore_not_found:
547-
return None
548-
raise ValueError(f"Value '{key}' not found in '{type(current_data).__name__}'")
549-
550-
else:
551-
break
552-
553-
path_ids.append(str(idx))
554-
max_id_length = max(max_id_length, len(str(idx)))
555-
556-
if not path_ids:
557-
return None
558-
return f"{max_id_length}>{''.join(id.zfill(max_id_length) for id in path_ids)}"
559-
560515
@classmethod
561516
def _set_nested_val(cls, data: DataStructure, id_path: list[int], value: Any) -> Any:
562517
"""Internal method to set a value in a nested data structure based on the provided ID path."""
@@ -605,19 +560,19 @@ def __init__(self, data: DataStructure, comment_start: str, comment_end: str, co
605560
)) if len(comment_end) > 0 else None
606561

607562
def __call__(self) -> DataStructure:
608-
return self.rem_nested_comments(self.data)
563+
return self.remove_nested_comments(self.data)
609564

610-
def rem_nested_comments(self, item: Any) -> Any:
565+
def remove_nested_comments(self, item: Any) -> Any:
611566
if isinstance(item, dict):
612567
return {
613568
key: val
614569
for key, val in ( \
615-
(self.rem_nested_comments(k), self.rem_nested_comments(v)) for k, v in item.items()
570+
(self.remove_nested_comments(k), self.remove_nested_comments(v)) for k, v in item.items()
616571
) if key is not None
617572
}
618573

619574
if isinstance(item, IndexIterableTypes):
620-
processed = (v for v in map(self.rem_nested_comments, item) if v is not None)
575+
processed = (v for v in map(self.remove_nested_comments, item) if v is not None)
621576
return type(item)(processed)
622577

623578
if isinstance(item, str):
@@ -632,6 +587,77 @@ def rem_nested_comments(self, item: Any) -> Any:
632587
return item
633588

634589

590+
class _DataGetPathIdHelper:
591+
"""Internal, callable helper class to process a data path and generate its unique path ID."""
592+
593+
def __init__(self, path: str, path_sep: str, data_obj: DataStructure, ignore_not_found: bool):
594+
self.keys = path.split(path_sep)
595+
self.data_obj = data_obj
596+
self.ignore_not_found = ignore_not_found
597+
598+
self.path_ids: list[str] = []
599+
self.max_id_length = 0
600+
self.current_data: Any = data_obj
601+
602+
def __call__(self) -> Optional[str]:
603+
for key in self.keys:
604+
if not self.process_key(key):
605+
break
606+
607+
if not self.path_ids:
608+
return None
609+
return f"{self.max_id_length}>{''.join(id.zfill(self.max_id_length) for id in self.path_ids)}"
610+
611+
def process_key(self, key: str) -> bool:
612+
"""Process a single key and update `path_ids`. Returns `False` if processing should stop."""
613+
idx: Optional[int] = None
614+
615+
if isinstance(self.current_data, dict):
616+
if (idx := self.process_dict_key(key)) is None:
617+
return False
618+
elif isinstance(self.current_data, IndexIterableTypes):
619+
if (idx := self.process_iterable_key(key)) is None:
620+
return False
621+
else:
622+
return False
623+
624+
self.path_ids.append(str(idx))
625+
self.max_id_length = max(self.max_id_length, len(str(idx)))
626+
return True
627+
628+
def process_dict_key(self, key: str) -> Optional[int]:
629+
"""Process a key for dictionary data. Returns the index or `None` if not found."""
630+
if key.isdigit():
631+
if self.ignore_not_found:
632+
return None
633+
raise TypeError(f"Key '{key}' is invalid for a dict type.")
634+
635+
try:
636+
idx = list(self.current_data.keys()).index(key)
637+
self.current_data = self.current_data[key]
638+
return idx
639+
except (ValueError, KeyError):
640+
if self.ignore_not_found:
641+
return None
642+
raise KeyError(f"Key '{key}' not found in dict.")
643+
644+
def process_iterable_key(self, key: str) -> Optional[int]:
645+
"""Process a key for iterable data. Returns the index or `None` if not found."""
646+
try:
647+
idx = int(key)
648+
self.current_data = list(self.current_data)[idx]
649+
return idx
650+
except ValueError:
651+
try:
652+
idx = list(self.current_data).index(key)
653+
self.current_data = list(self.current_data)[idx]
654+
return idx
655+
except ValueError:
656+
if self.ignore_not_found:
657+
return None
658+
raise ValueError(f"Value '{key}' not found in '{type(self.current_data).__name__}'")
659+
660+
635661
class _DataRenderHelper:
636662
"""Internal, callable helper class to format data structures as strings."""
637663

src/xulbux/format_codes.py

Lines changed: 115 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -420,11 +420,6 @@ def _validate_default_color(default_color: Optional[Rgba | Hexa]) -> tuple[bool,
420420
return True, Color._parse_rgba(default_color)
421421
raise TypeError("The 'default_color' parameter must be either a valid RGBA or HEXA color, or None.")
422422

423-
@staticmethod
424-
def _is_valid_color(color: str) -> bool:
425-
"""Internal method to check whether the given color string is a valid formatting-key color."""
426-
return bool((color in ANSI.COLOR_MAP) or Color.is_valid_rgba(color) or Color.is_valid_hexa(color))
427-
428423
@staticmethod
429424
def _formats_to_keys(formats: str) -> list[str]:
430425
"""Internal method to convert a string of multiple format keys
@@ -588,88 +583,140 @@ def __init__(
588583
self.default_color = default_color
589584
self.brightness_steps = brightness_steps
590585

586+
# INSTANCE VARIABLES FOR CURRENT PROCESSING STATE
587+
self.formats: str = ""
588+
self.original_formats: str = ""
589+
self.formats_escaped: bool = False
590+
self.auto_reset_escaped: bool = False
591+
self.auto_reset_txt: Optional[str] = None
592+
self.format_keys: list[str] = []
593+
self.ansi_formats: list[str] = []
594+
self.ansi_resets: list[str] = []
595+
591596
def __call__(self, match: _rx.Match[str]) -> str:
592-
_formats = formats = match.group(1)
593-
auto_reset_escaped = match.group(2)
594-
auto_reset_txt = match.group(3)
597+
self.original_formats = self.formats = match.group(1)
598+
self.auto_reset_escaped = bool(match.group(2))
599+
self.auto_reset_txt = match.group(3)
600+
601+
# CHECK IF THERE'S ESCAPED FORMAT CODES
602+
self.formats_escaped = bool(_PATTERNS.escape_char_cond.match(match.group(0)))
603+
if self.formats_escaped:
604+
self.original_formats = self.formats = _PATTERNS.escape_char.sub(r"\1", self.formats)
605+
606+
self.process_formats_and_auto_reset()
595607

596-
if formats_escaped := bool(_PATTERNS.escape_char_cond.match(match.group(0))):
597-
_formats = formats = _PATTERNS.escape_char.sub(r"\1", formats) # REMOVE / OR \\
608+
if not self.formats:
609+
return match.group(0)
610+
611+
self.convert_to_ansi()
612+
return self.build_output(match)
598613

599-
if auto_reset_txt and auto_reset_txt.count("[") > 0 and auto_reset_txt.count("]") > 0:
600-
auto_reset_txt = self.cls.to_ansi(
601-
auto_reset_txt,
614+
def process_formats_and_auto_reset(self) -> None:
615+
"""Process nested formatting in both formats and auto-reset text."""
616+
# PROCESS AUTO-RESET TEXT IF IT CONTAINS NESTED FORMATTING
617+
if self.auto_reset_txt and self.auto_reset_txt.count("[") > 0 and self.auto_reset_txt.count("]") > 0:
618+
self.auto_reset_txt = self.cls.to_ansi(
619+
self.auto_reset_txt,
602620
self.default_color,
603621
self.brightness_steps,
604622
_default_start=False,
605623
_validate_default=False,
606624
)
607625

608-
if not formats:
609-
return match.group(0)
610-
611-
if formats.count("[") > 0 and formats.count("]") > 0:
612-
formats = self.cls.to_ansi(
613-
formats,
626+
# PROCESS NESTED FORMATTING IN FORMATS
627+
if self.formats and self.formats.count("[") > 0 and self.formats.count("]") > 0:
628+
self.formats = self.cls.to_ansi(
629+
self.formats,
614630
self.default_color,
615631
self.brightness_steps,
616632
_default_start=False,
617633
_validate_default=False,
618634
)
619635

620-
format_keys = self.cls._formats_to_keys(formats)
621-
ansi_formats = [
636+
def convert_to_ansi(self) -> None:
637+
"""Convert format keys to ANSI codes and generate resets if needed."""
638+
self.format_keys = self.cls._formats_to_keys(self.formats)
639+
self.ansi_formats = [
622640
r if (r := self.cls._get_replacement(k, self.default_color, self.brightness_steps)) != k else f"[{k}]"
623-
for k in format_keys
641+
for k in self.format_keys
624642
]
625643

626-
if auto_reset_txt and not auto_reset_escaped:
627-
reset_keys: list[str] = []
628-
default_color_resets = ("_bg", "default") if self.use_default else ("_bg", "_c")
629-
630-
for k in format_keys:
631-
k_lower = k.lower()
632-
k_set = set(k_lower.split(":"))
633-
634-
if _PREFIX["BG"] & k_set and len(k_set) <= 3:
635-
if k_set & _PREFIX["BR"]:
636-
for i in range(len(k)):
637-
if self.cls._is_valid_color(k[i:]):
638-
reset_keys.extend(default_color_resets)
639-
break
640-
else:
641-
for i in range(len(k)):
642-
if self.cls._is_valid_color(k[i:]):
643-
reset_keys.append("_bg")
644-
break
645-
646-
elif self.cls._is_valid_color(k) or any(
647-
k_lower.startswith(pref_colon := f"{prefix}:") and self.cls._is_valid_color(k[len(pref_colon):])
648-
for prefix in _PREFIX["BR"]):
649-
reset_keys.append(default_color_resets[1])
650-
644+
# GENERATE RESET CODES IF AUTO-RESET IS ACTIVE
645+
if self.auto_reset_txt and not self.auto_reset_escaped:
646+
self.gen_reset_codes()
647+
else:
648+
self.ansi_resets = []
649+
650+
def gen_reset_codes(self) -> None:
651+
"""Generate appropriate ANSI reset codes for each format key."""
652+
default_color_resets = ("_bg", "default") if self.use_default else ("_bg", "_c")
653+
reset_keys: list[str] = []
654+
655+
for k in self.format_keys:
656+
k_lower = k.lower()
657+
k_set = set(k_lower.split(":"))
658+
659+
# BACKGROUND COLOR FORMAT
660+
if _PREFIX["BG"] & k_set and len(k_set) <= 3:
661+
if k_set & _PREFIX["BR"]:
662+
# BRIGHT BACKGROUND COLOR - RESET BOTH BG AND COLOR
663+
for i in range(len(k)):
664+
if self.is_valid_color(k[i:]):
665+
reset_keys.extend(default_color_resets)
666+
break
651667
else:
652-
reset_keys.append(f"_{k}")
653-
654-
ansi_resets = [
655-
r for k in reset_keys if (r := self.cls._get_replacement(k, self.default_color, self.brightness_steps)
656-
).startswith(f"{ANSI.CHAR}{ANSI.START}")
657-
]
668+
# REGULAR BACKGROUND COLOR - RESET ONLY BG
669+
for i in range(len(k)):
670+
if self.is_valid_color(k[i:]):
671+
reset_keys.append("_bg")
672+
break
673+
674+
# TEXT COLOR FORMAT
675+
elif self.is_valid_color(k) or any(
676+
k_lower.startswith(pref_colon := f"{prefix}:") and self.is_valid_color(k[len(pref_colon):]) \
677+
for prefix in _PREFIX["BR"]
678+
):
679+
reset_keys.append(default_color_resets[1])
680+
681+
# TEXT STYLE FORMAT
682+
else:
683+
reset_keys.append(f"_{k}")
684+
685+
# CONVERT RESET KEYS TO ANSI CODES
686+
self.ansi_resets = [
687+
r for k in reset_keys if ( \
688+
r := self.cls._get_replacement(k, self.default_color, self.brightness_steps)
689+
).startswith(f"{ANSI.CHAR}{ANSI.START}")
690+
]
658691

659-
else:
660-
ansi_resets = []
692+
def build_output(self, match: _rx.Match[str]) -> str:
693+
"""Build the final output string based on processed formats and resets."""
694+
# CHECK IF ALL FORMATS WERE VALID
695+
has_single_valid_ansi = len(self.ansi_formats) == 1 and self.ansi_formats[0].count(f"{ANSI.CHAR}{ANSI.START}") >= 1
696+
all_formats_valid = all(f.startswith(f"{ANSI.CHAR}{ANSI.START}") for f in self.ansi_formats)
661697

662-
if (
663-
not (len(ansi_formats) == 1 and ansi_formats[0].count(f"{ANSI.CHAR}{ANSI.START}") >= 1) and \
664-
not all(f.startswith(f"{ANSI.CHAR}{ANSI.START}") for f in ansi_formats) # FORMATTING WAS INVALID
665-
):
698+
if not has_single_valid_ansi and not all_formats_valid:
666699
return match.group(0)
667-
elif formats_escaped: # FORMATTING WAS VALID BUT ESCAPED
668-
return f"[{_formats}]({auto_reset_txt})" if auto_reset_txt else f"[{_formats}]"
669-
else:
670-
return (
671-
"".join(ansi_formats) + (
672-
f"({self.cls.to_ansi(auto_reset_txt, self.default_color, self.brightness_steps, _default_start=False, _validate_default=False)})"
673-
if auto_reset_escaped and auto_reset_txt else auto_reset_txt if auto_reset_txt else ""
674-
) + ("" if auto_reset_escaped else "".join(ansi_resets))
675-
)
700+
701+
# HANDLE ESCAPED FORMATTING
702+
if self.formats_escaped:
703+
return f"[{self.original_formats}]({self.auto_reset_txt})" if self.auto_reset_txt else f"[{self.original_formats}]"
704+
705+
# BUILD NORMAL OUTPUT WITH FORMATS AND RESETS
706+
output = "".join(self.ansi_formats)
707+
708+
# ADD AUTO-RESET TEXT
709+
if self.auto_reset_escaped and self.auto_reset_txt:
710+
output += f"({self.cls.to_ansi(self.auto_reset_txt, self.default_color, self.brightness_steps, _default_start=False, _validate_default=False)})"
711+
elif self.auto_reset_txt:
712+
output += self.auto_reset_txt
713+
714+
# ADD RESET CODES IF NOT ESCAPED
715+
if not self.auto_reset_escaped:
716+
output += "".join(self.ansi_resets)
717+
718+
return output
719+
720+
def is_valid_color(self, color: str) -> bool:
721+
"""Check whether the given color string is a valid formatting-key color."""
722+
return bool((color in ANSI.COLOR_MAP) or Color.is_valid_rgba(color) or Color.is_valid_hexa(color))

0 commit comments

Comments
 (0)