|
3 | 3 | Support for ANSI escape sequences which are used for things like applying style to text,
|
4 | 4 | setting the window title, and asynchronous alerts.
|
5 | 5 | """
|
| 6 | +from enum import Enum, unique |
6 | 7 | import functools
|
7 | 8 | import re
|
8 |
| -from typing import Any, IO |
| 9 | +from typing import Any, IO, List, Union |
9 | 10 |
|
10 | 11 | import colorama
|
11 | 12 | from colorama import Fore, Back, Style
|
|
25 | 26 | # Regular expression to match ANSI style sequences (including 8-bit and 24-bit colors)
|
26 | 27 | ANSI_STYLE_RE = re.compile(r'\x1b\[[^m]*m')
|
27 | 28 |
|
28 |
| -# Foreground color presets |
29 |
| -FG_COLORS = { |
30 |
| - 'black': Fore.BLACK, |
31 |
| - 'red': Fore.RED, |
32 |
| - 'green': Fore.GREEN, |
33 |
| - 'yellow': Fore.YELLOW, |
34 |
| - 'blue': Fore.BLUE, |
35 |
| - 'magenta': Fore.MAGENTA, |
36 |
| - 'cyan': Fore.CYAN, |
37 |
| - 'white': Fore.WHITE, |
38 |
| - 'bright_black': Fore.LIGHTBLACK_EX, |
39 |
| - 'bright_red': Fore.LIGHTRED_EX, |
40 |
| - 'bright_green': Fore.LIGHTGREEN_EX, |
41 |
| - 'bright_yellow': Fore.LIGHTYELLOW_EX, |
42 |
| - 'bright_blue': Fore.LIGHTBLUE_EX, |
43 |
| - 'bright_magenta': Fore.LIGHTMAGENTA_EX, |
44 |
| - 'bright_cyan': Fore.LIGHTCYAN_EX, |
45 |
| - 'bright_white': Fore.LIGHTWHITE_EX, |
46 |
| - 'reset': Fore.RESET, |
47 |
| -} |
48 |
| - |
49 |
| -# Background color presets |
50 |
| -BG_COLORS = { |
51 |
| - 'black': Back.BLACK, |
52 |
| - 'red': Back.RED, |
53 |
| - 'green': Back.GREEN, |
54 |
| - 'yellow': Back.YELLOW, |
55 |
| - 'blue': Back.BLUE, |
56 |
| - 'magenta': Back.MAGENTA, |
57 |
| - 'cyan': Back.CYAN, |
58 |
| - 'white': Back.WHITE, |
59 |
| - 'bright_black': Back.LIGHTBLACK_EX, |
60 |
| - 'bright_red': Back.LIGHTRED_EX, |
61 |
| - 'bright_green': Back.LIGHTGREEN_EX, |
62 |
| - 'bright_yellow': Back.LIGHTYELLOW_EX, |
63 |
| - 'bright_blue': Back.LIGHTBLUE_EX, |
64 |
| - 'bright_magenta': Back.LIGHTMAGENTA_EX, |
65 |
| - 'bright_cyan': Back.LIGHTCYAN_EX, |
66 |
| - 'bright_white': Back.LIGHTWHITE_EX, |
67 |
| - 'reset': Back.RESET, |
68 |
| -} |
69 |
| - |
70 |
| -FG_RESET = FG_COLORS['reset'] |
71 |
| -BG_RESET = BG_COLORS['reset'] |
| 29 | + |
| 30 | +# Foreground colors |
| 31 | +# noinspection PyPep8Naming,DuplicatedCode |
| 32 | +@unique |
| 33 | +class fg(Enum): |
| 34 | + """Enum class for foreground colors (to support IDE autocompletion).""" |
| 35 | + black = Fore.BLACK |
| 36 | + red = Fore.RED |
| 37 | + green = Fore.GREEN |
| 38 | + yellow = Fore.YELLOW |
| 39 | + blue = Fore.BLUE |
| 40 | + magenta = Fore.MAGENTA |
| 41 | + cyan = Fore.CYAN |
| 42 | + white = Fore.WHITE |
| 43 | + bright_black = Fore.LIGHTBLACK_EX |
| 44 | + bright_red = Fore.LIGHTRED_EX |
| 45 | + bright_green = Fore.LIGHTGREEN_EX |
| 46 | + bright_yellow = Fore.LIGHTYELLOW_EX |
| 47 | + bright_blue = Fore.LIGHTBLUE_EX |
| 48 | + bright_magenta = Fore.LIGHTMAGENTA_EX |
| 49 | + bright_cyan = Fore.LIGHTCYAN_EX |
| 50 | + bright_white = Fore.LIGHTWHITE_EX |
| 51 | + reset = Fore.RESET |
| 52 | + |
| 53 | + def __str__(self) -> str: |
| 54 | + """Make the value the string representation instead of the enum name.""" |
| 55 | + return self.value |
| 56 | + |
| 57 | + @staticmethod |
| 58 | + def colors() -> List[str]: |
| 59 | + """Return a list of color names.""" |
| 60 | + return [color.name for color in fg] |
| 61 | + |
| 62 | + @staticmethod |
| 63 | + def get_value(name: str) -> str: |
| 64 | + """Retrieve color code by name string.""" |
| 65 | + return fg.__members__[name].value |
| 66 | + |
| 67 | + |
| 68 | +# Background colors |
| 69 | +# noinspection PyPep8Naming,DuplicatedCode |
| 70 | +@unique |
| 71 | +class bg(Enum): |
| 72 | + """Enum class for background colors (to support IDE autocompletion).""" |
| 73 | + black = Back.BLACK |
| 74 | + red = Back.RED |
| 75 | + green = Back.GREEN |
| 76 | + yellow = Back.YELLOW |
| 77 | + blue = Back.BLUE |
| 78 | + magenta = Back.MAGENTA |
| 79 | + cyan = Back.CYAN |
| 80 | + white = Back.WHITE |
| 81 | + bright_black = Back.LIGHTBLACK_EX |
| 82 | + bright_red = Back.LIGHTRED_EX |
| 83 | + bright_green = Back.LIGHTGREEN_EX |
| 84 | + bright_yellow = Back.LIGHTYELLOW_EX |
| 85 | + bright_blue = Back.LIGHTBLUE_EX |
| 86 | + bright_magenta = Back.LIGHTMAGENTA_EX |
| 87 | + bright_cyan = Back.LIGHTCYAN_EX |
| 88 | + bright_white = Back.LIGHTWHITE_EX |
| 89 | + reset = Back.RESET |
| 90 | + |
| 91 | + def __str__(self) -> str: |
| 92 | + """Make the value the string representation instead of the enum name.""" |
| 93 | + return self.value |
| 94 | + |
| 95 | + @staticmethod |
| 96 | + def colors() -> List[str]: |
| 97 | + """Return a list of color names.""" |
| 98 | + return [color.name for color in bg] |
| 99 | + |
| 100 | + @staticmethod |
| 101 | + def get_value(name: str) -> str: |
| 102 | + """Retrieve color code by name string.""" |
| 103 | + return bg.__members__[name].value |
| 104 | + |
| 105 | + |
| 106 | +FG_RESET = fg.reset.value |
| 107 | +BG_RESET = bg.reset.value |
72 | 108 | RESET_ALL = Style.RESET_ALL
|
73 | 109 |
|
74 | 110 | # Text intensities
|
75 | 111 | INTENSITY_BRIGHT = Style.BRIGHT
|
76 | 112 | INTENSITY_DIM = Style.DIM
|
77 | 113 | INTENSITY_NORMAL = Style.NORMAL
|
78 |
| - |
79 | 114 | # ANSI style sequences not provided by colorama
|
80 | 115 | UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4)
|
81 | 116 | UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24)
|
@@ -115,46 +150,53 @@ def style_aware_write(fileobj: IO, msg: str) -> None:
|
115 | 150 | fileobj.write(msg)
|
116 | 151 |
|
117 | 152 |
|
118 |
| -def fg_lookup(fg_name: str) -> str: |
| 153 | +def fg_lookup(fg_name: Union[str, fg]) -> str: |
119 | 154 | """
|
120 | 155 | Look up ANSI escape codes based on foreground color name.
|
121 | 156 |
|
122 |
| - :param fg_name: foreground color name to look up ANSI escape code(s) for |
| 157 | + :param fg_name: foreground color name or enum to look up ANSI escape code(s) for |
123 | 158 | :return: ANSI escape code(s) associated with this color
|
124 | 159 | :raises ValueError: if the color cannot be found
|
125 | 160 | """
|
| 161 | + if isinstance(fg_name, fg): |
| 162 | + return fg_name.value |
| 163 | + |
126 | 164 | try:
|
127 |
| - ansi_escape = FG_COLORS[fg_name.lower()] |
| 165 | + ansi_escape = fg.get_value(fg_name.lower()) |
128 | 166 | except KeyError:
|
129 |
| - raise ValueError('Foreground color {!r} does not exist.'.format(fg_name)) |
| 167 | + raise ValueError('Foreground color {!r} does not exist; must be one of: {}'.format(fg_name, fg.colors())) |
130 | 168 | return ansi_escape
|
131 | 169 |
|
132 | 170 |
|
133 |
| -def bg_lookup(bg_name: str) -> str: |
| 171 | +def bg_lookup(bg_name: Union[str, bg]) -> str: |
134 | 172 | """
|
135 | 173 | Look up ANSI escape codes based on background color name.
|
136 | 174 |
|
137 |
| - :param bg_name: background color name to look up ANSI escape code(s) for |
| 175 | + :param bg_name: background color name or enum to look up ANSI escape code(s) for |
138 | 176 | :return: ANSI escape code(s) associated with this color
|
139 | 177 | :raises ValueError: if the color cannot be found
|
140 | 178 | """
|
| 179 | + if isinstance(bg_name, bg): |
| 180 | + return bg_name.value |
| 181 | + |
141 | 182 | try:
|
142 |
| - ansi_escape = BG_COLORS[bg_name.lower()] |
| 183 | + ansi_escape = bg.get_value(bg_name.lower()) |
143 | 184 | except KeyError:
|
144 |
| - raise ValueError('Background color {!r} does not exist.'.format(bg_name)) |
| 185 | + raise ValueError('Background color {!r} does not exist; must be one of: {}'.format(bg_name, bg.colors())) |
145 | 186 | return ansi_escape
|
146 | 187 |
|
147 | 188 |
|
148 |
| -def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, |
| 189 | +# noinspection PyShadowingNames |
| 190 | +def style(text: Any, *, fg: Union[str, fg] = '', bg: Union[str, bg] = '', bold: bool = False, |
149 | 191 | dim: bool = False, underline: bool = False) -> str:
|
150 | 192 | """
|
151 | 193 | Apply ANSI colors and/or styles to a string and return it.
|
152 | 194 | The styling is self contained which means that at the end of the string reset code(s) are issued
|
153 | 195 | to undo whatever styling was done at the beginning.
|
154 | 196 |
|
155 | 197 | :param text: Any object compatible with str.format()
|
156 |
| - :param fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name. Defaults to no color. |
157 |
| - :param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name. Defaults to no color. |
| 198 | + :param fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name or enum. Defaults to no color. |
| 199 | + :param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name or enum. Defaults to no color. |
158 | 200 | :param bold: apply the bold style if True. Can be combined with dim. Defaults to False.
|
159 | 201 | :param dim: apply the dim style if True. Can be combined with bold. Defaults to False.
|
160 | 202 | :param underline: apply the underline style if True. Defaults to False.
|
@@ -197,13 +239,13 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False,
|
197 | 239 | # Default styles for printing strings of various types.
|
198 | 240 | # These can be altered to suit an application's needs and only need to be a
|
199 | 241 | # function with the following structure: func(str) -> str
|
200 |
| -style_success = functools.partial(style, fg='green') |
| 242 | +style_success = functools.partial(style, fg=fg.green) |
201 | 243 | """Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify success"""
|
202 | 244 |
|
203 |
| -style_warning = functools.partial(style, fg='bright_yellow') |
| 245 | +style_warning = functools.partial(style, fg=fg.bright_yellow) |
204 | 246 | """Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify a warning"""
|
205 | 247 |
|
206 |
| -style_error = functools.partial(style, fg='bright_red') |
| 248 | +style_error = functools.partial(style, fg=fg.bright_red) |
207 | 249 | """Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify an error"""
|
208 | 250 |
|
209 | 251 |
|
|
0 commit comments