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