diff options
-rw-r--r-- | cmd2/ansi.py | 56 | ||||
-rwxr-xr-x | examples/async_printing.py | 26 | ||||
-rwxr-xr-x | examples/colors.py | 92 | ||||
-rwxr-xr-x | examples/plumbum_colors.py | 84 | ||||
-rwxr-xr-x | examples/python_scripting.py | 5 | ||||
-rwxr-xr-x | examples/table_display.py | 3 | ||||
-rw-r--r-- | tests/test_ansi.py | 12 | ||||
-rw-r--r-- | tests/test_cmd2.py | 35 |
8 files changed, 92 insertions, 221 deletions
diff --git a/cmd2/ansi.py b/cmd2/ansi.py index f9e32166..e1a4671d 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -21,15 +21,15 @@ ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m') # Foreground color presets FG_COLORS = { + 'black': Fore.BLACK, + 'red': Fore.RED, + 'green': Fore.GREEN, + 'yellow': Fore.YELLOW, + 'blue': Fore.BLUE, + 'magenta': Fore.MAGENTA, + 'cyan': Fore.CYAN, + 'white': Fore.WHITE, 'reset': Fore.RESET, - 'gray': Fore.LIGHTBLACK_EX, - 'lightred': Fore.LIGHTRED_EX, - 'lightblue': Fore.LIGHTBLUE_EX, - 'lightgreen': Fore.LIGHTGREEN_EX, - 'lightyellow': Fore.LIGHTYELLOW_EX, - 'lightmagenta': Fore.LIGHTMAGENTA_EX, - 'lightcyan': Fore.LIGHTCYAN_EX, - 'lightwhite': Fore.LIGHTWHITE_EX, 'bright_black': Fore.LIGHTBLACK_EX, 'bright_red': Fore.LIGHTRED_EX, 'bright_green': Fore.LIGHTGREEN_EX, @@ -39,19 +39,18 @@ FG_COLORS = { 'bright_cyan': Fore.LIGHTCYAN_EX, 'bright_white': Fore.LIGHTWHITE_EX, } -FG_RESET = FG_COLORS['reset'] # Background color presets BG_COLORS = { + 'black': Back.BLACK, + 'red': Back.RED, + 'green': Back.GREEN, + 'yellow': Back.YELLOW, + 'blue': Back.BLUE, + 'magenta': Back.MAGENTA, + 'cyan': Back.CYAN, + 'white': Back.WHITE, 'reset': Back.RESET, - 'gray': Back.LIGHTBLACK_EX, - 'lightred': Back.LIGHTRED_EX, - 'lightblue': Back.LIGHTBLUE_EX, - 'lightgreen': Back.LIGHTGREEN_EX, - 'lightyellow': Back.LIGHTYELLOW_EX, - 'lightmagenta': Back.LIGHTMAGENTA_EX, - 'lightcyan': Back.LIGHTCYAN_EX, - 'lightwhite': Back.LIGHTWHITE_EX, 'bright_black': Back.LIGHTBLACK_EX, 'bright_red': Back.LIGHTRED_EX, 'bright_green': Back.LIGHTGREEN_EX, @@ -61,7 +60,10 @@ BG_COLORS = { 'bright_cyan': Back.LIGHTCYAN_EX, 'bright_white': Back.LIGHTWHITE_EX, } + +FG_RESET = FG_COLORS['reset'] BG_RESET = BG_COLORS['reset'] +RESET_ALL = Style.RESET_ALL # ANSI escape sequences not provided by colorama UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) @@ -104,8 +106,6 @@ def ansi_aware_write(fileobj: IO, msg: str) -> None: def fg_lookup(fg_name: str) -> str: """Look up ANSI escape codes based on foreground color name. - This function first searches the FG_COLORS dictionary and then falls back to searching colorama.Fore. - :param fg_name: foreground color name to look up ANSI escape code(s) for :return: ANSI escape code(s) associated with this color :raises ValueError if the color cannot be found @@ -113,18 +113,13 @@ def fg_lookup(fg_name: str) -> str: try: ansi_escape = FG_COLORS[fg_name.lower()] except KeyError: - try: - ansi_escape = getattr(Fore, fg_name.upper()) - except AttributeError: - raise ValueError('Foreground color {!r} does not exist.'.format(fg_name)) + raise ValueError('Foreground color {!r} does not exist.'.format(fg_name)) return ansi_escape def bg_lookup(bg_name: str) -> str: """Look up ANSI escape codes based on background color name. - This function first searches the BG_COLORS dictionary and then falls back to searching colorama.Back. - :param bg_name: background color name to look up ANSI escape code(s) for :return: ANSI escape code(s) associated with this color :raises ValueError if the color cannot be found @@ -132,10 +127,7 @@ def bg_lookup(bg_name: str) -> str: try: ansi_escape = BG_COLORS[bg_name.lower()] except KeyError: - try: - ansi_escape = getattr(Back, bg_name.upper()) - except AttributeError: - raise ValueError('Background color {!r} does not exist.'.format(bg_name)) + raise ValueError('Background color {!r} does not exist.'.format(bg_name)) return ansi_escape @@ -144,7 +136,7 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin Applies styles to text :param text: Any object compatible with str.format() - :param fg: foreground color. Expects color names in FG_COLORS (e.g. 'lightred'). Defaults to no color. + :param fg: foreground color. Expects color names in FG_COLORS (e.g. 'red'). Defaults to no color. :param bg: background color. Expects color names in BG_COLORS (e.g. 'black'). Defaults to no color. :param bold: apply the bold style if True. Defaults to False. :param underline: apply the underline style if True. Defaults to False. @@ -186,5 +178,5 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin # These can be altered to suit an application's needs and only need to be a # function with the following structure: func(str) -> str style_success = functools.partial(style, fg='green', bold=True) -style_warning = functools.partial(style, fg='lightyellow') -style_error = functools.partial(style, fg='lightred') +style_warning = functools.partial(style, fg='bright_yellow') +style_error = functools.partial(style, fg='bright_red') diff --git a/examples/async_printing.py b/examples/async_printing.py index 3089070f..b3618475 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -4,15 +4,13 @@ A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title """ - import random import threading import time from typing import List -from colorama import Fore - import cmd2 +from cmd2 import ansi ALERTS = ["Watch as this application prints alerts and updates the prompt", "This will only happen when the prompt is present", @@ -141,26 +139,12 @@ class AlerterApp(cmd2.Cmd): return alert_str def _generate_colored_prompt(self) -> str: - """ - Randomly generates a colored prompt + """Randomly generates a colored prompt :return: the new prompt """ - rand_num = random.randint(1, 20) - - status_color = Fore.RESET - - if rand_num == 1: - status_color = Fore.LIGHTRED_EX - elif rand_num == 2: - status_color = Fore.LIGHTYELLOW_EX - elif rand_num == 3: - status_color = Fore.CYAN - elif rand_num == 4: - status_color = Fore.LIGHTGREEN_EX - elif rand_num == 5: - status_color = Fore.LIGHTBLUE_EX - - return status_color + self.visible_prompt + Fore.RESET + fg_color = random.choice(list(ansi.FG_COLORS.keys())) + bg_color = random.choice(list(ansi.BG_COLORS.keys())) + return ansi.style(self.visible_prompt, fg=fg_color, bg=bg_color) def _alerter_thread_func(self) -> None: """ Prints alerts and updates the prompt any time the prompt is showing """ diff --git a/examples/colors.py b/examples/colors.py index f8a9dfdb..c0c221c8 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -6,7 +6,7 @@ A sample application for cmd2. Demonstrating colorized output. Experiment with the command line options on the `speak` command to see how different output colors ca -The colors setting has three possible values: +The allow_ansi setting has three possible values: Never poutput(), pfeedback(), and ppaged() strip all ANSI escape sequences @@ -16,68 +16,38 @@ Terminal (the default value) poutput(), pfeedback(), and ppaged() do not strip any ANSI escape sequences when the output is a terminal, but if the output is a pipe or a file the escape sequences are stripped. If you want colorized - output you must add ANSI escape sequences, preferably using some python - color library like `plumbum.colors`, `colorama`, `blessings`, or - `termcolor`. + output you must add ANSI escape sequences using either cmd2's internal ansi + module or another color library such as `plumbum.colors` or `colorama`. Always poutput(), pfeedback(), and ppaged() never strip ANSI escape sequences, regardless of the output destination """ - -import random import argparse import cmd2 -from colorama import Fore, Back - -FG_COLORS = { - 'black': Fore.BLACK, - 'red': Fore.RED, - 'green': Fore.GREEN, - 'yellow': Fore.YELLOW, - 'blue': Fore.BLUE, - 'magenta': Fore.MAGENTA, - 'cyan': Fore.CYAN, - 'white': Fore.WHITE, -} -BG_COLORS = { - 'black': Back.BLACK, - 'red': Back.RED, - 'green': Back.GREEN, - 'yellow': Back.YELLOW, - 'blue': Back.BLUE, - 'magenta': Back.MAGENTA, - 'cyan': Back.CYAN, - 'white': Back.WHITE, -} +from cmd2 import ansi class CmdLineApp(cmd2.Cmd): """Example cmd2 application demonstrating colorized output.""" - - # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist - # default_to_shell = True - MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] - MUMBLE_FIRST = ['so', 'like', 'well'] - MUMBLE_LAST = ['right?'] - def __init__(self): - shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) - shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=True, multiline_commands=['orate'], shortcuts=shortcuts) + super().__init__(use_ipython=True) self.maxrepeats = 3 # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' + # Should ANSI color output be allowed + self.allow_ansi = ansi.ANSI_TERMINAL + speak_parser = argparse.ArgumentParser() 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=ansi.FG_COLORS, help='foreground color to apply to output') + speak_parser.add_argument('-b', '--bg', choices=ansi.BG_COLORS, help='background color to apply to output') speak_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(speak_parser) @@ -92,49 +62,11 @@ class CmdLineApp(cmd2.Cmd): words.append(word) repetitions = args.repeat or 1 - - color_on = '' - if args.fg: - color_on += FG_COLORS[args.fg] - if args.bg: - color_on += BG_COLORS[args.bg] - color_off = Fore.RESET + Back.RESET + output_str = ansi.style(' '.join(words), fg=args.fg, bg=args.bg) for i in range(min(repetitions, self.maxrepeats)): # .poutput handles newlines, and accommodates output redirection too - self.poutput(color_on + ' '.join(words) + color_off) - - do_say = do_speak # now "say" is a synonym for "speak" - do_orate = do_speak # another synonym, but this one takes multi-line input - - mumble_parser = argparse.ArgumentParser() - mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat') - mumble_parser.add_argument('-f', '--fg', help='foreground color to apply to output') - mumble_parser.add_argument('-b', '--bg', help='background color to apply to output') - mumble_parser.add_argument('words', nargs='+', help='words to say') - - @cmd2.with_argparser(mumble_parser) - def do_mumble(self, args): - """Mumbles what you tell me to.""" - color_on = '' - if args.fg and args.fg in FG_COLORS: - color_on += FG_COLORS[args.fg] - if args.bg and args.bg in BG_COLORS: - color_on += BG_COLORS[args.bg] - color_off = Fore.RESET + Back.RESET - - repetitions = args.repeat or 1 - for i in range(min(repetitions, self.maxrepeats)): - output = [] - if random.random() < .33: - output.append(random.choice(self.MUMBLE_FIRST)) - for word in args.words: - if random.random() < .40: - output.append(random.choice(self.MUMBLES)) - output.append(word) - if random.random() < .25: - output.append(random.choice(self.MUMBLE_LAST)) - self.poutput(color_on + ' '.join(output) + color_off) + self.poutput(output_str) if __name__ == '__main__': diff --git a/examples/plumbum_colors.py b/examples/plumbum_colors.py index 2c57c22b..971029fa 100755 --- a/examples/plumbum_colors.py +++ b/examples/plumbum_colors.py @@ -6,7 +6,7 @@ A sample application for cmd2. Demonstrating colorized output using the plumbum Experiment with the command line options on the `speak` command to see how different output colors ca -The colors setting has three possible values: +The allow_ansi setting has three possible values: Never poutput(), pfeedback(), and ppaged() strip all ANSI escape sequences @@ -16,9 +16,8 @@ Terminal (the default value) poutput(), pfeedback(), and ppaged() do not strip any ANSI escape sequences when the output is a terminal, but if the output is a pipe or a file the escape sequences are stripped. If you want colorized - output you must add ANSI escape sequences, preferably using some python - color library like `plumbum.colors`, `colorama`, `blessings`, or - `termcolor`. + output you must add ANSI escape sequences using either cmd2's internal ansi + module or another color library such as `plumbum.colors` or `colorama`. Always poutput(), pfeedback(), and ppaged() never strip ANSI escape sequences, @@ -26,13 +25,11 @@ Always WARNING: This example requires the plumbum package, which isn't normally required by cmd2. """ - -import random import argparse import cmd2 -from colorama import Fore, Back -from plumbum.colors import fg, bg, reset +from cmd2 import ansi +from plumbum.colors import fg, bg FG_COLORS = { 'black': fg.Black, @@ -43,7 +40,9 @@ FG_COLORS = { 'magenta': fg.Purple, 'cyan': fg.SkyBlue1, 'white': fg.White, + 'purple': fg.Purple, } + BG_COLORS = { 'black': bg.BLACK, 'red': bg.DarkRedA, @@ -56,25 +55,31 @@ BG_COLORS = { } -class CmdLineApp(cmd2.Cmd): - """Example cmd2 application demonstrating colorized output.""" +def get_fg(fg): + return str(FG_COLORS[fg]) + + +def get_bg(bg): + return str(BG_COLORS[bg]) + + +ansi.fg_lookup = get_fg +ansi.bg_lookup = get_bg - # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist - # default_to_shell = True - MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] - MUMBLE_FIRST = ['so', 'like', 'well'] - MUMBLE_LAST = ['right?'] +class CmdLineApp(cmd2.Cmd): + """Example cmd2 application demonstrating colorized output.""" def __init__(self): - shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) - shortcuts.update({'&': 'speak'}) # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell - super().__init__(use_ipython=True, multiline_commands=['orate'], shortcuts=shortcuts) + super().__init__(use_ipython=True) self.maxrepeats = 3 # Make maxrepeats settable at runtime self.settable['maxrepeats'] = 'max repetitions for speak command' + # Should ANSI color output be allowed + self.allow_ansi = ansi.ANSI_TERMINAL + speak_parser = argparse.ArgumentParser() speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') @@ -95,52 +100,15 @@ class CmdLineApp(cmd2.Cmd): words.append(word) repetitions = args.repeat or 1 - - color_on = '' - if args.fg: - color_on += FG_COLORS[args.fg] - if args.bg: - color_on += BG_COLORS[args.bg] - color_off = reset + output_str = ansi.style(' '.join(words), fg=args.fg, bg=args.bg) for i in range(min(repetitions, self.maxrepeats)): # .poutput handles newlines, and accommodates output redirection too - self.poutput(color_on + ' '.join(words) + color_off) - - do_say = do_speak # now "say" is a synonym for "speak" - do_orate = do_speak # another synonym, but this one takes multi-line input - - mumble_parser = argparse.ArgumentParser() - mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat') - mumble_parser.add_argument('-f', '--fg', help='foreground color to apply to output') - mumble_parser.add_argument('-b', '--bg', help='background color to apply to output') - mumble_parser.add_argument('words', nargs='+', help='words to say') - - @cmd2.with_argparser(mumble_parser) - def do_mumble(self, args): - """Mumbles what you tell me to.""" - color_on = '' - if args.fg and args.fg in FG_COLORS: - color_on += FG_COLORS[args.fg] - if args.bg and args.bg in BG_COLORS: - color_on += BG_COLORS[args.bg] - color_off = Fore.RESET + Back.RESET - - repetitions = args.repeat or 1 - for i in range(min(repetitions, self.maxrepeats)): - output = [] - if random.random() < .33: - output.append(random.choice(self.MUMBLE_FIRST)) - for word in args.words: - if random.random() < .40: - output.append(random.choice(self.MUMBLES)) - output.append(word) - if random.random() < .25: - output.append(random.choice(self.MUMBLE_LAST)) - self.poutput(color_on + ' '.join(output) + color_off) + self.poutput(output_str) if __name__ == '__main__': import sys + c = CmdLineApp() sys.exit(c.cmdloop()) diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 0cbfddff..3d0a54a9 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -17,9 +17,8 @@ This application and the "scripts/conditional.py" script serve as an example for import argparse import os -from colorama import Fore - import cmd2 +from cmd2 import ansi class CmdLineApp(cmd2.Cmd): @@ -35,7 +34,7 @@ class CmdLineApp(cmd2.Cmd): def _set_prompt(self): """Set prompt so it displays the current working directory.""" self.cwd = os.getcwd() - self.prompt = Fore.CYAN + '{!r} $ '.format(self.cwd) + Fore.RESET + self.prompt = ansi.style('{!r} $ '.format(self.cwd), fg='cyan') def postcmd(self, stop: bool, line: str) -> bool: """Hook method executed just after a command dispatch is finished. diff --git a/examples/table_display.py b/examples/table_display.py index dcde7a81..cedd2ca0 100755 --- a/examples/table_display.py +++ b/examples/table_display.py @@ -9,7 +9,8 @@ You can use the arrow keys (left, right, up, and down) to scroll around the tabl You can quit out of the pager by typing "q". You can also search for text within the pager using "/". WARNING: This example requires the tableformatter module: https://github.com/python-tableformatter/tableformatter -- pip install tableformatter +and either the colored or colorama module +- pip install tableformatter colorama """ from typing import Tuple diff --git a/tests/test_ansi.py b/tests/test_ansi.py index b32bdddf..bf628ef0 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -71,11 +71,7 @@ def test_style_color_not_exist(): ansi.style(base_str, fg='blue', bg='fake') -def test_fg_lookup_dict(): - assert ansi.fg_lookup('gray') == Fore.LIGHTBLACK_EX - - -def test_fg_lookup_colorama(): +def test_fg_lookup_exist(): assert ansi.fg_lookup('green') == Fore.GREEN @@ -84,11 +80,7 @@ def test_fg_lookup_nonexist(): ansi.fg_lookup('foo') -def test_bg_lookup_dict(): - assert ansi.bg_lookup('gray') == Back.LIGHTBLACK_EX - - -def test_bg_lookup_colorama(): +def test_bg_lookup_exist(): assert ansi.bg_lookup('green') == Back.GREEN diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index c820292c..1936c75c 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -11,7 +11,6 @@ import os import sys import tempfile -from colorama import Fore, Back, Style import pytest # Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available @@ -846,9 +845,9 @@ def test_ansi_prompt_not_esacped(base_app): def test_ansi_prompt_escaped(): from cmd2.rl_utils import rl_make_safe_prompt app = cmd2.Cmd() - color = Fore.CYAN + color = 'cyan' prompt = 'InColor' - color_prompt = color + prompt + Fore.RESET + color_prompt = ansi.style(prompt, fg=color) readline_hack_start = "\x01" readline_hack_end = "\x02" @@ -857,11 +856,11 @@ def test_ansi_prompt_escaped(): assert prompt != color_prompt if sys.platform.startswith('win'): # PyReadline on Windows doesn't suffer from the GNU readline bug which requires the hack - assert readline_safe_prompt.startswith(color) - assert readline_safe_prompt.endswith(Fore.RESET) + assert readline_safe_prompt.startswith(ansi.fg_lookup(color)) + assert readline_safe_prompt.endswith(ansi.FG_RESET) else: - assert readline_safe_prompt.startswith(readline_hack_start + color + readline_hack_end) - assert readline_safe_prompt.endswith(readline_hack_start + Fore.RESET + readline_hack_end) + assert readline_safe_prompt.startswith(readline_hack_start + ansi.fg_lookup(color) + readline_hack_end) + assert readline_safe_prompt.endswith(readline_hack_start + ansi.FG_RESET + readline_hack_end) class HelpApp(cmd2.Cmd): @@ -1460,17 +1459,22 @@ def test_poutput_none(outsim_app): def test_poutput_ansi_always(outsim_app): msg = 'Hello World' ansi.allow_ansi = ansi.ANSI_ALWAYS - outsim_app.poutput(ansi.style(msg, fg='cyan')) + colored_msg = ansi.style(msg, fg='cyan') + outsim_app.poutput(colored_msg) out = outsim_app.stdout.getvalue() - expected = Fore.CYAN + msg + Fore.RESET + '\n' + expected = colored_msg + '\n' + assert colored_msg != msg assert out == expected + def test_poutput_ansi_never(outsim_app): msg = 'Hello World' ansi.allow_ansi = ansi.ANSI_NEVER - outsim_app.poutput(ansi.style(msg, fg='cyan')) + colored_msg = ansi.style(msg, fg='cyan') + outsim_app.poutput(colored_msg) out = outsim_app.stdout.getvalue() expected = msg + '\n' + assert colored_msg != msg assert out == expected @@ -1769,7 +1773,7 @@ def test_ppaged_strips_ansi_when_redirecting(outsim_app): end = '\n' ansi.allow_ansi = ansi.ANSI_TERMINAL outsim_app._redirecting = True - outsim_app.ppaged(Fore.RED + msg) + outsim_app.ppaged(ansi.style(msg, fg='red')) out = outsim_app.stdout.getvalue() assert out == msg + end @@ -1778,9 +1782,10 @@ def test_ppaged_strips_ansi_when_redirecting_if_always(outsim_app): end = '\n' ansi.allow_ansi = ansi.ANSI_ALWAYS outsim_app._redirecting = True - outsim_app.ppaged(Fore.RED + msg) + colored_msg = ansi.style(msg, fg='red') + outsim_app.ppaged(colored_msg) out = outsim_app.stdout.getvalue() - assert out == Fore.RED + msg + end + assert out == colored_msg + end # we override cmd.parseline() so we always get consistent # command parsing by parent methods we don't override @@ -1904,9 +1909,7 @@ class AnsiApp(cmd2.Cmd): self.perror(args) def do_echo_error(self, args): - color_on = Fore.RED + Back.BLACK - color_off = Style.RESET_ALL - self.poutput(color_on + args + color_off) + self.poutput(ansi.style(args, fg='red')) # perror uses colors by default self.perror(args) |