Skip to content

Commit ede9f3e

Browse files
committed
Added convenience ansi.fg and ansi.bg enums of foreground and background colors which style() can now optionally use
This is to make it easier to autocomplete color names in an IDE
1 parent c7ac2e9 commit ede9f3e

File tree

5 files changed

+93
-15
lines changed

5 files changed

+93
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
* Enhancements
33
* Changed the default help text to make `help -v` more discoverable
44
* Added `add_settable()` and `remove_settable()` convenience methods to update `self.settable` dictionary
5+
* Added convenience `ansi.fg` and `ansi.bg` enums of foreground and background colors
6+
* `ansi.style()` `fg` argument can now either be of type `str` or `ansi.fg`
7+
* `ansi.style()` `bg` argument can now either be of type `str` or `ansi.bg`
8+
* This supports IDE auto-completion of color names
59
* Breaking changes
610
* Renamed `locals_in_py` attribute of `cmd2.Cmd` to `self_in_py`
711
* The following public attributes of `cmd2.Cmd` are no longer settable at runtime by default:

cmd2/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# package is not installed
1111
pass
1212

13-
from .ansi import style
13+
from .ansi import style, fg, bg
1414
from .argparse_custom import Cmd2ArgumentParser, CompletionError, CompletionItem, set_default_argument_parser
1515

1616
# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER

cmd2/ansi.py

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
Support for ANSI escape sequences which are used for things like applying style to text,
44
setting the window title, and asynchronous alerts.
55
"""
6+
from enum import Enum, unique
67
import functools
78
import re
8-
from typing import Any, IO
9+
from typing import Any, IO, Union
910

1011
import colorama
1112
from colorama import Fore, Back, Style
@@ -46,6 +47,33 @@
4647
'reset': Fore.RESET,
4748
}
4849

50+
51+
@unique
52+
class fg(Enum):
53+
"""Enum class for foreground colors (to support IDE autocompletion)."""
54+
black = Fore.BLACK
55+
red = Fore.RED
56+
green = Fore.GREEN
57+
yellow = Fore.YELLOW
58+
blue = Fore.BLUE
59+
magenta = Fore.MAGENTA
60+
cyan = Fore.CYAN
61+
white = Fore.WHITE
62+
bright_black = Fore.LIGHTBLACK_EX
63+
bright_red = Fore.LIGHTRED_EX
64+
bright_green = Fore.LIGHTGREEN_EX
65+
bright_yellow = Fore.LIGHTYELLOW_EX
66+
bright_blue = Fore.LIGHTBLUE_EX
67+
bright_magenta = Fore.LIGHTMAGENTA_EX
68+
bright_cyan = Fore.LIGHTCYAN_EX
69+
bright_white = Fore.LIGHTWHITE_EX
70+
reset = Fore.RESET
71+
72+
def __str__(self):
73+
"""Make the value the string representation instead of the enum name."""
74+
return self.value
75+
76+
4977
# Background color presets
5078
BG_COLORS = {
5179
'black': Back.BLACK,
@@ -67,6 +95,33 @@
6795
'reset': Back.RESET,
6896
}
6997

98+
99+
@unique
100+
class bg(Enum):
101+
"""Enum class for background colors (to support IDE autocompletion)."""
102+
black = Back.BLACK
103+
red = Back.RED
104+
green = Back.GREEN
105+
yellow = Back.YELLOW
106+
blue = Back.BLUE
107+
magenta = Back.MAGENTA
108+
cyan = Back.CYAN
109+
white = Back.WHITE
110+
bright_black = Back.LIGHTBLACK_EX
111+
bright_red = Back.LIGHTRED_EX
112+
bright_green = Back.LIGHTGREEN_EX
113+
bright_yellow = Back.LIGHTYELLOW_EX
114+
bright_blue = Back.LIGHTBLUE_EX
115+
bright_magenta = Back.LIGHTMAGENTA_EX
116+
bright_cyan = Back.LIGHTCYAN_EX
117+
bright_white = Back.LIGHTWHITE_EX
118+
reset = Back.RESET
119+
120+
def __str__(self):
121+
"""Make the value the string representation instead of the enum name."""
122+
return self.value
123+
124+
70125
FG_RESET = FG_COLORS['reset']
71126
BG_RESET = BG_COLORS['reset']
72127
RESET_ALL = Style.RESET_ALL
@@ -75,7 +130,6 @@
75130
INTENSITY_BRIGHT = Style.BRIGHT
76131
INTENSITY_DIM = Style.DIM
77132
INTENSITY_NORMAL = Style.NORMAL
78-
79133
# ANSI style sequences not provided by colorama
80134
UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4)
81135
UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24)
@@ -115,46 +169,52 @@ def style_aware_write(fileobj: IO, msg: str) -> None:
115169
fileobj.write(msg)
116170

117171

118-
def fg_lookup(fg_name: str) -> str:
172+
def fg_lookup(fg_name: Union[str, fg]) -> str:
119173
"""
120174
Look up ANSI escape codes based on foreground color name.
121175
122-
:param fg_name: foreground color name to look up ANSI escape code(s) for
176+
:param fg_name: foreground color name or enum to look up ANSI escape code(s) for
123177
:return: ANSI escape code(s) associated with this color
124178
:raises ValueError: if the color cannot be found
125179
"""
180+
if isinstance(fg_name, fg):
181+
return fg_name.value
182+
126183
try:
127184
ansi_escape = FG_COLORS[fg_name.lower()]
128185
except KeyError:
129186
raise ValueError('Foreground color {!r} does not exist.'.format(fg_name))
130187
return ansi_escape
131188

132189

133-
def bg_lookup(bg_name: str) -> str:
190+
def bg_lookup(bg_name: Union[str, bg]) -> str:
134191
"""
135192
Look up ANSI escape codes based on background color name.
136193
137-
:param bg_name: background color name to look up ANSI escape code(s) for
194+
:param bg_name: background color name or enum to look up ANSI escape code(s) for
138195
:return: ANSI escape code(s) associated with this color
139196
:raises ValueError: if the color cannot be found
140197
"""
198+
if isinstance(bg_name, bg):
199+
return bg_name.value
200+
141201
try:
142202
ansi_escape = BG_COLORS[bg_name.lower()]
143203
except KeyError:
144204
raise ValueError('Background color {!r} does not exist.'.format(bg_name))
145205
return ansi_escape
146206

147207

148-
def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False,
208+
def style(text: Any, *, fg: Union[str, fg] = '', bg: Union[str, bg] = '', bold: bool = False,
149209
dim: bool = False, underline: bool = False) -> str:
150210
"""
151211
Apply ANSI colors and/or styles to a string and return it.
152212
The styling is self contained which means that at the end of the string reset code(s) are issued
153213
to undo whatever styling was done at the beginning.
154214
155215
: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.
216+
:param fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name or enum. Defaults to no color.
217+
:param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name or enum. Defaults to no color.
158218
:param bold: apply the bold style if True. Can be combined with dim. Defaults to False.
159219
:param dim: apply the dim style if True. Can be combined with bold. Defaults to False.
160220
:param underline: apply the underline style if True. Defaults to False.
@@ -197,13 +257,13 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False,
197257
# Default styles for printing strings of various types.
198258
# These can be altered to suit an application's needs and only need to be a
199259
# function with the following structure: func(str) -> str
200-
style_success = functools.partial(style, fg='green')
260+
style_success = functools.partial(style, fg=fg.green)
201261
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify success"""
202262

203-
style_warning = functools.partial(style, fg='bright_yellow')
263+
style_warning = functools.partial(style, fg=fg.bright_yellow)
204264
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify a warning"""
205265

206-
style_error = functools.partial(style, fg='bright_red')
266+
style_error = functools.partial(style, fg=fg.bright_red)
207267
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify an error"""
208268

209269

examples/basic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
6) Shell-like capabilities
1010
"""
1111
import cmd2
12-
from cmd2 import style
12+
from cmd2 import style, fg, bg
1313

1414

1515
class BasicApp(cmd2.Cmd):
@@ -19,7 +19,7 @@ def __init__(self):
1919
super().__init__(multiline_commands=['echo'], persistent_history_file='cmd2_history.dat',
2020
startup_script='scripts/startup.txt', use_ipython=True)
2121

22-
self.intro = style('Welcome to PyOhio 2019 and cmd2!', fg='red', bg='white', bold=True) + ' 😀'
22+
self.intro = style('Welcome to PyOhio 2019 and cmd2!', fg=fg.red, bg=bg.white, bold=True) + ' 😀'
2323

2424
# Allow access to your application in py and ipy via self
2525
self.self_in_py = True

tests/test_ansi.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,17 @@ def test_async_alert_str(cols, prompt, line, cursor, msg, expected):
119119
alert_str = ansi.async_alert_str(terminal_columns=cols, prompt=prompt, line=line, cursor_offset=cursor,
120120
alert_msg=msg)
121121
assert alert_str == expected
122+
123+
124+
def test_fg_enum():
125+
assert ansi.fg_lookup('bright_red') == ansi.fg_lookup(ansi.fg.bright_red)
126+
127+
def test_fg_enum_to_str():
128+
assert str(ansi.fg.black) == ansi.fg_lookup('black')
129+
130+
def test_bg_enum():
131+
assert ansi.bg_lookup('green') == ansi.bg_lookup(ansi.bg.green)
132+
133+
def test_bg_enum_to_str():
134+
assert str(ansi.bg.blue) == ansi.bg_lookup('blue')
135+

0 commit comments

Comments
 (0)