File: _table.py

package info (click to toggle)
freeorion 0.5.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 194,920 kB
  • sloc: cpp: 186,821; python: 40,979; ansic: 1,164; xml: 721; makefile: 32; sh: 7
file content (146 lines) | stat: -rw-r--r-- 4,765 bytes parent folder | download | duplicates (2)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""
Print utils.

For test proposes this module is not import any freeorion runtime libraries.
If you improve it somehow, add usage example to __main__ section.
"""
from collections import defaultdict
from collections.abc import Collection
from itertools import zip_longest
from math import ceil
from typing import Any, Union

from common.print_utils._base_field import Field


def as_columns(items: Collection[Any], columns=2) -> str:
    """
    Split flat list to columns and print them.

    >>> as_columns(['a', 'b', 'c', 'd'], 2)
    a   c
    b   d
    """
    row_count = int(ceil(len(items) / columns))
    text_columns = list(zip_longest(*[iter(items)] * row_count, fillvalue=""))
    column_widths = (max(len(x) for x in word) for word in text_columns)
    template = "   ".join("%%-%ss" % w for w in column_widths)

    return "\n".join(template % row for row in zip(*text_columns))


class Table:
    def __init__(
        self,
        *fields: Union[Field, Collection[Field]],
        vertical_sep="|",
        header_sep="=",
        bottom_sep="-",
        table_name="",
        hide_header=False,
    ):
        """
        Table layout for print data.

        - specify headers in constructor
        - add rows
        - print
        """
        self._table_name = table_name
        self._bottom_sep = bottom_sep
        self._header_sep = header_sep
        self._vertical_sep = vertical_sep
        self._rows = []
        self._notes = []  # each row has a not attached for it
        self._headers = fields
        self._hide_headers = hide_header
        self.totals = defaultdict(int)

    def __str__(self):
        return self.get_table()

    def add_row(self, *row, note: str = ""):
        table_row = []
        for field, val in zip(self._headers, row):
            if field.total:
                self.totals[field] += val
            table_row.append(field.make_cell_string(val))
        self._rows.append(table_row)
        self._notes.append(note)

    def _get_row_separator(self, char, column_widthes):
        return char * (2 + (len(column_widthes) - 1) * 3 + sum(column_widthes) + 2)

    def _get_rows_width(self) -> list[int]:
        if self._rows:
            return [max([len(y) for y in x], default=0) for x in zip(*self._rows)]
        else:
            return [1] * len(self._headers)

    def _get_headers_width(self) -> list[int]:
        if self._hide_headers:
            return [1] * len(self._headers)
        else:
            return [len(x.name) for x in self._headers]

    def _get_table_column_width(self) -> list[int]:
        columns_width = self._get_rows_width()
        header_width = self._get_headers_width()
        return [max(a, b) for a, b in zip(columns_width, header_width)]

    def __iter__(self):
        column_widths = self._get_table_column_width()

        if self._table_name:
            yield self._table_name

        inner_separator = " %s " % self._vertical_sep

        if not self._hide_headers:
            yield self._get_row_separator(self._header_sep, column_widths)

            yield "{} {} {}".format(
                self._vertical_sep,
                inner_separator.join(h.format_header(width) for h, width in zip(self._headers, column_widths)),
                self._vertical_sep,
            )

        yield self._get_row_separator(self._header_sep, column_widths)

        for row, note in zip(self._rows, self._notes):
            if note:
                note = f"  {note.strip()}"

            row_content = inner_separator.join(
                h.format_cell(item, width) for h, item, width in zip(self._headers, row, column_widths)
            )
            yield f"{self._vertical_sep} {row_content} {self._vertical_sep}{note}"

        if self.totals:
            yield self._get_row_separator(self._header_sep, column_widths)

            inner = inner_separator.join(
                h.format_cell(self.totals.get(h, " "), width) for h, width in zip(self._headers, column_widths)
            )

            yield f"{self._vertical_sep} {inner} {self._vertical_sep}"
            yield self._get_row_separator(self._header_sep, column_widths)
        else:
            yield self._get_row_separator(self._bottom_sep, column_widths)

        # print legend
        legend = [x for x in self._headers if x.description]
        if legend:
            name_width = max(len(x.name) for x in legend)
            for header in legend:
                yield "*%-*s %s" % (name_width, header.name[:-1], header.description)

    def get_table(self) -> str:
        """
        Return table as text.

        This method is deprecated, since long output will be truncated by logger.
        Use  print_table instead.
        """

        return "\n".join(self)