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