View difference between Paste ID: 1zw9eFRm and bw9ZHxtN
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)