SHOW:
|
|
- or go back to the newest paste.
| 1 | from __future__ import division | |
| 2 | ||
| 3 | from collections import namedtuple | |
| 4 | from itertools import count | |
| 5 | from math import ceil | |
| 6 | ||
| 7 | try: | |
| 8 | range = xrange | |
| 9 | except NameError: | |
| 10 | pass | |
| 11 | ||
| 12 | def columnize(items, spacing=2, max_line_width=80): | |
| 13 | line_properties = get_line_properties(items, spacing, max_line_width) | |
| 14 | formatter = Formatter(line_properties, items) | |
| 15 | return '\n'.join(formatter.line_strings) | |
| 16 | ||
| 17 | def get_line_properties(items, spacing, max_line_width): | |
| 18 | for value in (spacing, max_line_width): | |
| 19 | if not isinstance(value, int) or value < 0: | |
| 20 | msg = 'spacing and max_line_width must be non-negative integers' | |
| 21 | raise ValueError(msg) | |
| 22 | item_widths = [len(item) for item in items] | |
| 23 | if max(item_widths) >= max_line_width: | |
| 24 | return LineProperties([max_line_width], spacing) | |
| 25 | num_items = len(item_widths) | |
| 26 | for chunk_size in count(1): | |
| 27 | column_widths = [max(item_widths[i : i + chunk_size]) | |
| 28 | for i in range(0, num_items, chunk_size)] | |
| 29 | line_width = sum(column_widths) + (len(column_widths) - 1) * spacing | |
| 30 | if line_width <= max_line_width: | |
| 31 | break | |
| 32 | return LineProperties(column_widths, spacing) | |
| 33 | ||
| 34 | LineProperties = namedtuple('LineProperties', 'column_widths, spacing')
| |
| 35 | ||
| 36 | class Formatter(object): | |
| 37 | def __init__(self, line_properties, items=[]): | |
| 38 | self.line_properties = line_properties | |
| 39 | self.items = items | |
| 40 | ||
| 41 | @property | |
| 42 | def line_strings(self): | |
| 43 | num_lines = int(ceil(len(self.items) / self.num_columns)) | |
| 44 | template = self.get_line_template() | |
| 45 | for i in range(num_lines): | |
| 46 | line_items = self.items[i::num_lines] | |
| 47 | try: | |
| 48 | yield template % tuple(line_items) | |
| 49 | except TypeError: | |
| 50 | # raised if last line contains too few items for line template | |
| 51 | # -> re-generate template for real number of items | |
| 52 | template = self.get_line_template(len(line_items)) | |
| 53 | yield template % tuple(line_items) | |
| 54 | ||
| 55 | @property | |
| 56 | def num_columns(self): | |
| 57 | return len(self.line_properties.column_widths) | |
| 58 | ||
| 59 | def get_line_template(self, max_items=-1): | |
| 60 | if max_items < 0 or max_items > self.num_columns: | |
| 61 | max_items = self.num_columns | |
| 62 | specs = ('%%-%ds' % width
| |
| 63 | for width in self.line_properties.column_widths[:max_items]) | |
| 64 | return (self.line_properties.spacing * ' ').join(specs) |