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) |