Adding expression syntax for console colors.
This commit is contained in:
parent
5c761aafab
commit
7a67cc1675
1 changed files with 162 additions and 0 deletions
162
lib/spack/spack/color.py
Normal file
162
lib/spack/spack/color.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
This file implements an expression syntax, similar to printf, for adding
|
||||
ANSI colors to text.
|
||||
|
||||
See colorize(), cwrite(), and cprint() for routines that can generate
|
||||
colored output.
|
||||
|
||||
colorize will take a string and replace all color expressions with
|
||||
ANSI control codes. If the isatty keyword arg is set to False, then
|
||||
the color expressions will be converted to null strings, and the
|
||||
returned string will have no color.
|
||||
|
||||
cwrite and cprint are equivalent to write() and print() calls in
|
||||
python, but they colorize their output. If the stream argument is
|
||||
not supplied, they write to sys.stdout.
|
||||
|
||||
Here are some example color expressions:
|
||||
|
||||
@r Turn on red coloring
|
||||
@R Turn on bright red coloring
|
||||
@*b Turn on bold, blue text
|
||||
@_B Turn on bright blue text with an underline
|
||||
@. Revert to plain formatting
|
||||
@*g{green} Print out 'green' in bold, green text, then reset to plain.
|
||||
@*ggreen@. Print out 'green' in bold, green text, then reset to plain.
|
||||
|
||||
The syntax consists of:
|
||||
|
||||
color-expr = '@' [style] color-code '{' text '}' | '@.' | '@@'
|
||||
style = '*' | '_'
|
||||
color-code = [krgybmcwKRGYBMCW]
|
||||
text = .*
|
||||
|
||||
'@' indicates the start of a color expression. It can be followed
|
||||
by an optional * or _ that indicates whether the font should be bold or
|
||||
underlined. If * or _ is not provided, the text will be plain. Then
|
||||
an optional color code is supplied. This can be [krgybmcw] or [KRGYBMCW],
|
||||
where the letters map to black(k), red(r), green(g), yellow(y), blue(b),
|
||||
magenta(m), cyan(c), and white(w). Lowercase letters denote normal ANSI
|
||||
colors and capital letters denote bright ANSI colors.
|
||||
|
||||
Finally, the color expression can be followed by text enclosed in {}. If
|
||||
braces are present, only the text in braces is colored. If the braces are
|
||||
NOT present, then just the control codes to enable the color will be output.
|
||||
The console can be reset later to plain text with '@.'.
|
||||
|
||||
To output an @, use '@@'. To output a } inside braces, use '}}'.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import spack.error
|
||||
|
||||
class ColorParseError(spack.error.SpackError):
|
||||
"""Raised when a color format fails to parse."""
|
||||
def __init__(self, message):
|
||||
super(ColorParseError, self).__init__(message)
|
||||
|
||||
# Text styles for ansi codes
|
||||
styles = {'*' : '1;%s', # bold
|
||||
'_' : '4:%s', # underline
|
||||
None : '0;%s' } # plain
|
||||
|
||||
# Dim and bright ansi colors
|
||||
colors = {'k' : 30, 'K' : 90, # black
|
||||
'r' : 31, 'R' : 91, # red
|
||||
'g' : 32, 'G' : 92, # green
|
||||
'y' : 33, 'Y' : 93, # yellow
|
||||
'b' : 34, 'B' : 94, # blue
|
||||
'm' : 35, 'M' : 95, # magenta
|
||||
'c' : 36, 'C' : 96, # cyan
|
||||
'w' : 37, 'W' : 97 } # white
|
||||
|
||||
# Regex to be used for color formatting
|
||||
color_re = r'@(?:@|\.|([*_])?([a-zA-Z])?(?:{((?:[^}]|}})*)})?)'
|
||||
|
||||
|
||||
class match_to_ansi(object):
|
||||
def __init__(self, color=True):
|
||||
self.color = color
|
||||
|
||||
def escape(self, s):
|
||||
"""Returns a TTY escape sequence for a color"""
|
||||
if self.color:
|
||||
return "\033[%sm" % s
|
||||
else:
|
||||
return ''
|
||||
|
||||
def __call__(self, match):
|
||||
"""Convert a match object generated by color_re into an ansi color code
|
||||
This can be used as a handler in re.sub.
|
||||
"""
|
||||
style, color, text = match.groups()
|
||||
m = match.group(0)
|
||||
|
||||
if m == '@@':
|
||||
return '@'
|
||||
elif m == '@.':
|
||||
return self.escape(0)
|
||||
elif m == '@' or (style and not color):
|
||||
raise ColorParseError("Incomplete color format: '%s'" % m)
|
||||
elif color not in colors:
|
||||
raise ColorParseError("invalid color specifier: '%s'" % color)
|
||||
|
||||
colored_text = ''
|
||||
if text:
|
||||
colored_text = text + self.escape(0)
|
||||
|
||||
if style == '*':
|
||||
color_code = self.escape(styles[style] % colors[color])
|
||||
elif style == '_':
|
||||
color_code = self.escape(styles[style] % colors[color])
|
||||
else:
|
||||
color_code = self.escape(styles[style] % colors[color])
|
||||
|
||||
return color_code + colored_text
|
||||
|
||||
|
||||
def colorize(string, **kwargs):
|
||||
"""Take a string and replace all color expressions with ANSI control
|
||||
codes. Return the resulting string.
|
||||
If color=False is supplied, output will be plain text without
|
||||
control codes, for output to non-console devices.
|
||||
"""
|
||||
color = kwargs.get('color', True)
|
||||
return re.sub(color_re, match_to_ansi(color), string)
|
||||
|
||||
|
||||
def cwrite(string, stream=sys.stdout, color=None):
|
||||
"""Replace all color expressions in string with ANSI control
|
||||
codes and write the result to the stream. If color is
|
||||
False, this will write plain text with o color. If True,
|
||||
then it will always write colored output. If not supplied,
|
||||
then it will be set based on stream.isatty().
|
||||
"""
|
||||
if color == None:
|
||||
color = stream.isatty()
|
||||
stream.write(colorize(string, color=color))
|
||||
|
||||
|
||||
def cprint(string, stream=sys.stdout, color=None):
|
||||
"""Same as cwrite, but writes a trailing newline to the stream."""
|
||||
cwrite(string + "\n", stream, color)
|
||||
|
||||
|
||||
class ColorStream(object):
|
||||
def __init__(self, stream, color=None):
|
||||
self.__class__ = type(stream.__class__.__name__,
|
||||
(self.__class__, stream.__class__), {})
|
||||
self.__dict__ = stream.__dict__
|
||||
self.color = color
|
||||
self.stream = stream
|
||||
|
||||
def write(self, string, **kwargs):
|
||||
if kwargs.get('raw', False):
|
||||
super(ColorStream, self).write(string)
|
||||
else:
|
||||
cwrite(string, self.stream, self.color)
|
||||
|
||||
def writelines(self, sequence, **kwargs):
|
||||
raw = kwargs.get('raw', False)
|
||||
for string in sequence:
|
||||
self.write(string, self.color, raw=raw)
|
Loading…
Reference in a new issue