summaryrefslogtreecommitdiff
path: root/lib/spack/llnl/util/tty/colify.py
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2014-03-12 22:24:47 -0400
committerTodd Gamblin <tgamblin@llnl.gov>2014-03-14 16:21:15 -0700
commit9d01df9e8aca7e1ddd88d9c9b1f42ca048878635 (patch)
treeb406cd2a6960fa8af784d2d44adf9234754db6d4 /lib/spack/llnl/util/tty/colify.py
parent03ee31e0e8cf7c58eb4aafc6912e698c0a23427e (diff)
downloadspack-9d01df9e8aca7e1ddd88d9c9b1f42ca048878635.tar.gz
spack-9d01df9e8aca7e1ddd88d9c9b1f42ca048878635.tar.bz2
spack-9d01df9e8aca7e1ddd88d9c9b1f42ca048878635.tar.xz
spack-9d01df9e8aca7e1ddd88d9c9b1f42ca048878635.zip
Moving utilities to a common LLNL package.
Diffstat (limited to 'lib/spack/llnl/util/tty/colify.py')
-rw-r--r--lib/spack/llnl/util/tty/colify.py193
1 files changed, 193 insertions, 0 deletions
diff --git a/lib/spack/llnl/util/tty/colify.py b/lib/spack/llnl/util/tty/colify.py
new file mode 100644
index 0000000000..1f66416e69
--- /dev/null
+++ b/lib/spack/llnl/util/tty/colify.py
@@ -0,0 +1,193 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# 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.
+#
+# 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.
+#
+# 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
+##############################################################################
+# colify
+# By Todd Gamblin, tgamblin@llnl.gov
+#
+# Takes a list of items as input and finds a good columnization of them,
+# similar to how gnu ls does. You can pipe output to this script and
+# get a tight display for it. This supports both uniform-width and
+# variable-width (tighter) columns.
+#
+# Run colify -h for more information.
+#
+import os
+import sys
+import fcntl
+import termios
+import struct
+
+def get_terminal_size():
+ """Get the dimensions of the console."""
+ def ioctl_GWINSZ(fd):
+ try:
+ cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
+ except:
+ return
+ return cr
+ cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
+ if not cr:
+ try:
+ fd = os.open(os.ctermid(), os.O_RDONLY)
+ cr = ioctl_GWINSZ(fd)
+ os.close(fd)
+ except:
+ pass
+ if not cr:
+ cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80))
+
+ return int(cr[1]), int(cr[0])
+
+
+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)
+
+
+def config_variable_cols(elts, console_cols, padding):
+ # Get a bound on the most columns we could possibly have.
+ lengths = [len(elt) for elt in elts]
+ max_cols = max(1, console_cols / (min(lengths) + padding))
+ max_cols = min(len(elts), max_cols)
+
+ configs = [ColumnConfig(c) for c in xrange(1, max_cols+1)]
+ for elt, length in enumerate(lengths):
+ for i, conf in enumerate(configs):
+ if conf.valid:
+ col = elt / ((len(elts) + i) / (i + 1))
+ padded = length
+ if col < i:
+ padded += padding
+
+ if conf.widths[col] < padded:
+ conf.line_length += padded - conf.widths[col]
+ conf.widths[col] = padded
+ conf.valid = (conf.line_length < console_cols)
+
+ 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
+
+
+def config_uniform_cols(elts, console_cols, padding):
+ max_len = max(len(elt) for elt in elts) + padding
+ cols = max(1, console_cols / max_len)
+ cols = min(len(elts), cols)
+ config = ColumnConfig(cols)
+ config.widths = [max_len] * cols
+ return config
+
+
+def isatty(ostream):
+ force = os.environ.get('COLIFY_TTY', 'false').lower() != 'false'
+ return force or ostream.isatty()
+
+
+def colify(elts, **options):
+ # Get keyword arguments or set defaults
+ output = options.get("output", sys.stdout)
+ indent = options.get("indent", 0)
+ padding = options.get("padding", 2)
+
+ # elts needs to be an array of strings so we can count the elements
+ elts = [str(elt) for elt in elts]
+ if not elts:
+ return
+
+ if not isatty(output):
+ for elt in elts:
+ output.write("%s\n" % elt)
+ return
+
+ console_cols = options.get("cols", None)
+ if not console_cols:
+ console_cols, console_rows = get_terminal_size()
+ elif type(console_cols) != int:
+ raise ValueError("Number of columns must be an int")
+ console_cols = max(1, console_cols - indent)
+
+ method = options.get("method", "variable")
+ if method == "variable":
+ config = config_variable_cols(elts, console_cols, padding)
+ elif method == "uniform":
+ config = config_uniform_cols(elts, console_cols, padding)
+ 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
+ output.write(formats[col] % elts[elt])
+
+ output.write("\n")
+ row += 1
+ if row == rows_last_col:
+ cols -= 1
+
+
+if __name__ == "__main__":
+ import optparse
+
+ cols, rows = get_terminal_size()
+ parser = optparse.OptionParser()
+ parser.add_option("-u", "--uniform", action="store_true", default=False,
+ help="Use uniformly sized columns instead of variable-size.")
+ parser.add_option("-p", "--padding", metavar="PADDING", action="store",
+ type=int, default=2, help="Spaces to add between columns. Default is 2.")
+ parser.add_option("-i", "--indent", metavar="SPACES", action="store",
+ type=int, default=0, help="Indent the output by SPACES. Default is 0.")
+ parser.add_option("-w", "--width", metavar="COLS", action="store",
+ type=int, default=cols, help="Indent the output by SPACES. Default is 0.")
+ options, args = parser.parse_args()
+
+ method = "variable"
+ if options.uniform:
+ method = "uniform"
+
+ if sys.stdin.isatty():
+ parser.print_help()
+ sys.exit(1)
+ else:
+ colify([line.strip() for line in sys.stdin], method=method, **options.__dict__)