aboutsummaryrefslogtreecommitdiffstats
path: root/tabletext.py
blob: 34b1b2f47d82e433346abd5c349c1c5bdf45c1c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
import sys
from codecs import open
from itertools import izip_longest


def get_widths(table, formats, padding):
    columns = izip_longest(*table, fillvalue="")
    return [max(len(format_entry(entry, format_string, padding))
                for entry in column)
            for column, format_string in zip(columns, formats)]


def add_widths(formats, widths, padding):
    return [add_width(format_string, width - sum(padding))
            for format_string, width in zip(formats, widths)]


def top_rule(widths, corners, hor):
    return (corners[0] + corners[1].join(hor * width for width in widths)
            + corners[2])


def inner_rule(widths, corners, hor):
    return (corners[3] + corners[4].join(hor * width for width in widths)
            + corners[5]) + "\n"


def bottom_rule(widths, corners, hor):
    return (corners[6] + corners[7].join(hor * width for width in widths)
            + corners[8])


def format_entry(entry, format_string, padding):
    format_string = "{0:" + format_string + "}"
    return " " * padding[0] + format_string.format(entry) + " " * padding[1]


def format_row(row, formats, padding, ver):
    return (ver + ver.join(format_entry(entry, format_string, padding)
                           for entry, format_string in zip(row, formats))
            + ver) + "\n"


def add_width(format_string, width):
    regexp = r",?(\.\d+)?(b|c|d|e|E|f|F|g|G|n|o|s|x|X|%)?"
    match = re.match(regexp, format_string)
    begin, end = match.span()
    if end - begin > 0:
        return format_string[:begin] + str(width) + format_string[begin:]
    else:
        return format_string + str(width)


def print_table(table, formats=None, padding=(1, 1), corners="┌┬┐├┼┤└┴┘",
                header_corners="╒╤╕╞╪╡", header_hor="═", header_ver="│",
                header=False, hor="─", ver="│"):
    if not formats:
        formats = [""] * len(table[-1])
    elif type(formats) is unicode:
        formats = [formats] * len(table[-1])
    if len(corners) == 1:
        corners = corners * 9
    widths = get_widths(table, formats, padding)
    formats = add_widths(formats, widths, padding)
    if header:
        print top_rule(widths, header_corners, header_hor)
        print format_row(table[0], formats, padding, header_ver),
        print inner_rule(widths, header_corners, header_hor),
        table = table[1:]
    else:
        print top_rule(widths, corners, hor)
    print inner_rule(widths, corners, hor).join(format_row(row, formats,
                                                           padding, ver)
                                                for row in table),
    print bottom_rule(widths, corners, hor)


def main():
    parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter,
                            description="Render tab separated data as \
                            a table.")
    parser.add_argument("--hor", help="horizontal line character",
                        metavar="CHAR", default="─")
    parser.add_argument("--ver", help="vertical line character",
                        metavar="CHAR", default="│")
    parser.add_argument("--corners", help="corner characters", metavar="CHARS",
                        default="┌┬┐├┼┤└┴┘")
    parser.add_argument("--padding", help="left and right horizontal padding \
                        lenghts", nargs=2, type=int, metavar="<n>",
                        default=[1, 1])
    parser.add_argument("--format", help="format string for the table entries",
                        default="", dest="formats", metavar="FORMAT")
    parser.add_argument("--header", help="format first row as header",
                        action="store_true")
    parser.add_argument("--hhor", help="horizontal line character \
                        for the header row", metavar="CHAR", dest="header_hor",
                        default="═")
    parser.add_argument("--hver", help="vertical line character \
                        for the header row", metavar="CHAR", dest="header_ver",
                        default="│")
    parser.add_argument("--hcorners", help="corner characters for \
                        the header row", metavar="CHARS",
                        dest="header_corners", default="╒╤╕╞╪╡")
    parser.add_argument("file", help="file to render or - to read from STDIN",
                        nargs="?", default="-", metavar="FILE")
    parser.add_argument("--version", "-v", action="version",
                        version="%(prog)s 0.1")
    args = parser.parse_args()

    if args.file == "-":
        args.file = sys.stdin
    else:
        args.file = open(args.file, encoding="utf-8")

    table = [line.strip().split("\t") for line in args.file.readlines()]
    args = vars(args)
    del args["file"]
    print_table(table, **args)

if __name__ == "__main__":
    main()