cs-printt 20261016__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cs_printt-20261016/PKG-INFO +188 -0
- cs_printt-20261016/pyproject.toml +212 -0
- cs_printt-20261016/src/cs/printt.py +399 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cs-printt
|
|
3
|
+
Version: 20261016
|
|
4
|
+
Summary: Print text tables, in the terminal or to a file, using `printt()` and its supporting functions.
|
|
5
|
+
Keywords: python3
|
|
6
|
+
Author-email: Cameron Simpson <cs@cskk.id.au>
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Topic :: Text Processing
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
15
|
+
Requires-Dist: cs.ascii_art>=20260403
|
|
16
|
+
Requires-Dist: cs.deco>=20261016
|
|
17
|
+
Requires-Dist: cs.lex>=20261016
|
|
18
|
+
Requires-Dist: cs.seq>=20260912
|
|
19
|
+
Requires-Dist: typeguard
|
|
20
|
+
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
|
|
21
|
+
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
|
|
22
|
+
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
|
|
23
|
+
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/printt.py
|
|
24
|
+
|
|
25
|
+
Print text tables, in the terminal or to a file, using `printt()` and its supporting functions.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Short summary:
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
* `flatten_table_rows`: Flatten a list of table rows or tuples-of-table-rows into a `(flat_rows,attach)` 2 -tuple where `flat_rows` is a list lof list-of-str rows and `attach` is a list of indices into `flat_rows` which are attachment points. Nested subtables are connected with lines.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
* `printt`: A wrapper for `tabulate()` to print the results. Each positional argument is a table row.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
* `row_cells`: Turn a row of items into a grid.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
* `tabulate`: A generator yielding lines of values from `rows` aligned in columns.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
* `tabulate_as_str`: The default function to return the printable form of an object `obj` for use in a table in `tabulate()`.
|
|
45
|
+
|
|
46
|
+
# Functions
|
|
47
|
+
|
|
48
|
+
## flatten_table_rows(table_rows: list, as_str: Optional[Callable[[Any], str]] = None) -> tuple[list[list[str]], list[int]]
|
|
49
|
+
|
|
50
|
+
Flatten a list of table rows or tuples-of-table-rows into a
|
|
51
|
+
`(flat_rows,attach)` 2 -tuple where `flat_rows` is a list lof
|
|
52
|
+
list-of-str rows and `attach` is a list of indices into `flat_rows`
|
|
53
|
+
which are attachment points.
|
|
54
|
+
Nested subtables are connected with lines.
|
|
55
|
+
|
|
56
|
+
The default `as_str` comes from `tabulate.as_str`.
|
|
57
|
+
|
|
58
|
+
This is a helper function for `tabulate()`, itself a helper
|
|
59
|
+
function for `printt()`.
|
|
60
|
+
|
|
61
|
+
## printt(*table, file=None, flush=False, indent='', print_func=None, **tabulate_kw)
|
|
62
|
+
|
|
63
|
+
A wrapper for `tabulate()` to print the results.
|
|
64
|
+
Each positional argument is a table row.
|
|
65
|
+
|
|
66
|
+
Parameters:
|
|
67
|
+
* `file`: optional output file, passed to `print_func`
|
|
68
|
+
* `flush`: optional flush flag, passed to `print_func`
|
|
69
|
+
* `indent`: optional leading indent for the output lines;
|
|
70
|
+
if this is an `int` instead of a `str`, use that many spaces
|
|
71
|
+
* `print_func`: optional `print()` function, default `builtins.print`
|
|
72
|
+
Other keyword arguments are passed to `tabulate()`.
|
|
73
|
+
|
|
74
|
+
## row_cells(row: Union[str, Sequence[Any]], as_str: Optional[Callable[[Any], str]] = None) -> list[list[str]]
|
|
75
|
+
|
|
76
|
+
Turn a row of items into a grid.
|
|
77
|
+
|
|
78
|
+
This is a helper function for `flatten_table_rows()`.
|
|
79
|
+
|
|
80
|
+
Process:
|
|
81
|
+
- convert each non-`str` item into a string using `as_str`
|
|
82
|
+
- break each string into lines
|
|
83
|
+
- make enough rows to cover the tallest column
|
|
84
|
+
|
|
85
|
+
The default `as_str` comes from `tabulate.as_str`.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
|
|
89
|
+
>>> for cells in row_cells(['col1', 'multi\nline\ntext', 'goes\nhere', 'and\nhere']):
|
|
90
|
+
... print(cells)
|
|
91
|
+
...
|
|
92
|
+
['col1', 'multi', 'goes', 'and']
|
|
93
|
+
['', 'line', 'here', 'here']
|
|
94
|
+
['', 'text', '', '']
|
|
95
|
+
|
|
96
|
+
## tabulate(*rows, sep=' ', as_str: Optional[Callable[[Any], str]] = None) -> Iterable[str]
|
|
97
|
+
|
|
98
|
+
A generator yielding lines of values from `rows` aligned in columns.
|
|
99
|
+
|
|
100
|
+
Usually, each row in rows is a `list` of strings. Non-`str` objects are
|
|
101
|
+
promoted to `str` via the `as_str` function.
|
|
102
|
+
If the strings contain newlines they will be split into
|
|
103
|
+
subrows.
|
|
104
|
+
|
|
105
|
+
A row may also be a `tuple`, indicating a subtable, which
|
|
106
|
+
will be embedded with its column 0 indented and joined up
|
|
107
|
+
with lines to indicate structure.
|
|
108
|
+
|
|
109
|
+
A row may also be a mapping, in which case it is transformed
|
|
110
|
+
into multiple rows of `[key,value]` from the `row.items()`,
|
|
111
|
+
sorted by `key` if possible.
|
|
112
|
+
|
|
113
|
+
The default `as_str` function is `TabulatePrettyPrinter().pformat`;
|
|
114
|
+
the `TabulatePrettyPrinter` class is a subclass of `pprint.PrettyPrinter`
|
|
115
|
+
which also understands `datetime.date` and `datetime.datetime` objects.
|
|
116
|
+
This is exposed as `tabulate.default_as_str`.
|
|
117
|
+
|
|
118
|
+
Example:
|
|
119
|
+
|
|
120
|
+
>>> for row in tabulate(
|
|
121
|
+
... ['one col'],
|
|
122
|
+
... ['three', 'column', 'row'],
|
|
123
|
+
... ['row3', 'multi\nline\ntext', 'goes\nhere', 'and\nhere'],
|
|
124
|
+
... ['two', 'cols'],
|
|
125
|
+
... (
|
|
126
|
+
... ['subrow', 'description'],
|
|
127
|
+
... (["0nd", "subtable"],),
|
|
128
|
+
... (
|
|
129
|
+
... ['inner', 'table'],
|
|
130
|
+
... ['', 'unattached'],
|
|
131
|
+
... ['inner\nbottom', 'last inner row'],
|
|
132
|
+
... ),
|
|
133
|
+
... (["2nd\nbbb", "subtable"],),
|
|
134
|
+
... ['subrow2'],
|
|
135
|
+
... ),
|
|
136
|
+
... ['one col'],
|
|
137
|
+
... ['three', 'column', 'row'],
|
|
138
|
+
... ['row3', 'multi\nline\ntext', 'goes\nhere', 'and\nhere'],
|
|
139
|
+
... ['two', 'cols'],
|
|
140
|
+
... (
|
|
141
|
+
... ['subrow','description'],
|
|
142
|
+
... (
|
|
143
|
+
... ['inner','table'],
|
|
144
|
+
... ['','unattached'],
|
|
145
|
+
... ['inner\nbottom','last inner row'],
|
|
146
|
+
... ),
|
|
147
|
+
... ),
|
|
148
|
+
... ['subrow2'],
|
|
149
|
+
... ):
|
|
150
|
+
... print(row)
|
|
151
|
+
...
|
|
152
|
+
one col
|
|
153
|
+
three column row
|
|
154
|
+
row3 multi goes and
|
|
155
|
+
line here here
|
|
156
|
+
text
|
|
157
|
+
two cols
|
|
158
|
+
├─subrow description
|
|
159
|
+
│ ╰─0nd subtable
|
|
160
|
+
│ ╭─inner table
|
|
161
|
+
│ │ unattached
|
|
162
|
+
│ ╰─inner last inner row
|
|
163
|
+
│ bottom
|
|
164
|
+
│ ◯─2nd subtable
|
|
165
|
+
│ bbb
|
|
166
|
+
╰─subrow2
|
|
167
|
+
one col
|
|
168
|
+
three column row
|
|
169
|
+
row3 multi goes and
|
|
170
|
+
line here here
|
|
171
|
+
text
|
|
172
|
+
two cols
|
|
173
|
+
╰─subrow description
|
|
174
|
+
├─inner table
|
|
175
|
+
│ unattached
|
|
176
|
+
╰─inner last inner row
|
|
177
|
+
bottom
|
|
178
|
+
subrow2
|
|
179
|
+
>>>
|
|
180
|
+
|
|
181
|
+
## tabulate_as_str(obj, *, compact=True, sort_dicts=True, **pformat_kw) -> str
|
|
182
|
+
|
|
183
|
+
The default function to return the printable form of an object `obj`
|
|
184
|
+
for use in a table in `tabulate()`.
|
|
185
|
+
|
|
186
|
+
This function calls `pprint.pformat()` except for certain specially handled types:
|
|
187
|
+
- `date` and `datetime`: their ISO8601 format
|
|
188
|
+
- `PIL.Image.Image`: is cs.imageutils is available, a SIXEL presentation
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cs-printt"
|
|
3
|
+
description = "Print text tables, in the terminal or to a file, using `printt()` and its supporting functions."
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Cameron Simpson", email = "cs@cskk.id.au" },
|
|
6
|
+
]
|
|
7
|
+
keywords = [
|
|
8
|
+
"python3",
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cs.ascii_art>=20260403",
|
|
12
|
+
"cs.deco>=20261016",
|
|
13
|
+
"cs.lex>=20261016",
|
|
14
|
+
"cs.seq>=20260912",
|
|
15
|
+
"typeguard",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Text Processing",
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
25
|
+
]
|
|
26
|
+
version = "20261016"
|
|
27
|
+
|
|
28
|
+
[project.license]
|
|
29
|
+
text = "GNU General Public License v3 or later (GPLv3+)"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
|
|
33
|
+
"Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
|
|
34
|
+
"MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
|
|
35
|
+
Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/printt.py"
|
|
36
|
+
|
|
37
|
+
[project.readme]
|
|
38
|
+
text = """
|
|
39
|
+
Print text tables, in the terminal or to a file, using `printt()` and its supporting functions.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Short summary:
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
* `flatten_table_rows`: Flatten a list of table rows or tuples-of-table-rows into a `(flat_rows,attach)` 2 -tuple where `flat_rows` is a list lof list-of-str rows and `attach` is a list of indices into `flat_rows` which are attachment points. Nested subtables are connected with lines.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
* `printt`: A wrapper for `tabulate()` to print the results. Each positional argument is a table row.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
* `row_cells`: Turn a row of items into a grid.
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
* `tabulate`: A generator yielding lines of values from `rows` aligned in columns.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
* `tabulate_as_str`: The default function to return the printable form of an object `obj` for use in a table in `tabulate()`.
|
|
59
|
+
|
|
60
|
+
# Functions
|
|
61
|
+
|
|
62
|
+
## flatten_table_rows(table_rows: list, as_str: Optional[Callable[[Any], str]] = None) -> tuple[list[list[str]], list[int]]
|
|
63
|
+
|
|
64
|
+
Flatten a list of table rows or tuples-of-table-rows into a
|
|
65
|
+
`(flat_rows,attach)` 2 -tuple where `flat_rows` is a list lof
|
|
66
|
+
list-of-str rows and `attach` is a list of indices into `flat_rows`
|
|
67
|
+
which are attachment points.
|
|
68
|
+
Nested subtables are connected with lines.
|
|
69
|
+
|
|
70
|
+
The default `as_str` comes from `tabulate.as_str`.
|
|
71
|
+
|
|
72
|
+
This is a helper function for `tabulate()`, itself a helper
|
|
73
|
+
function for `printt()`.
|
|
74
|
+
|
|
75
|
+
## printt(*table, file=None, flush=False, indent='', print_func=None, **tabulate_kw)
|
|
76
|
+
|
|
77
|
+
A wrapper for `tabulate()` to print the results.
|
|
78
|
+
Each positional argument is a table row.
|
|
79
|
+
|
|
80
|
+
Parameters:
|
|
81
|
+
* `file`: optional output file, passed to `print_func`
|
|
82
|
+
* `flush`: optional flush flag, passed to `print_func`
|
|
83
|
+
* `indent`: optional leading indent for the output lines;
|
|
84
|
+
if this is an `int` instead of a `str`, use that many spaces
|
|
85
|
+
* `print_func`: optional `print()` function, default `builtins.print`
|
|
86
|
+
Other keyword arguments are passed to `tabulate()`.
|
|
87
|
+
|
|
88
|
+
## row_cells(row: Union[str, Sequence[Any]], as_str: Optional[Callable[[Any], str]] = None) -> list[list[str]]
|
|
89
|
+
|
|
90
|
+
Turn a row of items into a grid.
|
|
91
|
+
|
|
92
|
+
This is a helper function for `flatten_table_rows()`.
|
|
93
|
+
|
|
94
|
+
Process:
|
|
95
|
+
- convert each non-`str` item into a string using `as_str`
|
|
96
|
+
- break each string into lines
|
|
97
|
+
- make enough rows to cover the tallest column
|
|
98
|
+
|
|
99
|
+
The default `as_str` comes from `tabulate.as_str`.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
|
|
103
|
+
>>> for cells in row_cells(['col1', 'multi\\nline\\ntext', 'goes\\nhere', 'and\\nhere']):
|
|
104
|
+
... print(cells)
|
|
105
|
+
...
|
|
106
|
+
['col1', 'multi', 'goes', 'and']
|
|
107
|
+
['', 'line', 'here', 'here']
|
|
108
|
+
['', 'text', '', '']
|
|
109
|
+
|
|
110
|
+
## tabulate(*rows, sep=' ', as_str: Optional[Callable[[Any], str]] = None) -> Iterable[str]
|
|
111
|
+
|
|
112
|
+
A generator yielding lines of values from `rows` aligned in columns.
|
|
113
|
+
|
|
114
|
+
Usually, each row in rows is a `list` of strings. Non-`str` objects are
|
|
115
|
+
promoted to `str` via the `as_str` function.
|
|
116
|
+
If the strings contain newlines they will be split into
|
|
117
|
+
subrows.
|
|
118
|
+
|
|
119
|
+
A row may also be a `tuple`, indicating a subtable, which
|
|
120
|
+
will be embedded with its column 0 indented and joined up
|
|
121
|
+
with lines to indicate structure.
|
|
122
|
+
|
|
123
|
+
A row may also be a mapping, in which case it is transformed
|
|
124
|
+
into multiple rows of `[key,value]` from the `row.items()`,
|
|
125
|
+
sorted by `key` if possible.
|
|
126
|
+
|
|
127
|
+
The default `as_str` function is `TabulatePrettyPrinter().pformat`;
|
|
128
|
+
the `TabulatePrettyPrinter` class is a subclass of `pprint.PrettyPrinter`
|
|
129
|
+
which also understands `datetime.date` and `datetime.datetime` objects.
|
|
130
|
+
This is exposed as `tabulate.default_as_str`.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
>>> for row in tabulate(
|
|
135
|
+
... ['one col'],
|
|
136
|
+
... ['three', 'column', 'row'],
|
|
137
|
+
... ['row3', 'multi\\nline\\ntext', 'goes\\nhere', 'and\\nhere'],
|
|
138
|
+
... ['two', 'cols'],
|
|
139
|
+
... (
|
|
140
|
+
... ['subrow', 'description'],
|
|
141
|
+
... ([\"0nd\", \"subtable\"],),
|
|
142
|
+
... (
|
|
143
|
+
... ['inner', 'table'],
|
|
144
|
+
... ['', 'unattached'],
|
|
145
|
+
... ['inner\\nbottom', 'last inner row'],
|
|
146
|
+
... ),
|
|
147
|
+
... ([\"2nd\\nbbb\", \"subtable\"],),
|
|
148
|
+
... ['subrow2'],
|
|
149
|
+
... ),
|
|
150
|
+
... ['one col'],
|
|
151
|
+
... ['three', 'column', 'row'],
|
|
152
|
+
... ['row3', 'multi\\nline\\ntext', 'goes\\nhere', 'and\\nhere'],
|
|
153
|
+
... ['two', 'cols'],
|
|
154
|
+
... (
|
|
155
|
+
... ['subrow','description'],
|
|
156
|
+
... (
|
|
157
|
+
... ['inner','table'],
|
|
158
|
+
... ['','unattached'],
|
|
159
|
+
... ['inner\\nbottom','last inner row'],
|
|
160
|
+
... ),
|
|
161
|
+
... ),
|
|
162
|
+
... ['subrow2'],
|
|
163
|
+
... ):
|
|
164
|
+
... print(row)
|
|
165
|
+
...
|
|
166
|
+
one col
|
|
167
|
+
three column row
|
|
168
|
+
row3 multi goes and
|
|
169
|
+
line here here
|
|
170
|
+
text
|
|
171
|
+
two cols
|
|
172
|
+
├─subrow description
|
|
173
|
+
│ ╰─0nd subtable
|
|
174
|
+
│ ╭─inner table
|
|
175
|
+
│ │ unattached
|
|
176
|
+
│ ╰─inner last inner row
|
|
177
|
+
│ bottom
|
|
178
|
+
│ ◯─2nd subtable
|
|
179
|
+
│ bbb
|
|
180
|
+
╰─subrow2
|
|
181
|
+
one col
|
|
182
|
+
three column row
|
|
183
|
+
row3 multi goes and
|
|
184
|
+
line here here
|
|
185
|
+
text
|
|
186
|
+
two cols
|
|
187
|
+
╰─subrow description
|
|
188
|
+
├─inner table
|
|
189
|
+
│ unattached
|
|
190
|
+
╰─inner last inner row
|
|
191
|
+
bottom
|
|
192
|
+
subrow2
|
|
193
|
+
>>>
|
|
194
|
+
|
|
195
|
+
## tabulate_as_str(obj, *, compact=True, sort_dicts=True, **pformat_kw) -> str
|
|
196
|
+
|
|
197
|
+
The default function to return the printable form of an object `obj`
|
|
198
|
+
for use in a table in `tabulate()`.
|
|
199
|
+
|
|
200
|
+
This function calls `pprint.pformat()` except for certain specially handled types:
|
|
201
|
+
- `date` and `datetime`: their ISO8601 format
|
|
202
|
+
- `PIL.Image.Image`: is cs.imageutils is available, a SIXEL presentation"""
|
|
203
|
+
content-type = "text/markdown"
|
|
204
|
+
|
|
205
|
+
[build-system]
|
|
206
|
+
build-backend = "flit_core.buildapi"
|
|
207
|
+
requires = [
|
|
208
|
+
"flit_core >=3.2,<4",
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
[tool.flit.module]
|
|
212
|
+
name = "cs.printt"
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
''' Print text tables, in the terminal or to a file, using `printt()` and its supporting functions.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
from datetime import date, datetime
|
|
7
|
+
from pprint import pformat
|
|
8
|
+
from typing import Any, Callable, Iterable, Mapping, Sequence
|
|
9
|
+
|
|
10
|
+
from typeguard import typechecked
|
|
11
|
+
|
|
12
|
+
from cs.ascii_art import box_char, HORIZ, LARGE_CIRCLE
|
|
13
|
+
from cs.deco import attr
|
|
14
|
+
from cs.lex import indent
|
|
15
|
+
from cs.seq import with_neighbours
|
|
16
|
+
|
|
17
|
+
__version__ = '20261016'
|
|
18
|
+
|
|
19
|
+
DISTINFO = {
|
|
20
|
+
'keywords': ["python3"],
|
|
21
|
+
'classifiers': [
|
|
22
|
+
"Programming Language :: Python",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Topic :: Text Processing",
|
|
25
|
+
],
|
|
26
|
+
'install_requires': [
|
|
27
|
+
'cs.ascii_art>=LARGE_CIRCLE',
|
|
28
|
+
'cs.deco',
|
|
29
|
+
'cs.lex',
|
|
30
|
+
'cs.seq',
|
|
31
|
+
'typeguard',
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
##class TabulatePrettyPrinter(PrettyPrinter):
|
|
36
|
+
## ''' A `PrettyPrinter` subclass which presents `date` and
|
|
37
|
+
## `datetime` as ISO8601 strings.
|
|
38
|
+
##
|
|
39
|
+
## This is used by `tabulate` and `printt`.
|
|
40
|
+
## '''
|
|
41
|
+
##
|
|
42
|
+
## def __init__(self, compact=True, sort_dicts=True, **ppkw):
|
|
43
|
+
## ''' The default format settings use `compact=True` and `sort_dicts=True`.
|
|
44
|
+
## '''
|
|
45
|
+
## super().__init__(compact=compact, sort_dicts=sort_dicts, **ppkw)
|
|
46
|
+
##
|
|
47
|
+
## def format(self, obj, *fmt_a) -> Tuple[str, bool, bool]:
|
|
48
|
+
## ''' Use ISO8601 for `date` and `datetime` objects.
|
|
49
|
+
## '''
|
|
50
|
+
## print("PFORMAT", type(obj))
|
|
51
|
+
## if type(obj) is not str:
|
|
52
|
+
## breakpoint()
|
|
53
|
+
## if isinstance(obj, date):
|
|
54
|
+
## return obj.isoformat(), True, False
|
|
55
|
+
## if isinstance(obj, datetime):
|
|
56
|
+
## return obj.isoformat(' '), True, False
|
|
57
|
+
## print("check for image", type(obj), repr(obj))
|
|
58
|
+
## try:
|
|
59
|
+
## from cs.imageutils import Image, SixelImageString
|
|
60
|
+
## except ImportError as e:
|
|
61
|
+
## ##warning(f'cs.imageutils import error: {e}')
|
|
62
|
+
## ##breakpoint()
|
|
63
|
+
## # this happens very ealy for cs.cmdutils
|
|
64
|
+
## pass
|
|
65
|
+
## else:
|
|
66
|
+
## ##print("imported cs.imageutils")
|
|
67
|
+
## print("check for Image vs", type(obj))
|
|
68
|
+
## if isinstance(obj, Image.Image):
|
|
69
|
+
## print("an Image, return SixelImageString")
|
|
70
|
+
## sis = SixelImageString(obj)
|
|
71
|
+
## print("sis =", repr(sis))
|
|
72
|
+
## breakpoint()
|
|
73
|
+
## print("RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR")
|
|
74
|
+
## return SixelImageString(obj), True, False
|
|
75
|
+
## print("check for SixelImageString vs", SixelImageString)
|
|
76
|
+
## print(isinstance(obj, SixelImageString))
|
|
77
|
+
## if isinstance(obj, SixelImageString):
|
|
78
|
+
## print("a SixelImageString, return directly")
|
|
79
|
+
## return obj, True, False
|
|
80
|
+
## print("PFORMAT: fall through to super for type", type(obj))
|
|
81
|
+
## return super().format(obj, *fmt_a)
|
|
82
|
+
|
|
83
|
+
def row_cells(
|
|
84
|
+
row: str | Sequence[Any],
|
|
85
|
+
as_str: Callable[Any, str] | None = None,
|
|
86
|
+
) -> list[list[str]]:
|
|
87
|
+
r'''Turn a row of items into a grid.
|
|
88
|
+
|
|
89
|
+
This is a helper function for `flatten_table_rows()`.
|
|
90
|
+
|
|
91
|
+
Process:
|
|
92
|
+
- convert each non-`str` item into a string using `as_str`
|
|
93
|
+
- break each string into lines
|
|
94
|
+
- make enough rows to cover the tallest column
|
|
95
|
+
|
|
96
|
+
The default `as_str` comes from `tabulate.as_str`.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
|
|
100
|
+
>>> for cells in row_cells(['col1', 'multi\nline\ntext', 'goes\nhere', 'and\nhere']):
|
|
101
|
+
... print(cells)
|
|
102
|
+
...
|
|
103
|
+
['col1', 'multi', 'goes', 'and']
|
|
104
|
+
['', 'line', 'here', 'here']
|
|
105
|
+
['', 'text', '', '']
|
|
106
|
+
'''
|
|
107
|
+
if as_str is None:
|
|
108
|
+
as_str = tabulate.default_as_str
|
|
109
|
+
# promote a str to a single item list
|
|
110
|
+
if isinstance(row, str):
|
|
111
|
+
row = [row]
|
|
112
|
+
# turn non-str row items into strings
|
|
113
|
+
row = [(cell if isinstance(cell, str) else as_str(cell)) for cell in row]
|
|
114
|
+
if all("\n" not in cell for cell in row):
|
|
115
|
+
# no multiline row cells
|
|
116
|
+
return [row]
|
|
117
|
+
# split multiline cells into columns, pad columns to match
|
|
118
|
+
cols = [[subcell.rstrip() for subcell in cell.splitlines()] for cell in row]
|
|
119
|
+
max_height = max(map(len, cols))
|
|
120
|
+
row_cells = [
|
|
121
|
+
[(col[subrow] if subrow < len(col) else '')
|
|
122
|
+
for col in cols]
|
|
123
|
+
for subrow in range(max_height)
|
|
124
|
+
]
|
|
125
|
+
return row_cells
|
|
126
|
+
|
|
127
|
+
@typechecked
|
|
128
|
+
def flatten_table_rows(
|
|
129
|
+
table_rows: list,
|
|
130
|
+
as_str: Callable[Any, str] | None = None,
|
|
131
|
+
) -> tuple[list[list[str]], list[int]]:
|
|
132
|
+
''' Flatten a list of table rows or tuples-of-table-rows into a
|
|
133
|
+
`(flat_rows,attach)` 2 -tuple where `flat_rows` is a list lof
|
|
134
|
+
list-of-str rows and `attach` is a list of indices into `flat_rows`
|
|
135
|
+
which are attachment points.
|
|
136
|
+
Nested subtables are connected with lines.
|
|
137
|
+
|
|
138
|
+
The default `as_str` comes from `tabulate.as_str`.
|
|
139
|
+
|
|
140
|
+
This is a helper function for `tabulate()`, itself a helper
|
|
141
|
+
function for `printt()`.
|
|
142
|
+
'''
|
|
143
|
+
if as_str is None:
|
|
144
|
+
as_str = tabulate.default_as_str
|
|
145
|
+
|
|
146
|
+
def into_lists_and_tuples(raw_rows):
|
|
147
|
+
''' Expand the rows into a list of list or tuple.
|
|
148
|
+
'''
|
|
149
|
+
lt_rows = []
|
|
150
|
+
for row in raw_rows:
|
|
151
|
+
if isinstance(row, (list, tuple)):
|
|
152
|
+
lt_rows.append(row)
|
|
153
|
+
elif isinstance(row, Mapping):
|
|
154
|
+
m_rows = list(row.items())
|
|
155
|
+
# we do not sort in place because a TypeError is raised
|
|
156
|
+
# during the sort if the keys are not all comparable
|
|
157
|
+
try:
|
|
158
|
+
m_rows = sorted(m_rows)
|
|
159
|
+
except TypeError:
|
|
160
|
+
pass
|
|
161
|
+
lt_rows.append(tuple(map(list, m_rows)))
|
|
162
|
+
else:
|
|
163
|
+
lt_rows.append([row])
|
|
164
|
+
return lt_rows
|
|
165
|
+
|
|
166
|
+
def is_attachable_cell_grid(cells):
|
|
167
|
+
''' Test if a cell row has nonwhitepace in column 0
|
|
168
|
+
'''
|
|
169
|
+
return bool(cells and cells[0] and cells[0][0].lstrip())
|
|
170
|
+
|
|
171
|
+
# promote rows to lists or AttachedLines
|
|
172
|
+
rows: list[list[str]] = []
|
|
173
|
+
attach: list[int] = []
|
|
174
|
+
|
|
175
|
+
def indent_cell(cell, line_indent: str):
|
|
176
|
+
try:
|
|
177
|
+
indent_method = cell.indent
|
|
178
|
+
except AttributeError:
|
|
179
|
+
pass
|
|
180
|
+
else:
|
|
181
|
+
if callable(indent_method):
|
|
182
|
+
return indent_method(line_indent)
|
|
183
|
+
return indent(cell, line_indent)
|
|
184
|
+
|
|
185
|
+
for prev_trow, trow, next_trow in with_neighbours(
|
|
186
|
+
into_lists_and_tuples(table_rows)):
|
|
187
|
+
# FIXME: this calls row_cells twice on each row
|
|
188
|
+
# maybe call row_cells on all lists?
|
|
189
|
+
# in a prepass?
|
|
190
|
+
if isinstance(trow, list):
|
|
191
|
+
attach_below = bool(
|
|
192
|
+
isinstance(next_trow, tuple) and next_trow
|
|
193
|
+
and is_attachable_cell_grid(row_cells(next_trow[0], as_str=as_str))
|
|
194
|
+
)
|
|
195
|
+
cells = row_cells(trow, as_str=as_str)
|
|
196
|
+
attachable = is_attachable_cell_grid(cells)
|
|
197
|
+
if attachable:
|
|
198
|
+
attach.append(len(rows))
|
|
199
|
+
# append the cell rows, possibly indented and attached
|
|
200
|
+
for ci, cell_row in enumerate(cells):
|
|
201
|
+
if ci > 0 and attach_below:
|
|
202
|
+
line_indent = box_char(
|
|
203
|
+
arc=True,
|
|
204
|
+
up=True,
|
|
205
|
+
down=attach_below,
|
|
206
|
+
right=attachable and ci == 0,
|
|
207
|
+
)
|
|
208
|
+
cell_row[0] = indent_cell(cell_row[0], line_indent)
|
|
209
|
+
rows.append(cell_row)
|
|
210
|
+
elif isinstance(trow, tuple):
|
|
211
|
+
# an indented subscetion
|
|
212
|
+
subrows, subattach = flatten_table_rows(
|
|
213
|
+
list(trow), as_str=as_str
|
|
214
|
+
) # was True
|
|
215
|
+
# indent all the subrows
|
|
216
|
+
n_subattach = len(subattach)
|
|
217
|
+
for subndx, subrow in enumerate(subrows):
|
|
218
|
+
# NB: must be before the subattach.pop(0) below
|
|
219
|
+
attach_above = bool(
|
|
220
|
+
subattach
|
|
221
|
+
and (type(prev_trow) is list or len(subattach) < n_subattach)
|
|
222
|
+
)
|
|
223
|
+
do_attach = bool(subattach and subattach[0] == subndx)
|
|
224
|
+
if do_attach:
|
|
225
|
+
subattach.pop(0)
|
|
226
|
+
sub_attach_below = bool(subattach) # more things to come
|
|
227
|
+
up = attach_above ## do_attach or sub_attach_below,
|
|
228
|
+
down = sub_attach_below
|
|
229
|
+
right = do_attach
|
|
230
|
+
line_indent = (
|
|
231
|
+
LARGE_CIRCLE if right and not up and not down else
|
|
232
|
+
box_char(arc=True, up=up, down=down, right=right)
|
|
233
|
+
) + (
|
|
234
|
+
HORIZ if do_attach else " "
|
|
235
|
+
)
|
|
236
|
+
if subrow:
|
|
237
|
+
subrow[0] = indent_cell(subrow[0], line_indent)
|
|
238
|
+
rows.append(subrow)
|
|
239
|
+
else:
|
|
240
|
+
raise TypeError(f'row type {type(trow)} is neither list nor tuple')
|
|
241
|
+
return rows, attach
|
|
242
|
+
|
|
243
|
+
def tabulate_as_str(
|
|
244
|
+
obj, *, compact=True, sort_dicts=True, **pformat_kw
|
|
245
|
+
) -> str:
|
|
246
|
+
''' The default function to return the printable form of an object `obj`
|
|
247
|
+
for use in a table in `tabulate()`.
|
|
248
|
+
|
|
249
|
+
This function calls `pprint.pformat()` except for certain specially handled types:
|
|
250
|
+
- `date` and `datetime`: their ISO8601 format
|
|
251
|
+
- `PIL.Image.Image`: is cs.imageutils is available, a SIXEL presentation
|
|
252
|
+
'''
|
|
253
|
+
if isinstance(obj, date):
|
|
254
|
+
return obj.isoformat()
|
|
255
|
+
if isinstance(obj, datetime):
|
|
256
|
+
return obj.isoformat(' ')
|
|
257
|
+
try:
|
|
258
|
+
from cs.imageutils import Image, SixelImageString
|
|
259
|
+
except ImportError:
|
|
260
|
+
# this happens very early for cs.cmdutils
|
|
261
|
+
pass
|
|
262
|
+
else:
|
|
263
|
+
if isinstance(obj, Image.Image):
|
|
264
|
+
return SixelImageString(obj)
|
|
265
|
+
if isinstance(obj, SixelImageString):
|
|
266
|
+
return obj
|
|
267
|
+
return pformat(obj, compact=compact, sort_dicts=sort_dicts, **pformat_kw)
|
|
268
|
+
|
|
269
|
+
@attr(default_as_str=tabulate_as_str)
|
|
270
|
+
def tabulate(
|
|
271
|
+
*rows,
|
|
272
|
+
sep=' ',
|
|
273
|
+
as_str: Callable[Any, str] | None = None,
|
|
274
|
+
) -> Iterable[str]:
|
|
275
|
+
r'''A generator yielding lines of values from `rows` aligned in columns.
|
|
276
|
+
|
|
277
|
+
Usually, each row in rows is a `list` of strings. Non-`str` objects are
|
|
278
|
+
promoted to `str` via the `as_str` function.
|
|
279
|
+
If the strings contain newlines they will be split into
|
|
280
|
+
subrows.
|
|
281
|
+
|
|
282
|
+
A row may also be a `tuple`, indicating a subtable, which
|
|
283
|
+
will be embedded with its column 0 indented and joined up
|
|
284
|
+
with lines to indicate structure.
|
|
285
|
+
|
|
286
|
+
A row may also be a mapping, in which case it is transformed
|
|
287
|
+
into multiple rows of `[key,value]` from the `row.items()`,
|
|
288
|
+
sorted by `key` if possible.
|
|
289
|
+
|
|
290
|
+
The default `as_str` function is `TabulatePrettyPrinter().pformat`;
|
|
291
|
+
the `TabulatePrettyPrinter` class is a subclass of `pprint.PrettyPrinter`
|
|
292
|
+
which also understands `datetime.date` and `datetime.datetime` objects.
|
|
293
|
+
This is exposed as `tabulate.default_as_str`.
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
|
|
297
|
+
>>> for row in tabulate(
|
|
298
|
+
... ['one col'],
|
|
299
|
+
... ['three', 'column', 'row'],
|
|
300
|
+
... ['row3', 'multi\nline\ntext', 'goes\nhere', 'and\nhere'],
|
|
301
|
+
... ['two', 'cols'],
|
|
302
|
+
... (
|
|
303
|
+
... ['subrow', 'description'],
|
|
304
|
+
... (["0nd", "subtable"],),
|
|
305
|
+
... (
|
|
306
|
+
... ['inner', 'table'],
|
|
307
|
+
... ['', 'unattached'],
|
|
308
|
+
... ['inner\nbottom', 'last inner row'],
|
|
309
|
+
... ),
|
|
310
|
+
... (["2nd\nbbb", "subtable"],),
|
|
311
|
+
... ['subrow2'],
|
|
312
|
+
... ),
|
|
313
|
+
... ['one col'],
|
|
314
|
+
... ['three', 'column', 'row'],
|
|
315
|
+
... ['row3', 'multi\nline\ntext', 'goes\nhere', 'and\nhere'],
|
|
316
|
+
... ['two', 'cols'],
|
|
317
|
+
... (
|
|
318
|
+
... ['subrow','description'],
|
|
319
|
+
... (
|
|
320
|
+
... ['inner','table'],
|
|
321
|
+
... ['','unattached'],
|
|
322
|
+
... ['inner\nbottom','last inner row'],
|
|
323
|
+
... ),
|
|
324
|
+
... ),
|
|
325
|
+
... ['subrow2'],
|
|
326
|
+
... ):
|
|
327
|
+
... print(row)
|
|
328
|
+
...
|
|
329
|
+
one col
|
|
330
|
+
three column row
|
|
331
|
+
row3 multi goes and
|
|
332
|
+
line here here
|
|
333
|
+
text
|
|
334
|
+
two cols
|
|
335
|
+
├─subrow description
|
|
336
|
+
│ ╰─0nd subtable
|
|
337
|
+
│ ╭─inner table
|
|
338
|
+
│ │ unattached
|
|
339
|
+
│ ╰─inner last inner row
|
|
340
|
+
│ bottom
|
|
341
|
+
│ ◯─2nd subtable
|
|
342
|
+
│ bbb
|
|
343
|
+
╰─subrow2
|
|
344
|
+
one col
|
|
345
|
+
three column row
|
|
346
|
+
row3 multi goes and
|
|
347
|
+
line here here
|
|
348
|
+
text
|
|
349
|
+
two cols
|
|
350
|
+
╰─subrow description
|
|
351
|
+
├─inner table
|
|
352
|
+
│ unattached
|
|
353
|
+
╰─inner last inner row
|
|
354
|
+
bottom
|
|
355
|
+
subrow2
|
|
356
|
+
>>>
|
|
357
|
+
'''
|
|
358
|
+
if as_str is None:
|
|
359
|
+
as_str = tabulate.default_as_str
|
|
360
|
+
if not rows:
|
|
361
|
+
# avoids max of empty list
|
|
362
|
+
return
|
|
363
|
+
rows, _ = flatten_table_rows(list(rows), as_str=as_str)
|
|
364
|
+
# pad short rows with empty columns
|
|
365
|
+
max_cols = max(map(len, rows))
|
|
366
|
+
for row in rows:
|
|
367
|
+
if len(row) < max_cols:
|
|
368
|
+
row += [''] * (max_cols - len(row))
|
|
369
|
+
col_widths = [
|
|
370
|
+
max(map(len, (row[c]
|
|
371
|
+
for row in rows)))
|
|
372
|
+
for c in range(max(map(len, rows)))
|
|
373
|
+
]
|
|
374
|
+
for row in rows:
|
|
375
|
+
yield sep.join(
|
|
376
|
+
f'{col_val:<{col_widths[c]}}' for c, col_val in enumerate(row)
|
|
377
|
+
).rstrip()
|
|
378
|
+
|
|
379
|
+
def printt(
|
|
380
|
+
*table, file=None, flush=False, indent='', print_func=None, **tabulate_kw
|
|
381
|
+
):
|
|
382
|
+
''' A wrapper for `tabulate()` to print the results.
|
|
383
|
+
Each positional argument is a table row.
|
|
384
|
+
|
|
385
|
+
Parameters:
|
|
386
|
+
* `file`: optional output file, passed to `print_func`
|
|
387
|
+
* `flush`: optional flush flag, passed to `print_func`
|
|
388
|
+
* `indent`: optional leading indent for the output lines;
|
|
389
|
+
if this is an `int` instead of a `str`, use that many spaces
|
|
390
|
+
* `print_func`: optional `print()` function, default `builtins.print`
|
|
391
|
+
Other keyword arguments are passed to `tabulate()`.
|
|
392
|
+
'''
|
|
393
|
+
if isinstance(indent, int):
|
|
394
|
+
indent = ' ' * indent
|
|
395
|
+
if print_func is None:
|
|
396
|
+
# obtain the _current_ builtins.print
|
|
397
|
+
from builtins import print as print_func
|
|
398
|
+
for line in tabulate(*table, **tabulate_kw):
|
|
399
|
+
print_func(indent + line, file=file, flush=flush)
|