mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 09:23:56 +00:00
add full support for ANSI SGR escape sequences
This is for adding color to the text output salt emits in various contexts including support for extended colors (256 and 256^3) and a few other common text modifiers like bold, italic, underline, etc. The code is put in salt/textformat.py (rather than salt/utils/textformat.py) in order to allow for logging to import TextFormat without loading all of salt/utils/__init__.py and causing logging failures.
This commit is contained in:
parent
be85f50178
commit
4723853128
190
salt/textformat.py
Normal file
190
salt/textformat.py
Normal file
@ -0,0 +1,190 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
ANSI escape code utilities, see
|
||||
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
|
||||
'''
|
||||
|
||||
graph_prefix = '\x1b['
|
||||
graph_suffix = 'm'
|
||||
codes = {
|
||||
'reset': '0',
|
||||
|
||||
'bold': '1',
|
||||
'faint': '2',
|
||||
'italic': '3',
|
||||
'underline': '4',
|
||||
'blink': '5',
|
||||
'slow_blink': '5',
|
||||
'fast_blink': '6',
|
||||
'inverse': '7',
|
||||
'conceal': '8',
|
||||
'strike': '9',
|
||||
|
||||
'primary_font': '10',
|
||||
'reset_font': '10',
|
||||
'font_0': '10',
|
||||
'font_1': '11',
|
||||
'font_2': '12',
|
||||
'font_3': '13',
|
||||
'font_4': '14',
|
||||
'font_5': '15',
|
||||
'font_6': '16',
|
||||
'font_7': '17',
|
||||
'font_8': '18',
|
||||
'font_9': '19',
|
||||
'fraktur': '20',
|
||||
|
||||
'double_underline': '21',
|
||||
'end_bold': '21',
|
||||
'normal_intensity': '22',
|
||||
'end_italic': '23',
|
||||
'end_fraktur': '23',
|
||||
'end_underline': '24', # single or double
|
||||
'end_blink': '25',
|
||||
'end_inverse': '27',
|
||||
'end_conceal': '28',
|
||||
'end_strike': '29',
|
||||
|
||||
'black': '30',
|
||||
'red': '31',
|
||||
'green': '32',
|
||||
'yellow': '33',
|
||||
'blue': '34',
|
||||
'magenta': '35',
|
||||
'cyan': '36',
|
||||
'white': '37',
|
||||
'extended': '38',
|
||||
'default': '39',
|
||||
|
||||
'fg_black': '30',
|
||||
'fg_red': '31',
|
||||
'fg_green': '32',
|
||||
'fg_yellow': '33',
|
||||
'fg_blue': '34',
|
||||
'fg_magenta': '35',
|
||||
'fg_cyan': '36',
|
||||
'fg_white': '37',
|
||||
'fg_extended': '38',
|
||||
'fg_default': '39',
|
||||
|
||||
'bg_black': '40',
|
||||
'bg_red': '41',
|
||||
'bg_green': '42',
|
||||
'bg_yellow': '44',
|
||||
'bg_blue': '44',
|
||||
'bg_magenta': '45',
|
||||
'bg_cyan': '46',
|
||||
'bg_white': '47',
|
||||
'bg_extended': '48',
|
||||
'bg_default': '49',
|
||||
|
||||
'frame': '51',
|
||||
'encircle': '52',
|
||||
'overline': '53',
|
||||
'end_frame': '54',
|
||||
'end_encircle': '54',
|
||||
'end_overline': '55',
|
||||
|
||||
'ideogram_underline': '60',
|
||||
'right_line': '60',
|
||||
'ideogram_double_underline': '61',
|
||||
'right_double_line': '61',
|
||||
'ideogram_overline': '62',
|
||||
'left_line': '62',
|
||||
'ideogram_double_overline': '63',
|
||||
'left_double_line': '63',
|
||||
'ideogram_stress': '64',
|
||||
'reset_ideogram': '65'
|
||||
}
|
||||
|
||||
|
||||
class TextFormat(object):
|
||||
'''
|
||||
ANSI Select Graphic Rendition (SGR) code escape sequence.
|
||||
'''
|
||||
|
||||
def __init__(self, *attrs, **kwargs):
|
||||
'''
|
||||
:param attrs: are the attribute names of any format codes in `codes`
|
||||
|
||||
:param kwargs: may contain
|
||||
|
||||
`x`, an integer in the range [0-255] that selects the corresponding
|
||||
color from the extended ANSI 256 color space for foreground text
|
||||
|
||||
`rgb`, an iterable of 3 integers in the range [0-255] that select the
|
||||
corresponding colors from the extended ANSI 256^3 color space for
|
||||
foreground text
|
||||
|
||||
`bg_x`, an integer in the range [0-255] that selects the corresponding
|
||||
color from the extended ANSI 256 color space for background text
|
||||
|
||||
`bg_rgb`, an iterable of 3 integers in the range [0-255] that select
|
||||
the corresponding colors from the extended ANSI 256^3 color space for
|
||||
background text
|
||||
|
||||
`reset`, prepend reset SGR code to sequence (default `True`)
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
red_underlined = TextFormat('red', 'underline')
|
||||
|
||||
nuanced_text = TextFormat(x=29, bg_x=71)
|
||||
|
||||
magenta_on_green = TextFormat('magenta', 'bg_green')
|
||||
print(
|
||||
'{0}Can you read this?{1}'
|
||||
).format(magenta_on_green, TextFormat('reset'))
|
||||
'''
|
||||
self.codes = [codes[attr.lower()] for attr in attrs if isinstance(attr, str)]
|
||||
|
||||
if kwargs.get('reset', True):
|
||||
self.codes[:0] = [codes['reset']]
|
||||
|
||||
def qualify_int(i):
|
||||
if isinstance(i, int):
|
||||
return i%256 # set i to unit element of its equivalence class
|
||||
def qualify_triple_int(t):
|
||||
if isinstance(t, (list, tuple)) and len(t) == 3:
|
||||
return qualify_int(i)
|
||||
|
||||
if kwargs.get('x', None) is not None:
|
||||
self.codes.extend((codes['extended'], '5', qualify_int(kwargs['x'])))
|
||||
elif kwargs.get('rgb', None) is not None:
|
||||
self.codes.extend((codes['extended'], '2'))
|
||||
self.codes.extend(*qualify_triple_int(kwargs['rgb']))
|
||||
|
||||
if kwargs.get('bg_x', None) is not None:
|
||||
self.codes.extend((codes['extended'], '5', qualify_int(kwargs['bg_x'])))
|
||||
elif kwargs.get('bg_rgb', None) is not None:
|
||||
self.codes.extend((codes['extended'], '2'))
|
||||
self.codes.extend(*qualify_triple_int(kwargs['bg_rgb']))
|
||||
|
||||
self.sequence = '{p}{c}{s}'.format(
|
||||
p=graph_prefix,
|
||||
c=';'.join(self.codes),
|
||||
s=graph_suffix)
|
||||
|
||||
|
||||
def __call__(self, text, reset=True):
|
||||
'''
|
||||
Format :param text: by prefixing `self.sequence` and suffixing the
|
||||
reset sequence if :param reset: is `True`.
|
||||
|
||||
Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
green_blink_text = TextFormat('blink', 'green')
|
||||
'The answer is: {0}'.format(green_blink_text(42))
|
||||
'''
|
||||
end = TextFormat('reset') if reset else ''
|
||||
return '{s}{t}{e}'.format(s=self.sequence, t=text, e=end)
|
||||
|
||||
def __str__(self):
|
||||
return self.sequence
|
||||
|
||||
def __repr__(self):
|
||||
return self.sequence
|
@ -98,6 +98,7 @@ import salt.defaults.exitcodes
|
||||
import salt.log
|
||||
import salt.version
|
||||
from salt.utils.decorators import memoize as real_memoize
|
||||
from salt.textformat import TextFormat
|
||||
from salt.exceptions import (
|
||||
CommandExecutionError, SaltClientError,
|
||||
CommandNotFoundError, SaltSystemExit,
|
||||
@ -127,6 +128,7 @@ DEFAULT_COLOR = '\033[00m'
|
||||
RED_BOLD = '\033[01;31m'
|
||||
ENDC = '\033[0m'
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
_empty = object()
|
||||
|
||||
@ -154,29 +156,36 @@ def is_empty(filename):
|
||||
|
||||
def get_colors(use=True):
|
||||
'''
|
||||
Return the colors as an easy to use dict, pass False to return the colors
|
||||
as empty strings so that they will not be applied
|
||||
Return the colors as an easy to use dict. Pass `False` to deactivate all
|
||||
colors by setting them to empty strings. Pass a string containing only the
|
||||
name of a single color to be used in place of all colors. Examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
colors = get_colors() # enable all colors
|
||||
no_colors = get_colors(False) # disable all colors
|
||||
red_colors = get_colors('RED') # set all colors to red
|
||||
'''
|
||||
colors = {
|
||||
'BLACK': '\033[0;30m',
|
||||
'DARK_GRAY': '\033[1;30m',
|
||||
'LIGHT_GRAY': '\033[0;37m',
|
||||
'BLUE': '\033[0;34m',
|
||||
'LIGHT_BLUE': '\033[1;34m',
|
||||
'GREEN': '\033[0;32m',
|
||||
'LIGHT_GREEN': '\033[1;32m',
|
||||
'CYAN': '\033[0;36m',
|
||||
'LIGHT_CYAN': '\033[1;36m',
|
||||
'RED': '\033[0;31m',
|
||||
'LIGHT_RED': '\033[1;31m',
|
||||
'PURPLE': '\033[0;35m',
|
||||
'LIGHT_PURPLE': '\033[1;35m',
|
||||
'BROWN': '\033[0;33m',
|
||||
'YELLOW': '\033[1;33m',
|
||||
'WHITE': '\033[1;37m',
|
||||
'DEFAULT_COLOR': '\033[00m',
|
||||
'RED_BOLD': '\033[01;31m',
|
||||
'ENDC': '\033[0m',
|
||||
'BLACK': TextFormat('black'),
|
||||
'DARK_GRAY': TextFormat('bold', 'black'),
|
||||
'LIGHT_GRAY': TextFormat('white'),
|
||||
'BLUE': TextFormat('blue'),
|
||||
'LIGHT_BLUE': TextFormat('bold', 'blue'),
|
||||
'GREEN': TextFormat('green'),
|
||||
'LIGHT_GREEN': TextFormat('bold', 'green'),
|
||||
'CYAN': TextFormat('cyan'),
|
||||
'LIGHT_CYAN': TextFormat('bold', 'cyan'),
|
||||
'RED': TextFormat('red'),
|
||||
'LIGHT_RED': TextFormat('bold', 'red'),
|
||||
'RED_BOLD': TextFormat('bold', 'red'),
|
||||
'PURPLE': TextFormat('magenta'),
|
||||
'LIGHT_PURPLE': TextFormat('bold', 'magenta'),
|
||||
'BROWN': TextFormat('yellow'),
|
||||
'YELLOW': TextFormat('bold', 'yellow'),
|
||||
'WHITE': TextFormat('bold', 'white'),
|
||||
'DEFAULT_COLOR': TextFormat('default'),
|
||||
'ENDC': TextFormat('reset'),
|
||||
}
|
||||
|
||||
if not use:
|
||||
@ -186,6 +195,9 @@ def get_colors(use=True):
|
||||
# Try to set all of the colors to the passed color
|
||||
if use in colors:
|
||||
for color in colors:
|
||||
# except for color reset
|
||||
if color == 'ENDC':
|
||||
continue
|
||||
colors[color] = colors[use]
|
||||
|
||||
return colors
|
||||
|
Loading…
Reference in New Issue
Block a user