diff options
-rw-r--r-- | cmd2/ansi.py | 56 | ||||
-rw-r--r-- | cmd2/argparse_completer.py | 7 | ||||
-rw-r--r-- | cmd2/cmd2.py | 44 | ||||
-rwxr-xr-x | examples/pirate.py | 3 | ||||
-rw-r--r-- | tests/test_cmd2.py | 4 | ||||
-rw-r--r-- | tests/test_utils.py | 6 |
6 files changed, 70 insertions, 50 deletions
diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 889f1cb3..667a279d 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -72,46 +72,66 @@ def ansi_safe_wcswidth(text: str) -> int: return wcswidth(strip_ansi(text)) -def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: +class TextStyle: + """Style settings for text""" + + def __init__(self, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False): + """ + Initializer + :param fg: foreground color. Expects color names in FG_COLORS (e.g. 'lightred'). Defaults to blank. + :param bg: background color. Expects color names in BG_COLORS (e.g. 'black'). Defaults to blank. + :param bold: apply the bold style if True. Defaults to False. + :param underline: apply the underline style if True. Defaults to False. + """ + self.fg = fg + self.bg = bg + self.bold = bold + self.underline = underline + + +# Default styles. These can be altered to suit an application's needs. +SuccessStyle = TextStyle(fg='green') +WarningStyle = TextStyle(fg='lightyellow') +ErrorStyle = TextStyle(fg='lightred') + + +def style(text: Any, text_style: TextStyle) -> str: """ - Applies styles to text + Applies a style to text :param text: Any object compatible with str.format() - :param fg: foreground color. Accepts color names like 'red' or 'blue' - :param bg: background color. Accepts color names like 'red' or 'blue' - :param bold: apply the bold style if True. Defaults to False. - :param underline: apply the underline style if True. Defaults to False. + :param text_style: the style to be applied """ values = [] text = "{}".format(text) # Add styles - if fg: + if text_style.fg: try: - values.append(FG_COLORS[fg.lower()]) + values.append(FG_COLORS[text_style.fg.lower()]) except KeyError: - raise ValueError('Color {} does not exist.'.format(fg)) - if bg: + raise ValueError('Color {} does not exist.'.format(text_style.fg)) + if text_style.bg: try: - values.append(BG_COLORS[bg.lower()]) + values.append(BG_COLORS[text_style.bg.lower()]) except KeyError: - raise ValueError('Color {} does not exist.'.format(bg)) - if bold: + raise ValueError('Color {} does not exist.'.format(text_style.bg)) + if text_style.bold: values.append(Style.BRIGHT) - if underline: + if text_style.underline: underline_enable = colorama.ansi.code_to_chars(4) values.append(underline_enable) values.append(text) # Remove styles - if fg: + if text_style.fg: values.append(FG_COLORS['reset']) - if bg: + if text_style.bg: values.append(BG_COLORS['reset']) - if bold: + if text_style.bold: values.append(Style.NORMAL) - if underline: + if text_style.underline: underline_disable = colorama.ansi.code_to_chars(24) values.append(underline_disable) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index c3f73552..fd0eb0ec 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -66,9 +66,7 @@ import sys from argparse import ZERO_OR_MORE, ONE_OR_MORE, ArgumentError, _, _get_action_name, SUPPRESS from typing import List, Dict, Tuple, Callable, Union -from colorama import Fore - -from .ansi import ansi_safe_wcswidth +from .ansi import ansi_safe_wcswidth, style, ErrorStyle from .rl_utils import rl_force_redisplay # attribute that can optionally added to an argparse argument (called an Action) to @@ -996,7 +994,8 @@ class ACArgumentParser(argparse.ArgumentParser): linum += 1 self.print_usage(sys.stderr) - self.exit(2, Fore.LIGHTRED_EX + '{}\n\n'.format(formatted_message) + Fore.RESET) + formatted_message = style(formatted_message, ErrorStyle) + self.exit(2, '{}\n\n'.format(formatted_message)) def format_help(self) -> str: """Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters""" diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 20e26e15..0ea958b0 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -43,7 +43,6 @@ from contextlib import redirect_stdout from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union, IO import colorama -from colorama import Fore from . import ansi from . import constants @@ -61,7 +60,7 @@ if rl_type == RlType.NONE: # pragma: no cover rl_warning = "Readline features including tab completion have been disabled since no \n" \ "supported version of readline was found. To resolve this, install \n" \ "pyreadline on Windows or gnureadline on Mac.\n\n" - sys.stderr.write(Fore.LIGHTYELLOW_EX + rl_warning + Fore.RESET) + sys.stderr.write(ansi.style(rl_warning, ansi.WarningStyle)) else: from .rl_utils import rl_force_redisplay, readline @@ -608,27 +607,27 @@ class Cmd(cmd.Cmd): if self.broken_pipe_warning: sys.stderr.write(self.broken_pipe_warning) - def perror(self, msg: Any, *, end: str = '\n', add_color: bool = True) -> None: + def perror(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None: """Print message to sys.stderr :param msg: message to print (anything convertible to a str with '{}'.format() is OK) :param end: string appended after the end of the message, default a newline - :param add_color: If True, then color will be added to the message text. Set to False in cases where - the message text already has the desired style. Defaults to True. + :param apply_style: If True, then ErrorStyle will be applied to the message text. Set to False in cases + where the message text already has the desired style. Defaults to True. """ - if add_color: - final_msg = ansi.style(msg, fg='lightred') + if apply_style: + final_msg = ansi.style(msg, ansi.ErrorStyle) else: final_msg = "{}".format(msg) self._decolorized_write(sys.stderr, final_msg + end) - def pexcept(self, msg: Any, *, end: str = '\n', add_color: bool = True) -> None: + def pexcept(self, msg: Any, *, end: str = '\n', apply_style: bool = True) -> None: """Print Exception message to sys.stderr. If debug is true, print exception traceback if one exists. :param msg: message or Exception to print :param end: string appended after the end of the message, default a newline - :param add_color: If True, then color will be added to the message text. Set to False in cases where - the message text already has the desired style. Defaults to True. + :param apply_style: If True, then ErrorStyle will be applied to the message text. Set to False in cases + where the message text already has the desired style. Defaults to True. """ if self.debug and sys.exc_info() != (None, None, None): import traceback @@ -639,15 +638,15 @@ class Cmd(cmd.Cmd): else: final_msg = "{}".format(msg) - if add_color: - final_msg = ansi.style(final_msg, fg='lightred') + if apply_style: + final_msg = ansi.style(final_msg, ansi.ErrorStyle) if not self.debug: warning = "\nTo enable full traceback, run the following command: 'set debug true'" - final_msg += ansi.style(warning, fg="lightyellow") + final_msg += ansi.style(warning, ansi.WarningStyle) - # Set add_color to False since style has already been applied - self.perror(final_msg, end=end, add_color=False) + # Set apply_style to False since style has already been applied + self.perror(final_msg, end=end, apply_style=False) def pfeedback(self, msg: str) -> None: """For printing nonessential feedback. Can be silenced with `quiet`. @@ -3247,7 +3246,7 @@ class Cmd(cmd.Cmd): if args.__statement__.command == "pyscript": warning = ("pyscript has been renamed and will be removed in the next release, " "please use run_pyscript instead\n") - self.perror(ansi.style(warning, fg="lightyellow")) + self.perror(ansi.style(warning, ansi.WarningStyle)) return py_return @@ -3556,7 +3555,7 @@ class Cmd(cmd.Cmd): # Check if all commands ran if commands_run < len(history): warning = "Command {} triggered a stop and ended transcript generation early".format(commands_run) - self.perror(ansi.style(warning, fg="lightyellow")) + self.perror(ansi.style(warning, ansi.WarningStyle)) # finally, we can write the transcript out to the file try: @@ -3676,7 +3675,7 @@ class Cmd(cmd.Cmd): if args.__statement__.command == "load": warning = ("load has been renamed and will be removed in the next release, " "please use run_script instead\n") - self.perror(ansi.style(warning, fg="lightyellow")) + self.perror(ansi.style(warning, ansi.WarningStyle)) # load has been deprecated do_load = do_run_script @@ -3703,7 +3702,7 @@ class Cmd(cmd.Cmd): if args.__statement__.command == "_relative_load": warning = ("_relative_load has been renamed and will be removed in the next release, " "please use _relative_run_script instead\n") - self.perror(ansi.style(warning, fg="lightyellow")) + self.perror(ansi.style(warning, ansi.WarningStyle)) file_path = args.file_path # NOTE: Relative path is an absolute path, it is just relative to the current script directory @@ -3739,12 +3738,13 @@ class Cmd(cmd.Cmd): verinfo = ".".join(map(str, sys.version_info[:3])) num_transcripts = len(transcripts_expanded) plural = '' if len(transcripts_expanded) == 1 else 's' - self.poutput(ansi.style(utils.center_text('cmd2 transcript test', pad='='), bold=True)) + self.poutput(ansi.style(utils.center_text('cmd2 transcript test', pad='='), ansi.TextStyle(bold=True))) self.poutput('platform {} -- Python {}, cmd2-{}, readline-{}'.format(sys.platform, verinfo, cmd2.__version__, rl_type)) self.poutput('cwd: {}'.format(os.getcwd())) self.poutput('cmd2 app: {}'.format(sys.argv[0])) - self.poutput(ansi.style('collected {} transcript{}'.format(num_transcripts, plural), bold=True)) + self.poutput(ansi.style('collected {} transcript{}'.format(num_transcripts, plural), + ansi.TextStyle(bold=True))) self.__class__.testfiles = transcripts_expanded sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main() @@ -3757,7 +3757,7 @@ class Cmd(cmd.Cmd): if test_results.wasSuccessful(): self._decolorized_write(sys.stderr, stream.read()) finish_msg = '{0} transcript{1} passed in {2:.3f} seconds'.format(num_transcripts, plural, execution_time) - finish_msg = ansi.style(utils.center_text(finish_msg, pad='='), fg="green", bold=True) + finish_msg = ansi.style(utils.center_text(finish_msg, pad='='), ansi.TextStyle(fg="green", bold=True)) self.poutput(finish_msg) else: # Strip off the initial traceback which isn't particularly useful for end users diff --git a/examples/pirate.py b/examples/pirate.py index 0d9d89bc..fad6e2c9 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -9,6 +9,7 @@ It demonstrates many features of cmd2. import argparse import cmd2 +import cmd2.ansi from cmd2.constants import MULTILINE_TERMINATOR @@ -69,7 +70,7 @@ class Pirate(cmd2.Cmd): def do_sing(self, arg): """Sing a colorful song.""" - self.poutput(cmd2.ansi.style(arg, fg=self.songcolor)) + self.poutput(cmd2.ansi.style(arg, cmd2.ansi.TextStyle(fg=self.songcolor))) yo_parser = argparse.ArgumentParser() yo_parser.add_argument('--ho', type=int, default=2, help="How often to chant 'ho'") diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 3b6089f4..80077a68 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1460,7 +1460,7 @@ def test_poutput_none(outsim_app): def test_poutput_color_always(outsim_app): msg = 'Hello World' outsim_app.colors = 'Always' - outsim_app.poutput(ansi.style(msg, fg='cyan')) + outsim_app.poutput(ansi.style(msg, ansi.TextStyle(fg='cyan'))) out = outsim_app.stdout.getvalue() expected = Fore.CYAN + msg + Fore.RESET + '\n' assert out == expected @@ -1468,7 +1468,7 @@ def test_poutput_color_always(outsim_app): def test_poutput_color_never(outsim_app): msg = 'Hello World' outsim_app.colors = 'Never' - outsim_app.poutput(ansi.style(msg, fg='cyan')) + outsim_app.poutput(ansi.style(msg, ansi.TextStyle(fg='cyan'))) out = outsim_app.stdout.getvalue() expected = msg + '\n' assert out == expected diff --git a/tests/test_utils.py b/tests/test_utils.py index 88eb0901..6e3600db 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -29,18 +29,18 @@ def test_ansi_safe_wcswidth(): def test_style(): base_str = HELLO_WORLD ansi_str = Fore.BLUE + Back.GREEN + base_str + Fore.RESET + Back.RESET - assert ansi.style(base_str, fg='blue', bg='green') == ansi_str + assert ansi.style(base_str, ansi.TextStyle(fg='blue', bg='green')) == ansi_str def test_style_color_not_exist(): base_str = HELLO_WORLD try: - ansi.style(base_str, fg='hello', bg='green') + ansi.style(base_str, ansi.TextStyle(fg='fake', bg='green')) assert False except ValueError: assert True try: - ansi.style(base_str, fg='blue', bg='hello') + ansi.style(base_str, ansi.TextStyle(fg='blue', bg='fake')) assert False except ValueError: assert True |