@@ -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