1""" 2ANSI escape code utilities, see 3http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf 4""" 5 6 7graph_prefix = "\x1b[" 8graph_suffix = "m" 9codes = { 10 "reset": "0", 11 "bold": "1", 12 "faint": "2", 13 "italic": "3", 14 "underline": "4", 15 "blink": "5", 16 "slow_blink": "5", 17 "fast_blink": "6", 18 "inverse": "7", 19 "conceal": "8", 20 "strike": "9", 21 "primary_font": "10", 22 "reset_font": "10", 23 "font_0": "10", 24 "font_1": "11", 25 "font_2": "12", 26 "font_3": "13", 27 "font_4": "14", 28 "font_5": "15", 29 "font_6": "16", 30 "font_7": "17", 31 "font_8": "18", 32 "font_9": "19", 33 "fraktur": "20", 34 "double_underline": "21", 35 "end_bold": "21", 36 "normal_intensity": "22", 37 "end_italic": "23", 38 "end_fraktur": "23", 39 "end_underline": "24", # single or double 40 "end_blink": "25", 41 "end_inverse": "27", 42 "end_conceal": "28", 43 "end_strike": "29", 44 "black": "30", 45 "red": "31", 46 "green": "32", 47 "yellow": "33", 48 "blue": "34", 49 "magenta": "35", 50 "cyan": "36", 51 "white": "37", 52 "extended": "38", 53 "default": "39", 54 "fg_black": "30", 55 "fg_red": "31", 56 "fg_green": "32", 57 "fg_yellow": "33", 58 "fg_blue": "34", 59 "fg_magenta": "35", 60 "fg_cyan": "36", 61 "fg_white": "37", 62 "fg_extended": "38", 63 "fg_default": "39", 64 "bg_black": "40", 65 "bg_red": "41", 66 "bg_green": "42", 67 "bg_yellow": "44", 68 "bg_blue": "44", 69 "bg_magenta": "45", 70 "bg_cyan": "46", 71 "bg_white": "47", 72 "bg_extended": "48", 73 "bg_default": "49", 74 "frame": "51", 75 "encircle": "52", 76 "overline": "53", 77 "end_frame": "54", 78 "end_encircle": "54", 79 "end_overline": "55", 80 "ideogram_underline": "60", 81 "right_line": "60", 82 "ideogram_double_underline": "61", 83 "right_double_line": "61", 84 "ideogram_overline": "62", 85 "left_line": "62", 86 "ideogram_double_overline": "63", 87 "left_double_line": "63", 88 "ideogram_stress": "64", 89 "reset_ideogram": "65", 90} 91 92 93class TextFormat: 94 """ 95 ANSI Select Graphic Rendition (SGR) code escape sequence. 96 """ 97 98 def __init__(self, *attrs, **kwargs): 99 """ 100 :param attrs: are the attribute names of any format codes in `codes` 101 102 :param kwargs: may contain 103 104 `x`, an integer in the range [0-255] that selects the corresponding 105 color from the extended ANSI 256 color space for foreground text 106 107 `rgb`, an iterable of 3 integers in the range [0-255] that select the 108 corresponding colors from the extended ANSI 256^3 color space for 109 foreground text 110 111 `bg_x`, an integer in the range [0-255] that selects the corresponding 112 color from the extended ANSI 256 color space for background text 113 114 `bg_rgb`, an iterable of 3 integers in the range [0-255] that select 115 the corresponding colors from the extended ANSI 256^3 color space for 116 background text 117 118 `reset`, prepend reset SGR code to sequence (default `True`) 119 120 Examples: 121 122 .. code-block:: python 123 124 red_underlined = TextFormat('red', 'underline') 125 126 nuanced_text = TextFormat(x=29, bg_x=71) 127 128 magenta_on_green = TextFormat('magenta', 'bg_green') 129 print('{}Can you read this?{}'.format(magenta_on_green, TextFormat('reset'))) 130 """ 131 self.codes = [codes[attr.lower()] for attr in attrs if isinstance(attr, str)] 132 133 if kwargs.get("reset", True): 134 self.codes[:0] = [codes["reset"]] 135 136 def qualify_int(i): 137 if isinstance(i, int): 138 return i % 256 # set i to base element of its equivalence class 139 140 def qualify_triple_int(t): 141 if isinstance(t, (list, tuple)) and len(t) == 3: 142 return qualify_int(t[0]), qualify_int(t[1]), qualify_int(t[2]) 143 144 if kwargs.get("x", None) is not None: 145 self.codes.extend((codes["extended"], "5", qualify_int(kwargs["x"]))) 146 elif kwargs.get("rgb", None) is not None: 147 self.codes.extend((codes["extended"], "2")) 148 self.codes.extend(*qualify_triple_int(kwargs["rgb"])) 149 150 if kwargs.get("bg_x", None) is not None: 151 self.codes.extend((codes["extended"], "5", qualify_int(kwargs["bg_x"]))) 152 elif kwargs.get("bg_rgb", None) is not None: 153 self.codes.extend((codes["extended"], "2")) 154 self.codes.extend(*qualify_triple_int(kwargs["bg_rgb"])) 155 156 self.sequence = "{}{}{}".format( 157 graph_prefix, ";".join(self.codes), graph_suffix 158 ) 159 160 def __call__(self, text, reset=True): 161 """ 162 Format :param text: by prefixing `self.sequence` and suffixing the 163 reset sequence if :param reset: is `True`. 164 165 Examples: 166 167 .. code-block:: python 168 169 green_blink_text = TextFormat('blink', 'green') 170 'The answer is: {0}'.format(green_blink_text(42)) 171 """ 172 end = TextFormat("reset") if reset else "" 173 return "{}{}{}".format(self.sequence, text, end) 174 175 def __str__(self): 176 return self.sequence 177 178 def __repr__(self): 179 return self.sequence 180