summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd2/ansi.py56
-rw-r--r--cmd2/argparse_completer.py7
-rw-r--r--cmd2/cmd2.py44
-rwxr-xr-xexamples/pirate.py3
-rw-r--r--tests/test_cmd2.py4
-rw-r--r--tests/test_utils.py6
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