From e34bba44ef53228aeba613ac6e928b2c315111cf Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 02:33:18 -0400 Subject: Moved code related to ANSI escape codes to new file called ansi.py --- cmd2/ansi.py | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 cmd2/ansi.py (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py new file mode 100644 index 00000000..889f1cb3 --- /dev/null +++ b/cmd2/ansi.py @@ -0,0 +1,118 @@ +# coding=utf-8 +"""Support for ANSI escape codes which are used for things like applying style to text""" +import re +from typing import Any + +import colorama +from colorama import Fore, Back, Style +from wcwidth import wcswidth + +# Regular expression to match ANSI escape codes +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, + '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, + 'reset': Fore.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, + '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, + 'reset': Back.RESET, +} + + +def strip_ansi(text: str) -> str: + """Strip ANSI escape codes from a string. + + :param text: string which may contain ANSI escape codes + :return: the same string with any ANSI escape codes removed + """ + return ANSI_ESCAPE_RE.sub('', text) + + +def ansi_safe_wcswidth(text: str) -> int: + """ + Wraps wcswidth to make it compatible with colored strings + + :param text: the string being measured + """ + # Strip ANSI escape codes since they cause wcswidth to return -1 + return wcswidth(strip_ansi(text)) + + +def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: + """ + Applies styles 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. + """ + values = [] + text = "{}".format(text) + + # Add styles + if fg: + try: + values.append(FG_COLORS[fg.lower()]) + except KeyError: + raise ValueError('Color {} does not exist.'.format(fg)) + if bg: + try: + values.append(BG_COLORS[bg.lower()]) + except KeyError: + raise ValueError('Color {} does not exist.'.format(bg)) + if bold: + values.append(Style.BRIGHT) + if underline: + underline_enable = colorama.ansi.code_to_chars(4) + values.append(underline_enable) + + values.append(text) + + # Remove styles + if fg: + values.append(FG_COLORS['reset']) + if bg: + values.append(BG_COLORS['reset']) + if bold: + values.append(Style.NORMAL) + if underline: + underline_disable = colorama.ansi.code_to_chars(24) + values.append(underline_disable) + + return "".join(values) -- cgit v1.2.1 From 72044030810dd293699a6bae8853e1cfd0b4887b Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 10:36:24 -0400 Subject: Added TextStyle class and default implementations for various message types like Warning, Error, and Succes --- cmd2/ansi.py | 56 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 18 deletions(-) (limited to 'cmd2/ansi.py') 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) -- cgit v1.2.1 From cd396d3ed9c2ea5aafa7f42d7396b4f0f6cd7941 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 12:26:54 -0400 Subject: Combined some logic in style --- cmd2/ansi.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 667a279d..5b3dff2c 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -72,6 +72,11 @@ def ansi_safe_wcswidth(text: str) -> int: return wcswidth(strip_ansi(text)) +# ANSI escape strings not provided by colorama +UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) +UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) + + class TextStyle: """Style settings for text""" @@ -102,37 +107,35 @@ def style(text: Any, text_style: TextStyle) -> str: :param text: Any object compatible with str.format() :param text_style: the style to be applied """ - values = [] + # List of strings that add style + additions = [] + + # List of strings that remove style + removals = [] + + # Convert the text object into a string if it isn't already one text = "{}".format(text) - # Add styles if text_style.fg: try: - values.append(FG_COLORS[text_style.fg.lower()]) + additions.append(FG_COLORS[text_style.fg.lower()]) + removals.append(FG_COLORS['reset']) except KeyError: raise ValueError('Color {} does not exist.'.format(text_style.fg)) + if text_style.bg: try: - values.append(BG_COLORS[text_style.bg.lower()]) + additions.append(BG_COLORS[text_style.bg.lower()]) + removals.append(BG_COLORS['reset']) except KeyError: raise ValueError('Color {} does not exist.'.format(text_style.bg)) - if text_style.bold: - values.append(Style.BRIGHT) - if text_style.underline: - underline_enable = colorama.ansi.code_to_chars(4) - values.append(underline_enable) - - values.append(text) - # Remove styles - if text_style.fg: - values.append(FG_COLORS['reset']) - if text_style.bg: - values.append(BG_COLORS['reset']) if text_style.bold: - values.append(Style.NORMAL) + additions.append(Style.BRIGHT) + removals.append(Style.NORMAL) + if text_style.underline: - underline_disable = colorama.ansi.code_to_chars(24) - values.append(underline_disable) + additions.append(UNDERLINE_ENABLE) + removals.append(UNDERLINE_DISABLE) - return "".join(values) + return "".join(additions) + text + "".join(removals) -- cgit v1.2.1 From 9f07daaff4763989c89ab51f64e190dc5ba825fe Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 13:04:16 -0400 Subject: Changed signature of style() to allow for simpler calling and overriding of settings in a provided TextStyle --- cmd2/ansi.py | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 5b3dff2c..f10c29ea 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,7 +1,7 @@ # coding=utf-8 """Support for ANSI escape codes which are used for things like applying style to text""" import re -from typing import Any +from typing import Any, Optional import colorama from colorama import Fore, Back, Style @@ -95,17 +95,32 @@ class TextStyle: # Default styles. These can be altered to suit an application's needs. -SuccessStyle = TextStyle(fg='green') +SuccessStyle = TextStyle(fg='green', bold=True) WarningStyle = TextStyle(fg='lightyellow') ErrorStyle = TextStyle(fg='lightred') -def style(text: Any, text_style: TextStyle) -> str: +def style(text: Any, text_style: Optional[TextStyle] = None, *, + fg: Optional[str] = None, bg: Optional[str] = None, + bold: Optional[bool] = None, underline: Optional[bool] = None) -> str: """ Applies a style to text :param text: Any object compatible with str.format() - :param text_style: the style to be applied + :param text_style: a TextStyle object. Defaults to None. + + The following are keyword-only parameters which allow calling style without creating a TextStyle object + style(text, fg='blue') + + They can also be used to override a setting in a provided TextStyle + style(text, ErrorStyle, underline=True) + + :param fg: foreground color. Expects color names in FG_COLORS (e.g. 'lightred'). Defaults to None. + :param bg: background color. Expects color names in BG_COLORS (e.g. 'black'). Defaults to None. + :param bold: apply the bold style if True. Defaults to None. + :param underline: apply the underline style if True. Defaults to None. + + :return: the stylized string """ # List of strings that add style additions = [] @@ -116,26 +131,38 @@ def style(text: Any, text_style: TextStyle) -> str: # Convert the text object into a string if it isn't already one text = "{}".format(text) - if text_style.fg: + # Figure out what parameters to use + if fg is None and text_style is not None: + fg = text_style.fg + if bg is None and text_style is not None: + bg = text_style.bg + if bold is None and text_style is not None: + bold = text_style.bold + if underline is None and text_style is not None: + underline = text_style.underline + + # Process the style settings + if fg: try: - additions.append(FG_COLORS[text_style.fg.lower()]) + additions.append(FG_COLORS[fg.lower()]) removals.append(FG_COLORS['reset']) except KeyError: - raise ValueError('Color {} does not exist.'.format(text_style.fg)) + raise ValueError('Color {} does not exist.'.format(fg)) - if text_style.bg: + if bg: try: - additions.append(BG_COLORS[text_style.bg.lower()]) + additions.append(BG_COLORS[bg.lower()]) removals.append(BG_COLORS['reset']) except KeyError: - raise ValueError('Color {} does not exist.'.format(text_style.bg)) + raise ValueError('Color {} does not exist.'.format(bg)) - if text_style.bold: + if bold: additions.append(Style.BRIGHT) removals.append(Style.NORMAL) - if text_style.underline: + if underline: additions.append(UNDERLINE_ENABLE) removals.append(UNDERLINE_DISABLE) + # Combine the ANSI escape strings with the text return "".join(additions) + text + "".join(removals) -- cgit v1.2.1 From c96648038289017531f68eeb7dcfddfe9ea85735 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 17:06:32 -0400 Subject: Changed default styles to use a more flexible approach which could be used to call any function to add style --- cmd2/ansi.py | 62 +++++++++++++----------------------------------------------- 1 file changed, 13 insertions(+), 49 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index f10c29ea..97fb1e8a 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,7 +1,8 @@ # coding=utf-8 """Support for ANSI escape codes which are used for things like applying style to text""" +import functools import re -from typing import Any, Optional +from typing import Any import colorama from colorama import Fore, Back, Style @@ -77,48 +78,15 @@ UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) -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', bold=True) -WarningStyle = TextStyle(fg='lightyellow') -ErrorStyle = TextStyle(fg='lightred') - - -def style(text: Any, text_style: Optional[TextStyle] = None, *, - fg: Optional[str] = None, bg: Optional[str] = None, - bold: Optional[bool] = None, underline: Optional[bool] = None) -> str: +def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: """ Applies a style to text :param text: Any object compatible with str.format() - :param text_style: a TextStyle object. Defaults to None. - - The following are keyword-only parameters which allow calling style without creating a TextStyle object - style(text, fg='blue') - - They can also be used to override a setting in a provided TextStyle - style(text, ErrorStyle, underline=True) - - :param fg: foreground color. Expects color names in FG_COLORS (e.g. 'lightred'). Defaults to None. - :param bg: background color. Expects color names in BG_COLORS (e.g. 'black'). Defaults to None. - :param bold: apply the bold style if True. Defaults to None. - :param underline: apply the underline style if True. Defaults to None. + :param fg: foreground color. Expects color names in FG_COLORS (e.g. 'lightred'). 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. :return: the stylized string """ @@ -131,16 +99,6 @@ def style(text: Any, text_style: Optional[TextStyle] = None, *, # Convert the text object into a string if it isn't already one text = "{}".format(text) - # Figure out what parameters to use - if fg is None and text_style is not None: - fg = text_style.fg - if bg is None and text_style is not None: - bg = text_style.bg - if bold is None and text_style is not None: - bold = text_style.bold - if underline is None and text_style is not None: - underline = text_style.underline - # Process the style settings if fg: try: @@ -166,3 +124,9 @@ def style(text: Any, text_style: Optional[TextStyle] = None, *, # Combine the ANSI escape strings with the text return "".join(additions) + text + "".join(removals) + + +# Default styles. These can be altered to suit an application's needs. +style_success = functools.partial(style, fg='green', bold=True) +style_warning = functools.partial(style, fg='lightyellow') +style_error = functools.partial(style, fg='lightred') -- cgit v1.2.1 From 92f8e3d616c836748638a19ad954c7e050059f21 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 17:15:43 -0400 Subject: Updated documentation --- cmd2/ansi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 97fb1e8a..8980b1d4 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -80,7 +80,7 @@ UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: """ - Applies a style to text + 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. @@ -126,7 +126,9 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin return "".join(additions) + text + "".join(removals) -# Default styles. These can be altered to suit an application's needs. +# Default styles for printing strings of various types. +# 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') -- cgit v1.2.1 From 2f9aab59acbbfc177a924afe7601b9021a4b1399 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 26 Jun 2019 17:44:25 -0400 Subject: Renamed colors setting to allow_ansi --- cmd2/ansi.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 8980b1d4..7ae9016e 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,5 +1,5 @@ # coding=utf-8 -"""Support for ANSI escape codes which are used for things like applying style to text""" +"""Support for ANSI escape sequences which are used for things like applying style to text""" import functools import re from typing import Any @@ -8,7 +8,7 @@ import colorama from colorama import Fore, Back, Style from wcwidth import wcswidth -# Regular expression to match ANSI escape codes +# Regular expression to match ANSI escape sequences ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m') # Foreground color presets @@ -55,10 +55,10 @@ BG_COLORS = { def strip_ansi(text: str) -> str: - """Strip ANSI escape codes from a string. + """Strip ANSI escape sequences from a string. - :param text: string which may contain ANSI escape codes - :return: the same string with any ANSI escape codes removed + :param text: string which may contain ANSI escape sequences + :return: the same string with any ANSI escape sequences removed """ return ANSI_ESCAPE_RE.sub('', text) @@ -69,11 +69,11 @@ def ansi_safe_wcswidth(text: str) -> int: :param text: the string being measured """ - # Strip ANSI escape codes since they cause wcswidth to return -1 + # Strip ANSI escape sequences since they cause wcswidth to return -1 return wcswidth(strip_ansi(text)) -# ANSI escape strings not provided by colorama +# ANSI escape sequences not provided by colorama UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) @@ -122,7 +122,7 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin additions.append(UNDERLINE_ENABLE) removals.append(UNDERLINE_DISABLE) - # Combine the ANSI escape strings with the text + # Combine the ANSI escape sequences with the text return "".join(additions) + text + "".join(removals) -- cgit v1.2.1 From 447479ecd6ef2cf01434485c4c93537c8be1a566 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 27 Jun 2019 11:03:09 -0400 Subject: Made allow_ansi an application-wide setting and moved it to ansi.py --- cmd2/ansi.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 7ae9016e..3e5fdbc2 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -8,6 +8,14 @@ import colorama from colorama import Fore, Back, Style from wcwidth import wcswidth +# Values for allow_ansi setting +ANSI_NEVER = 'Never' +ANSI_TERMINAL = 'Terminal' +ANSI_ALWAYS = 'Always' + +# Controls when ANSI escape sequences are allowed in output +allow_ansi = ANSI_TERMINAL + # Regular expression to match ANSI escape sequences ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m') -- cgit v1.2.1 From 0dbb4d208a51b15ca814c00d2eceb43436d15dc1 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 27 Jun 2019 11:30:20 -0400 Subject: Moved cmd2.Cmd._decolorized_write() to ansi.py and renamed it to ansi_aware_write(). --- cmd2/ansi.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 3e5fdbc2..a856f031 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -2,7 +2,7 @@ """Support for ANSI escape sequences which are used for things like applying style to text""" import functools import re -from typing import Any +from typing import Any, IO import colorama from colorama import Fore, Back, Style @@ -61,9 +61,14 @@ BG_COLORS = { 'reset': Back.RESET, } +# ANSI escape sequences not provided by colorama +UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) +UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) + def strip_ansi(text: str) -> str: - """Strip ANSI escape sequences from a string. + """ + Strip ANSI escape sequences from a string. :param text: string which may contain ANSI escape sequences :return: the same string with any ANSI escape sequences removed @@ -73,7 +78,7 @@ def strip_ansi(text: str) -> str: def ansi_safe_wcswidth(text: str) -> int: """ - Wraps wcswidth to make it compatible with colored strings + Wrap wcswidth to make it compatible with strings that contains ANSI escape sequences :param text: the string being measured """ @@ -81,9 +86,17 @@ def ansi_safe_wcswidth(text: str) -> int: return wcswidth(strip_ansi(text)) -# ANSI escape sequences not provided by colorama -UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) -UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24) +def ansi_aware_write(fileobj: IO, msg: str) -> None: + """ + Write a string to a fileobject and strip its ANSI escape sequences if required by allow_ansi setting + + :param fileobj: the file object being written to + :param msg: the string being written + """ + if allow_ansi.lower() == ANSI_NEVER.lower() or \ + (allow_ansi.lower() == ANSI_TERMINAL.lower() and not fileobj.isatty()): + msg = strip_ansi(msg) + fileobj.write(msg) def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: -- cgit v1.2.1 From 00388938d2c02922660e63ae163373b25789fafa Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 27 Jun 2019 22:00:10 -0400 Subject: Added fg_lookup() and bg_lookup() two-stage color lookup functions --- cmd2/ansi.py | 91 +++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 28 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index a856f031..f9e32166 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -21,14 +21,7 @@ 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, @@ -37,19 +30,20 @@ FG_COLORS = { 'lightmagenta': Fore.LIGHTMAGENTA_EX, 'lightcyan': Fore.LIGHTCYAN_EX, 'lightwhite': Fore.LIGHTWHITE_EX, - 'reset': Fore.RESET, + 'bright_black': Fore.LIGHTBLACK_EX, + 'bright_red': Fore.LIGHTRED_EX, + 'bright_green': Fore.LIGHTGREEN_EX, + 'bright_yellow': Fore.LIGHTYELLOW_EX, + 'bright_blue': Fore.LIGHTBLUE_EX, + 'bright_magenta': Fore.LIGHTMAGENTA_EX, + '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, @@ -58,8 +52,16 @@ BG_COLORS = { 'lightmagenta': Back.LIGHTMAGENTA_EX, 'lightcyan': Back.LIGHTCYAN_EX, 'lightwhite': Back.LIGHTWHITE_EX, - 'reset': Back.RESET, + 'bright_black': Back.LIGHTBLACK_EX, + 'bright_red': Back.LIGHTRED_EX, + 'bright_green': Back.LIGHTGREEN_EX, + 'bright_yellow': Back.LIGHTYELLOW_EX, + 'bright_blue': Back.LIGHTBLUE_EX, + 'bright_magenta': Back.LIGHTMAGENTA_EX, + 'bright_cyan': Back.LIGHTCYAN_EX, + 'bright_white': Back.LIGHTWHITE_EX, } +BG_RESET = BG_COLORS['reset'] # ANSI escape sequences not provided by colorama UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4) @@ -99,6 +101,44 @@ def ansi_aware_write(fileobj: IO, msg: str) -> None: fileobj.write(msg) +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 + """ + 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)) + 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 + """ + 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)) + return ansi_escape + + def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: """ Applies styles to text @@ -110,6 +150,7 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin :param underline: apply the underline style if True. Defaults to False. :return: the stylized string + :raises ValueError if fg or bg color does cannot be found """ # List of strings that add style additions = [] @@ -122,18 +163,12 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin # Process the style settings if fg: - try: - additions.append(FG_COLORS[fg.lower()]) - removals.append(FG_COLORS['reset']) - except KeyError: - raise ValueError('Color {} does not exist.'.format(fg)) + additions.append(fg_lookup(fg)) + removals.append(FG_RESET) if bg: - try: - additions.append(BG_COLORS[bg.lower()]) - removals.append(BG_COLORS['reset']) - except KeyError: - raise ValueError('Color {} does not exist.'.format(bg)) + additions.append(bg_lookup(bg)) + removals.append(BG_RESET) if bold: additions.append(Style.BRIGHT) -- cgit v1.2.1 From f91ccf2bb28531f9d1dee551314cb091fa57df74 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 27 Jun 2019 23:54:07 -0400 Subject: Simplified ansi color dictionaries and lookup methods Also: - Updated examples that use color to use cmd2.ansi instead of colorama - Updated tests that use color to use cmd2.ansi instead of colorama - plumbum_colorspy example shows how to override color lookup functions to use a different color library --- cmd2/ansi.py | 56 ++++++++++++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 32 deletions(-) (limited to 'cmd2/ansi.py') 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') -- cgit v1.2.1 From 06c9adea8f0a0136afc48e2784ce89d694c6dd37 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 28 Jun 2019 00:01:45 -0400 Subject: Moved RESET to end of color dictionaries and skip a test on Mac since it is unreliable on Azure DevOps CI --- cmd2/ansi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index e1a4671d..0e73ab57 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -29,7 +29,6 @@ FG_COLORS = { 'magenta': Fore.MAGENTA, 'cyan': Fore.CYAN, 'white': Fore.WHITE, - 'reset': Fore.RESET, 'bright_black': Fore.LIGHTBLACK_EX, 'bright_red': Fore.LIGHTRED_EX, 'bright_green': Fore.LIGHTGREEN_EX, @@ -38,6 +37,7 @@ FG_COLORS = { 'bright_magenta': Fore.LIGHTMAGENTA_EX, 'bright_cyan': Fore.LIGHTCYAN_EX, 'bright_white': Fore.LIGHTWHITE_EX, + 'reset': Fore.RESET, } # Background color presets @@ -50,7 +50,6 @@ BG_COLORS = { 'magenta': Back.MAGENTA, 'cyan': Back.CYAN, 'white': Back.WHITE, - 'reset': Back.RESET, 'bright_black': Back.LIGHTBLACK_EX, 'bright_red': Back.LIGHTRED_EX, 'bright_green': Back.LIGHTGREEN_EX, @@ -59,6 +58,7 @@ BG_COLORS = { 'bright_magenta': Back.LIGHTMAGENTA_EX, 'bright_cyan': Back.LIGHTCYAN_EX, 'bright_white': Back.LIGHTWHITE_EX, + 'reset': Back.RESET, } FG_RESET = FG_COLORS['reset'] -- cgit v1.2.1 From 62327923a0f8db318dd6ff951b9d98a6b04bdbae Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 28 Jun 2019 00:50:40 -0400 Subject: Updated Sphinx documentation and README.md --- cmd2/ansi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 0e73ab57..081e28b7 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -132,17 +132,17 @@ def bg_lookup(bg_name: str) -> str: def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underline: bool = False) -> str: - """ - Applies styles to text + """Styles a string with ANSI colors and/or styles and returns the new string. + + The styling is self contained which means that at the end of the string reset code(s) are issued + to undo whatever styling was done at the beginning. :param text: Any object compatible with str.format() :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. - :return: the stylized string - :raises ValueError if fg or bg color does cannot be found """ # List of strings that add style additions = [] -- cgit v1.2.1 From 8b49dea5166b11fed6c6206e95119ae00dc8ea20 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 28 Jun 2019 01:15:56 -0400 Subject: Minor fix to docstring of ansi.style() --- cmd2/ansi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'cmd2/ansi.py') diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 081e28b7..035991bb 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -138,8 +138,8 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin to undo whatever styling was done at the beginning. :param text: Any object compatible with str.format() - :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 fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name. Defaults to no color. + :param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name. 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. :return: the stylized string -- cgit v1.2.1