From 11cffff943b04ce69692db973c48de29647c8087 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Tue, 2 Dec 2014 21:56:22 -0800 Subject: [PATCH] colify handles ansi color input directly; no more decorator. --- lib/spack/llnl/util/tty/__init__.py | 25 ++++++--------- lib/spack/llnl/util/tty/colify.py | 48 ++++++++++++++++------------- lib/spack/llnl/util/tty/color.py | 5 +++ lib/spack/spack/cmd/compiler.py | 10 ++++-- lib/spack/spack/cmd/find.py | 14 ++++----- 5 files changed, 55 insertions(+), 47 deletions(-) diff --git a/lib/spack/llnl/util/tty/__init__.py b/lib/spack/llnl/util/tty/__init__.py index 5eeab67d6f..aba9e61f4f 100644 --- a/lib/spack/llnl/util/tty/__init__.py +++ b/lib/spack/llnl/util/tty/__init__.py @@ -145,18 +145,16 @@ def get_yes_or_no(prompt, **kwargs): def hline(label=None, **kwargs): - """Draw an optionally colored or labeled horizontal line. + """Draw a labeled horizontal line. Options: - char Char to draw the line with. Default '-' - color Color of the label. Default is no color. max_width Maximum width of the line. Default is 64 chars. - - See tty.color for possible color formats. """ - char = kwargs.get('char', '-') - color = kwargs.get('color', '') - max_width = kwargs.get('max_width', 64) + char = kwargs.pop('char', '-') + max_width = kwargs.pop('max_width', 64) + if kwargs: + raise TypeError("'%s' is an invalid keyword argument for this function." + % next(kwargs.iterkeys())) rows, cols = terminal_size() if not cols: @@ -166,15 +164,12 @@ def hline(label=None, **kwargs): cols = min(max_width, cols) label = str(label) - prefix = char * 2 + " " + label + " " - suffix = (cols - len(prefix)) * char + prefix = char * 2 + " " + suffix = " " + (cols - len(prefix) - clen(label)) * char out = StringIO() - if color: - prefix = char * 2 + " " + color + cescape(label) + "@. " - cwrite(prefix, stream=out, color=True) - else: - out.write(prefix) + out.write(prefix) + out.write(label) out.write(suffix) print out.getvalue() diff --git a/lib/spack/llnl/util/tty/colify.py b/lib/spack/llnl/util/tty/colify.py index 5c5c6077ec..6b2909990c 100644 --- a/lib/spack/llnl/util/tty/colify.py +++ b/lib/spack/llnl/util/tty/colify.py @@ -33,6 +33,7 @@ from StringIO import StringIO from llnl.util.tty import terminal_size +from llnl.util.tty.color import clen class ColumnConfig: @@ -40,7 +41,8 @@ def __init__(self, cols): self.cols = cols self.line_length = 0 self.valid = True - self.widths = [0] * cols + self.widths = [0] * cols # does not include ansi colors + self.cwidths = [0] * cols # includes ansi colors def __repr__(self): attrs = [(a,getattr(self, a)) for a in dir(self) if not a.startswith("__")] @@ -62,7 +64,10 @@ def config_variable_cols(elts, console_width, padding, cols=0): raise ValueError("cols must be non-negative.") # Get a bound on the most columns we could possibly have. - lengths = [len(elt) for elt in elts] + # 'clen' ignores length of ansi color sequences. + lengths = [clen(e) for e in elts] + clengths = [len(e) for e in elts] + max_cols = max(1, console_width / (min(lengths) + padding)) max_cols = min(len(elts), max_cols) @@ -71,17 +76,16 @@ def config_variable_cols(elts, console_width, padding, cols=0): # Determine the most columns possible for the console width. configs = [ColumnConfig(c) for c in col_range] - for elt, length in enumerate(lengths): + for i, length in enumerate(lengths): for conf in configs: if conf.valid: - col = elt / ((len(elts) + conf.cols - 1) / conf.cols) - padded = length - if col < (conf.cols - 1): - padded += padding + col = i / ((len(elts) + conf.cols - 1) / conf.cols) + p = padding if col < (conf.cols - 1) else 0 - if conf.widths[col] < padded: - conf.line_length += padded - conf.widths[col] - conf.widths[col] = padded + if conf.widths[col] < (length + p): + conf.line_length += length + p - conf.widths[col] + conf.widths[col] = length + p + conf.cwidths[col] = clengths[i] + p conf.valid = (conf.line_length < console_width) try: @@ -105,12 +109,17 @@ def config_uniform_cols(elts, console_width, padding, cols=0): if cols < 0: raise ValueError("cols must be non-negative.") - max_len = max(len(elt) for elt in elts) + padding + # 'clen' ignores length of ansi color sequences. + max_len = max(clen(e) for e in elts) + padding + max_clen = max(len(e) for e in elts) + padding if cols == 0: cols = max(1, console_width / max_len) cols = min(len(elts), cols) + config = ColumnConfig(cols) config.widths = [max_len] * cols + config.cwidths = [max_clen] * cols + return config @@ -139,9 +148,8 @@ def colify(elts, **options): Variable-width columns are tighter, uniform columns are all the same width and fit less data on the screen. - decorator= Function to add decoration (such as color) after columns have - already been fitted. Useful for fitting based only on - positive-width characters. + len= Function to use for calculating string length. + Useful for ignoring ansi color. Default is 'len'. """ # Get keyword arguments or set defaults cols = options.pop("cols", 0) @@ -151,7 +159,6 @@ def colify(elts, **options): tty = options.pop('tty', None) method = options.pop("method", "variable") console_cols = options.pop("width", None) - decorator = options.pop("decorator", lambda x:x) if options: raise TypeError("'%s' is an invalid keyword argument for this function." @@ -162,13 +169,10 @@ def colify(elts, **options): if not elts: return (0, ()) + # Use only one column if not a tty. if not tty: if tty is False or not output.isatty(): - for elt in elts: - output.write("%s\n" % elt) - - maxlen = max(len(str(s)) for s in elts) - return (1, (maxlen,)) + cols = 1 # Specify the number of character columns to use. if not console_cols: @@ -186,7 +190,7 @@ def colify(elts, **options): raise ValueError("method must be one of: " + allowed_methods) cols = config.cols - formats = ["%%-%ds" % width for width in config.widths[:-1]] + formats = ["%%-%ds" % width for width in config.cwidths[:-1]] formats.append("%s") # last column has no trailing space rows = (len(elts) + cols - 1) / cols @@ -196,7 +200,7 @@ def colify(elts, **options): output.write(" " * indent) for col in xrange(cols): elt = col * rows + row - output.write(decorator(formats[col] % elts[elt])) + output.write(formats[col] % elts[elt]) output.write("\n") row += 1 diff --git a/lib/spack/llnl/util/tty/color.py b/lib/spack/llnl/util/tty/color.py index 14974a1014..598e9d44f5 100644 --- a/lib/spack/llnl/util/tty/color.py +++ b/lib/spack/llnl/util/tty/color.py @@ -149,6 +149,11 @@ def colorize(string, **kwargs): return re.sub(color_re, match_to_ansi(color), string) +def clen(string): + """Return the length of a string, excluding ansi color sequences.""" + return len(re.sub(r'\033[^m]*m', '', 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 diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py index 5c46a3536d..e37f44b3b7 100644 --- a/lib/spack/spack/cmd/compiler.py +++ b/lib/spack/spack/cmd/compiler.py @@ -25,6 +25,7 @@ from external import argparse import llnl.util.tty as tty +from llnl.util.tty.color import colorize from llnl.util.tty.colify import colify from llnl.util.lang import index_by @@ -96,9 +97,12 @@ def compiler_info(args): def compiler_list(args): tty.msg("Available compilers") index = index_by(spack.compilers.all_compilers(), 'name') - for name, compilers in index.items(): - tty.hline(name, char='-', color=spack.spec.compiler_color) - colify(reversed(sorted(compilers)), indent=4) + for i, (name, compilers) in enumerate(index.items()): + if i >= 1: print + + cname = "%s{%s}" % (spack.spec.compiler_color, name) + tty.hline(colorize(cname), char='-') + colify(reversed(sorted(compilers))) def compiler(parser, args): diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py index c0a3162429..b6efc980b6 100644 --- a/lib/spack/spack/cmd/find.py +++ b/lib/spack/spack/cmd/find.py @@ -79,13 +79,16 @@ def find(parser, args): # Traverse the index and print out each package for i, (architecture, compiler) in enumerate(sorted(index)): if i > 0: print - tty.hline("%s / %s" % (compiler, architecture), char='-') - specs = index[(architecture, compiler)] + header = "%s{%s} / %s{%s}" % ( + spack.spec.architecture_color, architecture, + spack.spec.compiler_color, compiler) + tty.hline(colorize(header), char='-') + + specs = index[(architecture,compiler)] specs.sort() abbreviated = [s.format('$_$@$+$#', color=True) for s in specs] - if args.paths: # Print one spec per line along with prefix path width = max(len(s) for s in abbreviated) @@ -99,7 +102,4 @@ def find(parser, args): for spec in specs: print spec.tree(indent=4, format='$_$@$+', color=True), else: - max_len = max([len(s.name) for s in specs]) - max_len += 4 - - colify((s.format('$-_$@$+$#') for s in specs), decorator=spack.spec.colorize_spec) + colify(s.format('$-_$@$+$#', color=True) for s in specs)