summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2020-02-07 17:04:24 -0500
committerGitHub <noreply@github.com>2020-02-07 17:04:24 -0500
commit5def049472e1c261ad78ff56c5d5b3c6332e9bab (patch)
tree9750793bab06c519362712ee3e4c56ff7c7fc662
parent3f075900264fee32c356aac3d03b2568664ba684 (diff)
parent715af6059d06521c8f0054007a1105d211402d63 (diff)
downloadcmd2-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.md7
-rwxr-xr-xREADME.md2
-rw-r--r--cmd2/ansi.py91
-rw-r--r--cmd2/argparse_custom.py2
-rwxr-xr-xexamples/async_printing.py16
-rwxr-xr-xexamples/plumbum_colors.py65
-rw-r--r--tests/test_ansi.py45
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
diff --git a/README.md b/README.md
index 19bd259b..72053ef5 100755
--- a/README.md
+++ b/README.md
@@ -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()