2014-01-08 17:21:02 +08:00
|
|
|
##############################################################################
|
|
|
|
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
|
|
|
|
# Produced at the Lawrence Livermore National Laboratory.
|
2014-01-13 01:19:18 +08:00
|
|
|
#
|
2014-01-08 17:21:02 +08:00
|
|
|
# This file is part of Spack.
|
|
|
|
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
|
|
|
# LLNL-CODE-647188
|
2014-01-13 01:19:18 +08:00
|
|
|
#
|
2014-01-08 17:21:02 +08:00
|
|
|
# For details, see https://scalability-llnl.github.io/spack
|
|
|
|
# Please also see the LICENSE file for our notice and the LGPL.
|
2014-01-13 01:19:18 +08:00
|
|
|
#
|
2014-01-08 17:21:02 +08:00
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License (as published by
|
|
|
|
# the Free Software Foundation) version 2.1 dated February 1999.
|
2014-01-13 01:19:18 +08:00
|
|
|
#
|
2014-01-08 17:21:02 +08:00
|
|
|
# This program is distributed in the hope that it will be useful, but
|
|
|
|
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
|
|
|
|
# conditions of the GNU General Public License for more details.
|
2014-01-13 01:19:18 +08:00
|
|
|
#
|
2014-01-08 17:21:02 +08:00
|
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
|
|
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
##############################################################################
|
2014-11-24 07:55:57 +08:00
|
|
|
"""
|
|
|
|
Routines for printing columnar output. See colify() for more information.
|
|
|
|
"""
|
2013-02-20 09:08:38 +08:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import fcntl
|
|
|
|
import termios
|
|
|
|
import struct
|
2014-09-30 11:00:00 +08:00
|
|
|
from StringIO import StringIO
|
2013-02-19 15:46:04 +08:00
|
|
|
|
2014-04-15 03:53:23 +08:00
|
|
|
from llnl.util.tty import terminal_size
|
2013-02-19 15:46:04 +08:00
|
|
|
|
2014-09-30 11:00:00 +08:00
|
|
|
|
2013-02-19 15:46:04 +08:00
|
|
|
class ColumnConfig:
|
|
|
|
def __init__(self, cols):
|
|
|
|
self.cols = cols
|
|
|
|
self.line_length = 0
|
|
|
|
self.valid = True
|
|
|
|
self.widths = [0] * cols
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
attrs = [(a,getattr(self, a)) for a in dir(self) if not a.startswith("__")]
|
|
|
|
return "<Config: %s>" % ", ".join("%s: %r" % a for a in attrs)
|
|
|
|
|
|
|
|
|
2014-12-02 13:29:01 +08:00
|
|
|
def config_variable_cols(elts, console_width, padding, cols=0):
|
2014-11-24 07:55:57 +08:00
|
|
|
"""Variable-width column fitting algorithm.
|
|
|
|
|
|
|
|
This function determines the most columns that can fit in the
|
|
|
|
screen width. Unlike uniform fitting, where all columns take
|
|
|
|
the width of the longest element in the list, each column takes
|
|
|
|
the width of its own longest element. This packs elements more
|
|
|
|
efficiently on screen.
|
2014-12-02 13:29:01 +08:00
|
|
|
|
|
|
|
If cols is nonzero, force
|
2014-11-24 07:55:57 +08:00
|
|
|
"""
|
2014-12-02 13:29:01 +08:00
|
|
|
if cols < 0:
|
|
|
|
raise ValueError("cols must be non-negative.")
|
|
|
|
|
2013-02-19 15:46:04 +08:00
|
|
|
# Get a bound on the most columns we could possibly have.
|
|
|
|
lengths = [len(elt) for elt in elts]
|
2014-11-24 07:55:57 +08:00
|
|
|
max_cols = max(1, console_width / (min(lengths) + padding))
|
2013-02-19 15:46:04 +08:00
|
|
|
max_cols = min(len(elts), max_cols)
|
|
|
|
|
2014-12-02 13:29:01 +08:00
|
|
|
# Range of column counts to try. If forced, use the supplied value.
|
|
|
|
col_range = [cols] if cols else xrange(1, max_cols+1)
|
|
|
|
|
2014-11-24 07:55:57 +08:00
|
|
|
# Determine the most columns possible for the console width.
|
2014-12-02 13:29:01 +08:00
|
|
|
configs = [ColumnConfig(c) for c in col_range]
|
2013-02-19 15:46:04 +08:00
|
|
|
for elt, length in enumerate(lengths):
|
2014-12-02 13:29:01 +08:00
|
|
|
for conf in configs:
|
2013-02-19 15:46:04 +08:00
|
|
|
if conf.valid:
|
2014-12-02 13:29:01 +08:00
|
|
|
col = elt / ((len(elts) + conf.cols - 1) / conf.cols)
|
2013-02-19 15:46:04 +08:00
|
|
|
padded = length
|
2014-12-02 13:29:01 +08:00
|
|
|
if col < (conf.cols - 1):
|
2013-02-19 15:46:04 +08:00
|
|
|
padded += padding
|
|
|
|
|
|
|
|
if conf.widths[col] < padded:
|
|
|
|
conf.line_length += padded - conf.widths[col]
|
|
|
|
conf.widths[col] = padded
|
2014-11-24 07:55:57 +08:00
|
|
|
conf.valid = (conf.line_length < console_width)
|
2013-02-19 15:46:04 +08:00
|
|
|
|
|
|
|
try:
|
|
|
|
config = next(conf for conf in reversed(configs) if conf.valid)
|
|
|
|
except StopIteration:
|
|
|
|
# If nothing was valid the screen was too narrow -- just use 1 col.
|
|
|
|
config = configs[0]
|
|
|
|
|
|
|
|
config.widths = [w for w in config.widths if w != 0]
|
|
|
|
config.cols = len(config.widths)
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
2014-12-02 13:29:01 +08:00
|
|
|
def config_uniform_cols(elts, console_width, padding, cols=0):
|
2014-11-24 07:55:57 +08:00
|
|
|
"""Uniform-width column fitting algorithm.
|
|
|
|
|
|
|
|
Determines the longest element in the list, and determines how
|
|
|
|
many columns of that width will fit on screen. Returns a
|
|
|
|
corresponding column config.
|
|
|
|
"""
|
2014-12-02 13:29:01 +08:00
|
|
|
if cols < 0:
|
|
|
|
raise ValueError("cols must be non-negative.")
|
|
|
|
|
2013-02-19 15:46:04 +08:00
|
|
|
max_len = max(len(elt) for elt in elts) + padding
|
2014-12-02 13:29:01 +08:00
|
|
|
if cols == 0:
|
|
|
|
cols = max(1, console_width / max_len)
|
|
|
|
cols = min(len(elts), cols)
|
2013-02-19 15:46:04 +08:00
|
|
|
config = ColumnConfig(cols)
|
|
|
|
config.widths = [max_len] * cols
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
2014-11-24 07:55:57 +08:00
|
|
|
def colify(elts, **options):
|
|
|
|
"""Takes a list of elements as input and finds a good columnization
|
|
|
|
of them, similar to how gnu ls does. This supports both
|
|
|
|
uniform-width and variable-width (tighter) columns.
|
|
|
|
|
|
|
|
If elts is not a list of strings, each element is first conveted
|
|
|
|
using str().
|
2013-12-19 03:02:31 +08:00
|
|
|
|
2014-11-24 07:55:57 +08:00
|
|
|
Keyword arguments:
|
2013-12-19 03:02:31 +08:00
|
|
|
|
2014-11-24 07:55:57 +08:00
|
|
|
output=<stream> A file object to write to. Default is sys.stdout.
|
|
|
|
indent=<int> Optionally indent all columns by some number of spaces.
|
|
|
|
padding=<int> Spaces between columns. Default is 2.
|
2014-12-02 13:29:01 +08:00
|
|
|
width=<int> Width of the output. Default is 80 if tty is not detected.
|
|
|
|
|
|
|
|
cols=<int> Force number of columns. Default is to size to terminal,
|
|
|
|
or single-column if no tty
|
2014-11-24 07:55:57 +08:00
|
|
|
|
|
|
|
tty=<bool> Whether to attempt to write to a tty. Default is to
|
|
|
|
autodetect a tty. Set to False to force single-column output.
|
|
|
|
|
|
|
|
method=<string> Method to use to fit columns. Options are variable or uniform.
|
|
|
|
Variable-width columns are tighter, uniform columns are all the
|
|
|
|
same width and fit less data on the screen.
|
|
|
|
|
2014-12-02 13:29:01 +08:00
|
|
|
decorator=<func> Function to add decoration (such as color) after columns have
|
|
|
|
already been fitted. Useful for fitting based only on
|
|
|
|
positive-width characters.
|
2014-11-24 07:55:57 +08:00
|
|
|
"""
|
2013-02-19 15:46:04 +08:00
|
|
|
# Get keyword arguments or set defaults
|
2014-12-02 13:29:01 +08:00
|
|
|
cols = options.pop("cols", 0)
|
2014-11-24 07:55:57 +08:00
|
|
|
output = options.pop("output", sys.stdout)
|
|
|
|
indent = options.pop("indent", 0)
|
|
|
|
padding = options.pop("padding", 2)
|
|
|
|
tty = options.pop('tty', None)
|
|
|
|
method = options.pop("method", "variable")
|
|
|
|
console_cols = options.pop("width", None)
|
2014-12-02 13:29:01 +08:00
|
|
|
decorator = options.pop("decorator", lambda x:x)
|
2014-11-24 07:55:57 +08:00
|
|
|
|
|
|
|
if options:
|
|
|
|
raise TypeError("'%s' is an invalid keyword argument for this function."
|
|
|
|
% next(options.iterkeys()))
|
2013-02-19 15:46:04 +08:00
|
|
|
|
2013-10-08 08:57:27 +08:00
|
|
|
# elts needs to be an array of strings so we can count the elements
|
|
|
|
elts = [str(elt) for elt in elts]
|
|
|
|
if not elts:
|
2014-10-08 18:07:54 +08:00
|
|
|
return (0, ())
|
2013-02-19 15:46:04 +08:00
|
|
|
|
2014-09-30 11:00:00 +08:00
|
|
|
if not tty:
|
2014-11-24 07:55:57 +08:00
|
|
|
if tty is False or not output.isatty():
|
2014-09-30 11:00:00 +08:00
|
|
|
for elt in elts:
|
|
|
|
output.write("%s\n" % elt)
|
2014-10-08 18:07:54 +08:00
|
|
|
|
|
|
|
maxlen = max(len(str(s)) for s in elts)
|
|
|
|
return (1, (maxlen,))
|
2013-05-18 07:25:00 +08:00
|
|
|
|
2014-11-24 07:55:57 +08:00
|
|
|
# Specify the number of character columns to use.
|
2013-02-19 15:46:04 +08:00
|
|
|
if not console_cols:
|
2014-11-24 07:53:21 +08:00
|
|
|
console_rows, console_cols = terminal_size()
|
2013-02-19 15:46:04 +08:00
|
|
|
elif type(console_cols) != int:
|
|
|
|
raise ValueError("Number of columns must be an int")
|
|
|
|
console_cols = max(1, console_cols - indent)
|
|
|
|
|
2014-11-24 07:55:57 +08:00
|
|
|
# Choose a method. Variable-width colums vs uniform-width.
|
2013-02-19 15:46:04 +08:00
|
|
|
if method == "variable":
|
2014-12-02 13:29:01 +08:00
|
|
|
config = config_variable_cols(elts, console_cols, padding, cols)
|
2013-02-19 15:46:04 +08:00
|
|
|
elif method == "uniform":
|
2014-12-02 13:29:01 +08:00
|
|
|
config = config_uniform_cols(elts, console_cols, padding, cols)
|
2013-02-19 15:46:04 +08:00
|
|
|
else:
|
|
|
|
raise ValueError("method must be one of: " + allowed_methods)
|
|
|
|
|
|
|
|
cols = config.cols
|
|
|
|
formats = ["%%-%ds" % width for width in config.widths[:-1]]
|
|
|
|
formats.append("%s") # last column has no trailing space
|
|
|
|
|
|
|
|
rows = (len(elts) + cols - 1) / cols
|
|
|
|
rows_last_col = len(elts) % rows
|
|
|
|
|
|
|
|
for row in xrange(rows):
|
|
|
|
output.write(" " * indent)
|
|
|
|
for col in xrange(cols):
|
|
|
|
elt = col * rows + row
|
2014-12-02 13:29:01 +08:00
|
|
|
output.write(formats[col] % decorator(elts[elt]))
|
2013-02-19 15:46:04 +08:00
|
|
|
|
|
|
|
output.write("\n")
|
|
|
|
row += 1
|
|
|
|
if row == rows_last_col:
|
|
|
|
cols -= 1
|
|
|
|
|
2014-10-08 18:07:54 +08:00
|
|
|
return (config.cols, tuple(config.widths))
|
|
|
|
|
2013-02-20 09:08:38 +08:00
|
|
|
|
2014-09-30 11:00:00 +08:00
|
|
|
def colified(elts, **options):
|
|
|
|
"""Invokes the colify() function but returns the result as a string
|
|
|
|
instead of writing it to an output string."""
|
|
|
|
sio = StringIO()
|
|
|
|
options['output'] = sio
|
|
|
|
colify(elts, **options)
|
|
|
|
return sio.getvalue()
|