diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2020-02-07 17:04:24 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-07 17:04:24 -0500 |
commit | 5def049472e1c261ad78ff56c5d5b3c6332e9bab (patch) | |
tree | 9750793bab06c519362712ee3e4c56ff7c7fc662 | |
parent | 3f075900264fee32c356aac3d03b2568664ba684 (diff) | |
parent | 715af6059d06521c8f0054007a1105d211402d63 (diff) | |
download | cmd2-git-5def049472e1c261ad78ff56c5d5b3c6332e9bab.tar.gz |
Merge pull request #877 from python-cmd2/changelog_fix
Changelog fix for removed ansi.FG_COLORS and ansi.BG_COLORS
-rw-r--r-- | CHANGELOG.md | 7 | ||||
-rwxr-xr-x | README.md | 2 | ||||
-rw-r--r-- | cmd2/ansi.py | 91 | ||||
-rw-r--r-- | cmd2/argparse_custom.py | 2 | ||||
-rwxr-xr-x | examples/async_printing.py | 16 | ||||
-rwxr-xr-x | examples/plumbum_colors.py | 65 | ||||
-rw-r--r-- | tests/test_ansi.py | 45 |
7 files changed, 128 insertions, 100 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 07225efc..11d34ed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ ## 1.0.0-rc1 (TBD, 2020) * Enhancements * Changed the default help text to make `help -v` more discoverable + * **set** command now supports tab-completion of values * Added `add_settable()` and `remove_settable()` convenience methods to update `self.settable` dictionary * Added convenience `ansi.fg` and `ansi.bg` enums of foreground and background colors * `ansi.style()` `fg` argument can now either be of type `str` or `ansi.fg` * `ansi.style()` `bg` argument can now either be of type `str` or `ansi.bg` * This supports IDE auto-completion of color names + * The enums also support + * `f-strings` and `format()` calls (e.g. `"{}hello{}".format(fg.blue, fg.reset)`) + * string concatenation (e.g. `fg.blue + "hello" + fg.reset`) * Breaking changes * Renamed `locals_in_py` attribute of `cmd2.Cmd` to `self_in_py` * The following public attributes of `cmd2.Cmd` are no longer settable at runtime by default: @@ -16,8 +20,9 @@ * It is now a Dict[str, Settable] instead of Dict[str, str] * setting onchange callbacks have a new method signature and must be added to the Settable instance in order to be called - * **set** command now supports tab-completion of values * Removed `cast()` utility function + * Removed `ansi.FG_COLORS` and `ansi.BG_COLORS` dictionaries + * Replaced with `ansi.fg` and `ansi.bg` enums providing similar but improved functionality ## 0.9.25 (January 26, 2020) * Enhancements @@ -91,7 +91,7 @@ Instructions for implementing each feature follow. class MyApp(cmd2.Cmd): def do_foo(self, args): """This docstring is the built-in help for the foo command.""" - self.poutput(cmd2.style('foo bar baz', fg='red')) + self.poutput(cmd2.style('foo bar baz', fg=cmd2.fg.red)) ``` - By default the docstring for your **do_foo** method is the help for the **foo** command - NOTE: This doesn't apply if you use one of the `argparse` decorators mentioned below diff --git a/cmd2/ansi.py b/cmd2/ansi.py index abe7b3dc..2813d62f 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -3,9 +3,9 @@ Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. """ -from enum import Enum, unique import functools import re +from enum import Enum from typing import Any, IO, List, Union import colorama @@ -27,11 +27,47 @@ allow_style = STYLE_TERMINAL ANSI_STYLE_RE = re.compile(r'\x1b\[[^m]*m') +class ColorBase(Enum): + """ + Base class used for defining color enums. See fg and bg classes for examples. + + Child classes should define enums in the follow structure: + key: color name (e.g. black) + value: anything that when cast to a string returns an ANSI sequence + """ + def __str__(self) -> str: + """ + Return ANSI color sequence instead of enum name + This is helpful when using a ColorBase in an f-string or format() call + e.g. my_str = "{}hello{}".format(fg.blue, fg.reset) + """ + return str(self.value) + + def __add__(self, other: Any) -> str: + """ + Support building a color string when self is the left operand + e.g. fg.blue + "hello" + """ + return str(self) + other + + def __radd__(self, other: Any) -> str: + """ + Support building a color string when self is the right operand + e.g. "hello" + fg.reset + """ + return other + str(self) + + @classmethod + def colors(cls) -> List[str]: + """Return a list of color names.""" + # Use __members__ to ensure we get all key names, including those which are aliased + return [color for color in cls.__members__] + + # Foreground colors -# noinspection PyPep8Naming,DuplicatedCode -@unique -class fg(Enum): - """Enum class for foreground colors (to support IDE autocompletion).""" +# noinspection PyPep8Naming +class fg(ColorBase): + """Enum class for foreground colors""" black = Fore.BLACK red = Fore.RED green = Fore.GREEN @@ -50,26 +86,11 @@ class fg(Enum): bright_white = Fore.LIGHTWHITE_EX reset = Fore.RESET - def __str__(self) -> str: - """Make the value the string representation instead of the enum name.""" - return self.value - - @staticmethod - def colors() -> List[str]: - """Return a list of color names.""" - return [color.name for color in fg] - - @staticmethod - def get_value(name: str) -> str: - """Retrieve color code by name string.""" - return fg.__members__[name].value - # Background colors -# noinspection PyPep8Naming,DuplicatedCode -@unique -class bg(Enum): - """Enum class for background colors (to support IDE autocompletion).""" +# noinspection PyPep8Naming +class bg(ColorBase): + """Enum class for background colors""" black = Back.BLACK red = Back.RED green = Back.GREEN @@ -88,20 +109,6 @@ class bg(Enum): bright_white = Back.LIGHTWHITE_EX reset = Back.RESET - def __str__(self) -> str: - """Make the value the string representation instead of the enum name.""" - return self.value - - @staticmethod - def colors() -> List[str]: - """Return a list of color names.""" - return [color.name for color in bg] - - @staticmethod - def get_value(name: str) -> str: - """Retrieve color code by name string.""" - return bg.__members__[name].value - FG_RESET = fg.reset.value BG_RESET = bg.reset.value @@ -162,7 +169,7 @@ def fg_lookup(fg_name: Union[str, fg]) -> str: return fg_name.value try: - ansi_escape = fg.get_value(fg_name.lower()) + ansi_escape = fg[fg_name.lower()].value except KeyError: raise ValueError('Foreground color {!r} does not exist; must be one of: {}'.format(fg_name, fg.colors())) return ansi_escape @@ -180,7 +187,7 @@ def bg_lookup(bg_name: Union[str, bg]) -> str: return bg_name.value try: - ansi_escape = bg.get_value(bg_name.lower()) + ansi_escape = bg[bg_name.lower()].value except KeyError: raise ValueError('Background color {!r} does not exist; must be one of: {}'.format(bg_name, bg.colors())) return ansi_escape @@ -195,8 +202,10 @@ def style(text: Any, *, fg: Union[str, fg] = '', bg: Union[str, bg] = '', bold: to undo whatever styling was done at the beginning. :param text: Any object compatible with str.format() - :param fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name or enum. Defaults to no color. - :param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name or enum. Defaults to no color. + :param fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name or enum. + Defaults to no color. + :param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name or enum. + Defaults to no color. :param bold: apply the bold style if True. Can be combined with dim. Defaults to False. :param dim: apply the dim style if True. Can be combined with bold. Defaults to False. :param underline: apply the underline style if True. Defaults to False. diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index f735498d..a0e05ae9 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -529,7 +529,7 @@ argparse.ArgumentParser._match_argument = _match_argument_wrapper ############################################################################################################ -# noinspection PyCompatibility,PyShadowingBuiltins,PyShadowingBuiltins +# noinspection PyCompatibility,PyShadowingBuiltins class Cmd2HelpFormatter(argparse.RawTextHelpFormatter): """Custom help formatter to configure ordering of help text""" diff --git a/examples/async_printing.py b/examples/async_printing.py index fdba14a0..692ce769 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -10,7 +10,7 @@ import time from typing import List import cmd2 -from cmd2 import ansi +from cmd2 import style, fg ALERTS = ["Watch as this application prints alerts and updates the prompt", "This will only happen when the prompt is present", @@ -145,20 +145,20 @@ class AlerterApp(cmd2.Cmd): """ rand_num = random.randint(1, 20) - status_color = 'reset' + status_color = fg.reset if rand_num == 1: - status_color = 'bright_red' + status_color = fg.bright_red elif rand_num == 2: - status_color = 'bright_yellow' + status_color = fg.bright_yellow elif rand_num == 3: - status_color = 'cyan' + status_color = fg.cyan elif rand_num == 4: - status_color = 'bright_green' + status_color = fg.bright_green elif rand_num == 5: - status_color = 'bright_blue' + status_color = fg.bright_blue - return ansi.style(self.visible_prompt, fg=status_color) + return style(self.visible_prompt, fg=status_color) def _alerter_thread_func(self) -> None: """ Prints alerts and updates the prompt any time the prompt is showing """ diff --git a/examples/plumbum_colors.py b/examples/plumbum_colors.py index 94815f50..2887ef1f 100755 --- a/examples/plumbum_colors.py +++ b/examples/plumbum_colors.py @@ -31,36 +31,37 @@ import cmd2 from cmd2 import ansi from plumbum.colors import fg, bg -FG_COLORS = { - 'black': fg.Black, - 'red': fg.DarkRedA, - 'green': fg.MediumSpringGreen, - 'yellow': fg.LightYellow, - 'blue': fg.RoyalBlue1, - 'magenta': fg.Purple, - 'cyan': fg.SkyBlue1, - 'white': fg.White, - 'purple': fg.Purple, -} - -BG_COLORS = { - 'black': bg.BLACK, - 'red': bg.DarkRedA, - 'green': bg.MediumSpringGreen, - 'yellow': bg.LightYellow, - 'blue': bg.RoyalBlue1, - 'magenta': bg.Purple, - 'cyan': bg.SkyBlue1, - 'white': bg.White, -} - - -def get_fg(fg): - return str(FG_COLORS[fg]) - - -def get_bg(bg): - return str(BG_COLORS[bg]) + +class FgColors(ansi.ColorBase): + black = fg.Black + red = fg.DarkRedA + green = fg.MediumSpringGreen + yellow = fg.LightYellow + blue = fg.RoyalBlue1 + magenta = fg.Purple + cyan = fg.SkyBlue1 + white = fg.White + purple = fg.Purple + + +class BgColors(ansi.ColorBase): + black = bg.BLACK + red = bg.DarkRedA + green = bg.MediumSpringGreen + yellow = bg.LightYellow + blue = bg.RoyalBlue1 + magenta = bg.Purple + cyan = bg.SkyBlue1 + white = bg.White + purple = bg.Purple + + +def get_fg(name: str): + return str(FgColors[name]) + + +def get_bg(name: str): + return str(BgColors[name]) ansi.fg_lookup = get_fg @@ -84,8 +85,8 @@ class CmdLineApp(cmd2.Cmd): speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') - speak_parser.add_argument('-f', '--fg', choices=FG_COLORS, help='foreground color to apply to output') - speak_parser.add_argument('-b', '--bg', choices=BG_COLORS, help='background color to apply to output') + speak_parser.add_argument('-f', '--fg', choices=FgColors.colors(), help='foreground color to apply to output') + speak_parser.add_argument('-b', '--bg', choices=BgColors.colors(), help='background color to apply to output') speak_parser.add_argument('-l', '--bold', action='store_true', help='bold the output') speak_parser.add_argument('-u', '--underline', action='store_true', help='underline the output') speak_parser.add_argument('words', nargs='+', help='words to say') diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 51c3b50f..4a28b1a0 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -32,14 +32,14 @@ def test_style_none(): def test_style_fg(): base_str = HELLO_WORLD fg_color = 'blue' - ansi_str = ansi.fg.get_value(fg_color) + base_str + ansi.FG_RESET + ansi_str = ansi.fg[fg_color].value + base_str + ansi.FG_RESET assert ansi.style(base_str, fg=fg_color) == ansi_str def test_style_bg(): base_str = HELLO_WORLD bg_color = 'green' - ansi_str = ansi.bg.get_value(bg_color) + base_str + ansi.BG_RESET + ansi_str = ansi.bg[bg_color].value + base_str + ansi.BG_RESET assert ansi.style(base_str, bg=bg_color) == ansi_str @@ -65,7 +65,7 @@ def test_style_multi(): base_str = HELLO_WORLD fg_color = 'blue' bg_color = 'green' - ansi_str = (ansi.fg.get_value(fg_color) + ansi.bg.get_value(bg_color) + + ansi_str = (ansi.fg[fg_color].value + ansi.bg[bg_color].value + ansi.INTENSITY_BRIGHT + ansi.INTENSITY_DIM + ansi.UNDERLINE_ENABLE + base_str + ansi.FG_RESET + ansi.BG_RESET + @@ -85,7 +85,7 @@ def test_style_color_not_exist(): def test_fg_lookup_exist(): fg_color = 'green' - assert ansi.fg_lookup(fg_color) == ansi.fg.get_value(fg_color) + assert ansi.fg_lookup(fg_color) == ansi.fg_lookup(ansi.fg.green) def test_fg_lookup_nonexist(): @@ -94,8 +94,8 @@ def test_fg_lookup_nonexist(): def test_bg_lookup_exist(): - bg_color = 'green' - assert ansi.bg_lookup(bg_color) == ansi.bg.get_value(bg_color) + bg_color = 'red' + assert ansi.bg_lookup(bg_color) == ansi.bg_lookup(ansi.bg.red) def test_bg_lookup_nonexist(): @@ -121,20 +121,33 @@ def test_async_alert_str(cols, prompt, line, cursor, msg, expected): assert alert_str == expected -def test_fg_enum(): - assert ansi.fg_lookup('bright_red') == ansi.fg_lookup(ansi.fg.bright_red) +def test_cast_color_as_str(): + assert str(ansi.fg.blue) == ansi.fg.blue.value + assert str(ansi.bg.blue) == ansi.bg.blue.value + + +def test_color_str_building(): + from cmd2.ansi import fg, bg + assert fg.blue + "hello" == fg.blue.value + "hello" + assert bg.blue + "hello" == bg.blue.value + "hello" + assert fg.blue + "hello" + fg.reset == fg.blue.value + "hello" + fg.reset.value + assert bg.blue + "hello" + bg.reset == bg.blue.value + "hello" + bg.reset.value + assert fg.blue + bg.white + "hello" + fg.reset + bg.reset == \ + fg.blue.value + bg.white.value + "hello" + fg.reset.value + bg.reset.value -def test_fg_enum_to_str(): - assert str(ansi.fg.black) == ansi.fg_lookup('black') -def test_bg_enum(): +def test_color_nonunique_values(): + class Matching(ansi.ColorBase): + magenta = ansi.fg_lookup('magenta') + purple = ansi.fg_lookup('magenta') + assert sorted(Matching.colors()) == ['magenta', 'purple'] + + +def test_color_enum(): + assert ansi.fg_lookup('bright_red') == ansi.fg_lookup(ansi.fg.bright_red) assert ansi.bg_lookup('green') == ansi.bg_lookup(ansi.bg.green) -def test_bg_enum_to_str(): - assert str(ansi.bg.blue) == ansi.bg_lookup('blue') -def test_fg_colors(): +def test_colors_list(): assert list(ansi.fg.__members__.keys()) == ansi.fg.colors() - -def test_bg_colors(): assert list(ansi.bg.__members__.keys()) == ansi.bg.colors() |