flexablelib 2.0.6__cp39-cp39-win_amd64.whl
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.
- flexablelib/__init__.py +6 -0
- flexablelib/_speedups.cp39-win_amd64.pyd +0 -0
- flexablelib/cells.py +523 -0
- flexablelib/colors.py +195 -0
- flexablelib/columns.py +905 -0
- flexablelib/fast.py +81 -0
- flexablelib/merges.py +603 -0
- flexablelib/options.py +88 -0
- flexablelib/readers.py +401 -0
- flexablelib/style_compositions.py +872 -0
- flexablelib/table.py +4211 -0
- flexablelib/table_strings.py +711 -0
- flexablelib/text_width.py +480 -0
- flexablelib/utils.py +232 -0
- flexablelib/writers.py +582 -0
- flexablelib-2.0.6.dist-info/METADATA +371 -0
- flexablelib-2.0.6.dist-info/RECORD +20 -0
- flexablelib-2.0.6.dist-info/WHEEL +5 -0
- flexablelib-2.0.6.dist-info/licenses/LICENSE +21 -0
- flexablelib-2.0.6.dist-info/top_level.txt +1 -0
flexablelib/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .table import Table
|
|
2
|
+
from .style_compositions import TableComposition, SeparatorLine
|
|
3
|
+
|
|
4
|
+
# Names, not the objects themselves: `from flexablelib import *` walks this
|
|
5
|
+
# list and raises TypeError on anything that is not a string.
|
|
6
|
+
__all__ = ['Table', 'TableComposition', 'SeparatorLine']
|
|
Binary file
|
flexablelib/cells.py
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
""" CELL WRAPPING AND ADJUSTMENT """
|
|
2
|
+
|
|
3
|
+
from .fast import pad_to_width, visible_width
|
|
4
|
+
from .utils import is_some_instance
|
|
5
|
+
from .options import FLT_FILTER, INT_FILTER
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Tuple, Union
|
|
8
|
+
|
|
9
|
+
# Columns a tab advances to. Eight is what `str.expandtabs` and every
|
|
10
|
+
# terminal default to.
|
|
11
|
+
TAB_SIZE = 8
|
|
12
|
+
|
|
13
|
+
LEFT_SIDE_WIDTH_I = 0
|
|
14
|
+
POINT_WIDTH_I = 1
|
|
15
|
+
RIGHT_SIDE_WIDTH_I = -1
|
|
16
|
+
LEFT_SIDE_I = 0
|
|
17
|
+
RIGHT_SIDE_I = 1
|
|
18
|
+
|
|
19
|
+
def _add_cell_spacing(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
20
|
+
left: int,
|
|
21
|
+
right: int,
|
|
22
|
+
placeholder_size: int = 0,
|
|
23
|
+
fill_char: str = ' '
|
|
24
|
+
) -> Union[str, Tuple[str]]:
|
|
25
|
+
"""
|
|
26
|
+
Add spacing to a cell (string).
|
|
27
|
+
|
|
28
|
+
Examples::
|
|
29
|
+
|
|
30
|
+
_add_cell_spacing('a', 1, 3, 0) -> ' a '
|
|
31
|
+
_add_cell_spacing('', 1, 3, 1) -> ' '
|
|
32
|
+
_add_cell_spacing(['a', 'b'], 1, 3, 0) -> (' a ', ' b ')
|
|
33
|
+
_add_cell_spacing('', 1, 3, 0) -> ' '
|
|
34
|
+
"""
|
|
35
|
+
left = fill_char * left # Space for the left
|
|
36
|
+
right = fill_char * right # Space for the right
|
|
37
|
+
|
|
38
|
+
# Creates a string of the desired size
|
|
39
|
+
# to be a placeholder when an empty string is received
|
|
40
|
+
# in the cell parameter.
|
|
41
|
+
diff = fill_char * placeholder_size
|
|
42
|
+
|
|
43
|
+
# Check if the cell is a wrapped cell (list or tuple):
|
|
44
|
+
if is_some_instance(cell, tuple, list):
|
|
45
|
+
# If it is, add the spacing to each part of the cell.
|
|
46
|
+
spaced = list(map(str, cell)) # Convert to strings to prevent errors.
|
|
47
|
+
for part_i, cell_part in enumerate(spaced):
|
|
48
|
+
# Add the spacing to the cell part.
|
|
49
|
+
# FIX should consider the cell_part length
|
|
50
|
+
if placeholder_size != 0 and part_i != 0:
|
|
51
|
+
spaced[part_i] = ''.join([left, diff, right])
|
|
52
|
+
else:
|
|
53
|
+
spaced[part_i] = ''.join([left, cell_part, right])
|
|
54
|
+
else:
|
|
55
|
+
# If it is not, add the spacing to the cell.
|
|
56
|
+
# FIX should consider the cell length
|
|
57
|
+
cell_text = str(cell)
|
|
58
|
+
if placeholder_size != 0:
|
|
59
|
+
spaced = ''.join([left, diff, right])
|
|
60
|
+
else:
|
|
61
|
+
spaced = ''.join([left, cell_text, right])
|
|
62
|
+
|
|
63
|
+
return spaced
|
|
64
|
+
|
|
65
|
+
def _center_cell(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
66
|
+
cell_length: int,
|
|
67
|
+
fill_char: str = ' '
|
|
68
|
+
) -> Union[str, Tuple[str]]:
|
|
69
|
+
"""
|
|
70
|
+
Center a cell. Pads by visible width, so ANSI escape sequences and
|
|
71
|
+
double-width characters are measured correctly. Can also center
|
|
72
|
+
wrapped cells (lists or tuples).
|
|
73
|
+
|
|
74
|
+
Examples::
|
|
75
|
+
|
|
76
|
+
_center_cell('a', 3, '-') -> '-a-'
|
|
77
|
+
_center_cell(['a', 'b'], 3, '-') -> ('-a- ', '-b-')
|
|
78
|
+
"""
|
|
79
|
+
# Check if the cell is a wrapped cell (list or tuple):
|
|
80
|
+
if is_some_instance(cell, tuple, list):
|
|
81
|
+
# If it is, center each part of the cell.
|
|
82
|
+
centered = list(map(str, cell)) # Convert to strings to prevent errors.
|
|
83
|
+
for part_i, cell_part in enumerate(centered):
|
|
84
|
+
# Center the cell part.
|
|
85
|
+
centered[part_i] = pad_to_width(cell_part, cell_length, 'c', fill_char)
|
|
86
|
+
centered = tuple(centered)
|
|
87
|
+
else:
|
|
88
|
+
# If it is not, center the cell.
|
|
89
|
+
centered = pad_to_width(str(cell), cell_length, 'c', fill_char)
|
|
90
|
+
|
|
91
|
+
return centered
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _ljust_cell(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
95
|
+
cell_length: int,
|
|
96
|
+
fill_char: str = ' '
|
|
97
|
+
) -> Union[str, Tuple[str]]:
|
|
98
|
+
"""
|
|
99
|
+
Aligns a cell to the left, padding by visible width. Can also
|
|
100
|
+
align wrapped cells (lists or tuples).
|
|
101
|
+
|
|
102
|
+
Examples::
|
|
103
|
+
|
|
104
|
+
_ljust_cell('a', 3, '-') -> 'a--'
|
|
105
|
+
_ljust_cell(['a', 'b'], 3, '-') -> ('a--', 'b--')
|
|
106
|
+
"""
|
|
107
|
+
# Check if the cell is a wrapped cell (list or tuple):
|
|
108
|
+
if is_some_instance(cell, list, tuple):
|
|
109
|
+
# If it is, align each part of the cell.
|
|
110
|
+
left_adjusted = list(map(str, cell)) # Convert to strings to prevent errors.
|
|
111
|
+
for part_i, cell_part in enumerate(left_adjusted):
|
|
112
|
+
# Align the cell part to the left.
|
|
113
|
+
left_adjusted[part_i] = pad_to_width(cell_part, cell_length, 'l', fill_char)
|
|
114
|
+
left_adjusted = tuple(left_adjusted)
|
|
115
|
+
else:
|
|
116
|
+
# If it is not, align the cell to the left.
|
|
117
|
+
left_adjusted = pad_to_width(str(cell), cell_length, 'l', fill_char)
|
|
118
|
+
|
|
119
|
+
return left_adjusted
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _rjust_cell(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
123
|
+
cell_length: int,
|
|
124
|
+
fill_char: str = ' '
|
|
125
|
+
)-> Union[str, Tuple[str]]:
|
|
126
|
+
"""
|
|
127
|
+
Aligns a cell to the right, padding by visible width. Can also
|
|
128
|
+
align wrapped cells (lists or tuples).
|
|
129
|
+
|
|
130
|
+
Examples::
|
|
131
|
+
|
|
132
|
+
_rjust_cell('a', 3, '-') -> '--a'
|
|
133
|
+
_rjust_cell(['a', 'b'], 3, '-') -> ('--a', '--b')
|
|
134
|
+
"""
|
|
135
|
+
# Check if the cell is a wrapped cell (list or tuple):
|
|
136
|
+
if is_some_instance(cell, list, tuple):
|
|
137
|
+
# If it is, align each part of the cell.
|
|
138
|
+
right_adjusted = list(map(str, cell)) # Convert to strings to prevent errors.
|
|
139
|
+
for part_i, cell_part in enumerate(right_adjusted):
|
|
140
|
+
# Align the cell part to the right.
|
|
141
|
+
right_adjusted[part_i] = pad_to_width(cell_part, cell_length, 'r', fill_char)
|
|
142
|
+
right_adjusted = tuple(right_adjusted)
|
|
143
|
+
else:
|
|
144
|
+
# If it is not, align the cell to the right.
|
|
145
|
+
right_adjusted = pad_to_width(str(cell), cell_length, 'r', fill_char)
|
|
146
|
+
|
|
147
|
+
return right_adjusted
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def fljust(string: str,
|
|
151
|
+
sides_widths: Union[List[int], Tuple[int, int]],
|
|
152
|
+
fill_char: str=' ') -> str:
|
|
153
|
+
"""
|
|
154
|
+
Align a float number according to the sides widths. The purpose is
|
|
155
|
+
to send various float numbers to align them by the points.
|
|
156
|
+
The number must always have a decimal point.
|
|
157
|
+
|
|
158
|
+
Examples::
|
|
159
|
+
|
|
160
|
+
fljust('1.23', [3, 5], '-')
|
|
161
|
+
fljust('1.2', [3, 5], '-')
|
|
162
|
+
fljust('.00321', [3, 5], '-')
|
|
163
|
+
|
|
164
|
+
Results in::
|
|
165
|
+
|
|
166
|
+
'--1.23---'
|
|
167
|
+
'--1.2----'
|
|
168
|
+
'---.00321'
|
|
169
|
+
"""
|
|
170
|
+
# Split at the point. ``partition`` rather than ``split`` because a
|
|
171
|
+
# number may not have one at all -- 1e+16 is a float and has no point,
|
|
172
|
+
# and indexing the second half of a one-element list raised IndexError.
|
|
173
|
+
left_string, point, right_string = string.partition('.')
|
|
174
|
+
|
|
175
|
+
left_width = sides_widths[LEFT_SIDE_WIDTH_I]
|
|
176
|
+
right_width = sides_widths[RIGHT_SIDE_WIDTH_I]
|
|
177
|
+
|
|
178
|
+
# Align the left part to the right.
|
|
179
|
+
left = pad_to_width(left_string, left_width, 'r', fill_char)
|
|
180
|
+
|
|
181
|
+
# Align the right part to the left.
|
|
182
|
+
right = pad_to_width(right_string, right_width, 'l', fill_char)
|
|
183
|
+
|
|
184
|
+
# A number with no point leaves the point's own column blank, so it is
|
|
185
|
+
# still exactly as wide as the ones that have one.
|
|
186
|
+
return ''.join([left, point or fill_char, right])
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def __fljust_part(cell_part: Any,
|
|
190
|
+
cell_length: int,
|
|
191
|
+
float_column_sizes: Union[List[int], Tuple[int, int, int]],
|
|
192
|
+
fill_char: str = ' '
|
|
193
|
+
) -> str:
|
|
194
|
+
"""
|
|
195
|
+
This will align a cell part.
|
|
196
|
+
If it has a point, it will be aligned as a float with ``fljust()``.
|
|
197
|
+
Int numbers are also aligned with the int part of the floats, but
|
|
198
|
+
using ``_add_cell_spacing()``.
|
|
199
|
+
Any other element (nor float nor int) is right-aligned by visible width
|
|
200
|
+
|
|
201
|
+
Examples::
|
|
202
|
+
|
|
203
|
+
__fljust_part('1.23', 9, [3, 1, 5], '-')
|
|
204
|
+
__fljust_part('1.2', 9, [3, 1, 5], '-')
|
|
205
|
+
__fljust_part('.00321', 9, [3, 1, 5], '-')
|
|
206
|
+
__fljust_part('5', 9, [3, 1, 5], '-')
|
|
207
|
+
__fljust_part('?', 9, [3, 1, 5], '-')
|
|
208
|
+
|
|
209
|
+
Results in::
|
|
210
|
+
|
|
211
|
+
'--1.23---'
|
|
212
|
+
'--1.2----'
|
|
213
|
+
'---.00321'
|
|
214
|
+
'--5------'
|
|
215
|
+
'--------?'
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
# Convert to string because anything could come.
|
|
219
|
+
str_cell = str(cell_part)
|
|
220
|
+
|
|
221
|
+
# Check if the cell part is a float or an int.
|
|
222
|
+
is_float_number = FLT_FILTER(
|
|
223
|
+
str_cell
|
|
224
|
+
) is not None
|
|
225
|
+
is_int_number = INT_FILTER(
|
|
226
|
+
str_cell
|
|
227
|
+
) is not None
|
|
228
|
+
|
|
229
|
+
# If it is a float, align it as a float.
|
|
230
|
+
if is_float_number:
|
|
231
|
+
return fljust(
|
|
232
|
+
str_cell,
|
|
233
|
+
float_column_sizes,
|
|
234
|
+
fill_char
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# If it is an int, align it as an int.
|
|
238
|
+
# Will be aligned to the int part of the float.
|
|
239
|
+
if is_int_number:
|
|
240
|
+
cell_len = visible_width(str_cell)
|
|
241
|
+
|
|
242
|
+
# Get the left and right sides widths, subtracting
|
|
243
|
+
# the length of the int part to the left and adding
|
|
244
|
+
# the len of the point to the right.
|
|
245
|
+
#
|
|
246
|
+
# Explanation:
|
|
247
|
+
# '--1.2----' Having these floats aligned.
|
|
248
|
+
# '---.00321'
|
|
249
|
+
# '--5------' If an int is going to be aligned with the same sizes:
|
|
250
|
+
# --- left width of 3
|
|
251
|
+
# - dot width of 1
|
|
252
|
+
# ------ right width of 5
|
|
253
|
+
# Will do the following:
|
|
254
|
+
# -- 1. Subtract its width of the left.
|
|
255
|
+
# ------- 2. Adding the width of the dot to the right.
|
|
256
|
+
# --5------- 3. add those two spaces to the left and right.
|
|
257
|
+
|
|
258
|
+
# Get the left and right sides widths.
|
|
259
|
+
left_width = float_column_sizes[LEFT_SIDE_WIDTH_I]
|
|
260
|
+
right_width = float_column_sizes[RIGHT_SIDE_WIDTH_I]
|
|
261
|
+
|
|
262
|
+
# Subtract the length of the int part to the left.
|
|
263
|
+
fixed_left_width = left_width - cell_len
|
|
264
|
+
|
|
265
|
+
# Add the len of the point to the right.
|
|
266
|
+
fixed_right_width = right_width + float_column_sizes[POINT_WIDTH_I]
|
|
267
|
+
|
|
268
|
+
# Add the spaces to the left and right.
|
|
269
|
+
return _add_cell_spacing(
|
|
270
|
+
str_cell,
|
|
271
|
+
fixed_left_width,
|
|
272
|
+
fixed_right_width,
|
|
273
|
+
0,
|
|
274
|
+
fill_char
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# If it is not a float nor an int, align it to the right.
|
|
278
|
+
else:
|
|
279
|
+
return pad_to_width(
|
|
280
|
+
str_cell, cell_length, 'r', fill_char
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _fljust_cell(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
285
|
+
cell_length: int,
|
|
286
|
+
float_column_sizes: Union[List[int], Tuple[int, int, int]],
|
|
287
|
+
fill_char: str = ' ',
|
|
288
|
+
) -> Union[str, Union[List[str], Tuple[str]]]:
|
|
289
|
+
"""
|
|
290
|
+
Examples::
|
|
291
|
+
|
|
292
|
+
_fljust_cell('1.23', 9, [3, 1, 5], '-')
|
|
293
|
+
_fljust_cell('1.2', 9, [3, 1, 5], '-')
|
|
294
|
+
_fljust_cell('.00321', 9, [3, 1, 5], '-')
|
|
295
|
+
_fljust_cell('5', 9, [3, 1, 5], '-')
|
|
296
|
+
_fljust_cell(['Nothing', 'here'], 9, [3, 1, 5], '-')
|
|
297
|
+
|
|
298
|
+
will result in::
|
|
299
|
+
|
|
300
|
+
'--1.23---'
|
|
301
|
+
'--1.2----'
|
|
302
|
+
'---.00321'
|
|
303
|
+
'--5------'
|
|
304
|
+
('--Nothing',
|
|
305
|
+
'-----here')
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
# If it is a wrapped cell (list or tuple), align each part.
|
|
309
|
+
if is_some_instance(cell, list, tuple):
|
|
310
|
+
float_adjusted = list(map(str, cell)) # Convert to string.
|
|
311
|
+
for part_i, cell_part in enumerate(float_adjusted):
|
|
312
|
+
float_adjusted[part_i] = __fljust_part(
|
|
313
|
+
cell_part,
|
|
314
|
+
cell_length,
|
|
315
|
+
float_column_sizes,
|
|
316
|
+
fill_char
|
|
317
|
+
)
|
|
318
|
+
float_adjusted = tuple(float_adjusted)
|
|
319
|
+
|
|
320
|
+
# If it is not a wrapped cell, align it.
|
|
321
|
+
else:
|
|
322
|
+
float_adjusted = __fljust_part(
|
|
323
|
+
cell,
|
|
324
|
+
cell_length,
|
|
325
|
+
float_column_sizes,
|
|
326
|
+
fill_char
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return float_adjusted
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _zip_wrapped_rows(wrapped_headers: Union[list, tuple],
|
|
333
|
+
wrapped_rows: Union[list, tuple]
|
|
334
|
+
) -> Tuple[tuple, tuple]:
|
|
335
|
+
"""
|
|
336
|
+
Will zip a wrapped row and convert it to a wrapped column.
|
|
337
|
+
|
|
338
|
+
A wrapped row looks like this::
|
|
339
|
+
|
|
340
|
+
[['Wrapped', 'Looking']['row', 'good']]
|
|
341
|
+
|
|
342
|
+
In order to put in a column, it needs to be paired with
|
|
343
|
+
it's respective part of the consequent sub-rows, so 0
|
|
344
|
+
goes with 0, 1 goes with 1, 2 goes with 2, etc.
|
|
345
|
+
|
|
346
|
+
>>> 0 1 0 1
|
|
347
|
+
>>> [['Wrapped', 'Looking']['row', 'good']]
|
|
348
|
+
|
|
349
|
+
The zip function does exactly that::
|
|
350
|
+
|
|
351
|
+
tuple(zip(*[['Wrapped', 'Looking']['row', 'good']]))
|
|
352
|
+
|
|
353
|
+
results in::
|
|
354
|
+
|
|
355
|
+
(('Wrapped', 'row'), ('Looking', 'good'))
|
|
356
|
+
|
|
357
|
+
But it's also necessary to consider that the intention is to
|
|
358
|
+
convert a row in a column, so the process must be done twice:
|
|
359
|
+
one for the wrapped cells, and one for all the rows::
|
|
360
|
+
|
|
361
|
+
_zip_wrapped_rows([
|
|
362
|
+
[['Data' , 'In a row']], # Not wrapped.
|
|
363
|
+
[['Wrapped', 'Looking'] # Wrapped.
|
|
364
|
+
['row' , 'good' ]],
|
|
365
|
+
[['More' , 'data' ]], # Not wrapped.
|
|
366
|
+
])
|
|
367
|
+
|
|
368
|
+
results in::
|
|
369
|
+
|
|
370
|
+
(
|
|
371
|
+
('Data', ('Wrapped', 'row'), 'More'),
|
|
372
|
+
('In a row', ('Looking', 'good'), 'data'),
|
|
373
|
+
)
|
|
374
|
+
"""
|
|
375
|
+
zipped_wrapped_cells = []
|
|
376
|
+
for column_i, column in enumerate(wrapped_rows):
|
|
377
|
+
# If there's any wrapped cells, here is where they are zipped.
|
|
378
|
+
zipped_wrapped_cells.append(__zip_sub_rows(column))
|
|
379
|
+
# Now, the rows are zipped to be converted to columns.
|
|
380
|
+
zipped_rows = tuple(zip(*zipped_wrapped_cells))
|
|
381
|
+
# The headers are zipped to be converted to columns (in case
|
|
382
|
+
# of a wrapped header).
|
|
383
|
+
zipped_headers = __zip_sub_rows(wrapped_headers)
|
|
384
|
+
|
|
385
|
+
if not zipped_rows:
|
|
386
|
+
# A table with columns but no rows at all still has to hand back one
|
|
387
|
+
# (empty) column per header. Transposing nothing yields nothing, and
|
|
388
|
+
# the caller then indexed past the end of it -- a table with headers
|
|
389
|
+
# and no data raised IndexError rather than rendering its headers.
|
|
390
|
+
zipped_rows = tuple(() for _ in zipped_headers)
|
|
391
|
+
|
|
392
|
+
return zipped_rows, zipped_headers
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def __zip_sub_rows(row: Union[list, tuple]) -> tuple:
|
|
396
|
+
"""
|
|
397
|
+
Zips a wrapped row::
|
|
398
|
+
|
|
399
|
+
__zip_sub_rows([['a', 'b'], ['c', 'd']]) -> (('a', 'c'), ('b', 'd'))
|
|
400
|
+
__zip_sub_rows([['a', 'b']]) -> ('a', 'b')
|
|
401
|
+
"""
|
|
402
|
+
if len(row) > 1:
|
|
403
|
+
# If there's more than one sub-row, zip them.
|
|
404
|
+
zipped_sub_rows = tuple(zip(*row))
|
|
405
|
+
return zipped_sub_rows
|
|
406
|
+
else:
|
|
407
|
+
# If there's only one sub-row, return it.
|
|
408
|
+
only_sub_row = row[0]
|
|
409
|
+
return only_sub_row
|
|
410
|
+
|
|
411
|
+
def _wrap_rows(headers: Union[list, tuple],
|
|
412
|
+
data: Union[List[Union[list, tuple]], Tuple[Union[list, tuple]]]
|
|
413
|
+
) -> Tuple[list, list]:
|
|
414
|
+
"""
|
|
415
|
+
Wraps the rows and headers of the table if
|
|
416
|
+
any new line characters are present::
|
|
417
|
+
|
|
418
|
+
_wrap_rows(headers=['a', 'b\\nc'], data=[['a', 'b\\nc'], ['c', 'd']])
|
|
419
|
+
|
|
420
|
+
Results in:
|
|
421
|
+
|
|
422
|
+
>>> ([['a', 'b'], ['', 'c']], [[['a', 'b'], ['', 'c']], [['c', 'd']]])
|
|
423
|
+
>>> | ^ ^ | | ^ ^ | | |
|
|
424
|
+
>>> # sub-row sub-row sub-row sub-row no sub-rows
|
|
425
|
+
>>> #<-------wrapped-------> <-------wrapped-------> <not wrapped>
|
|
426
|
+
"""
|
|
427
|
+
# If no headers are provided, return None.
|
|
428
|
+
wrapped_headers = None
|
|
429
|
+
|
|
430
|
+
there_are_headers = headers is not None and is_some_instance(headers, list, tuple)
|
|
431
|
+
if there_are_headers:
|
|
432
|
+
# If there are headers, wrap them.
|
|
433
|
+
wrapped_headers = __wrap_single_row(headers)
|
|
434
|
+
|
|
435
|
+
# Wrap each row.
|
|
436
|
+
# If no new line characters are present, data is returned as it is.
|
|
437
|
+
wrapped_data = list(map(__wrap_single_row, data))
|
|
438
|
+
|
|
439
|
+
return wrapped_headers, wrapped_data
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def __wrap_single_row(row: Union[list, tuple]) -> list:
|
|
443
|
+
"""
|
|
444
|
+
Will split every cell that has an new line character.
|
|
445
|
+
The splitted cell generates a new sub-row, and every other
|
|
446
|
+
cell that doesn't have a new line character will be adjusted::
|
|
447
|
+
|
|
448
|
+
__wrap_single_row(['a', 'b\\nc'])
|
|
449
|
+
|
|
450
|
+
Results in:
|
|
451
|
+
|
|
452
|
+
>>> [['a', 'b'], ['', 'c']]
|
|
453
|
+
>>> ^
|
|
454
|
+
>>> # Blank space to adjust the cell.
|
|
455
|
+
"""
|
|
456
|
+
# Split in lines if any new line characters are present.
|
|
457
|
+
splitted_cells = list(map(__wrap_cell, row))
|
|
458
|
+
|
|
459
|
+
# Get the quantity of sub-rows calculating the max length of
|
|
460
|
+
# all of the splitted (or not) cells.
|
|
461
|
+
quantity_of_sub_rows = max([len(x) for x in splitted_cells])
|
|
462
|
+
|
|
463
|
+
# Create blank sub-rows each one having the same quantity of
|
|
464
|
+
# cells as the original row.
|
|
465
|
+
sub_rows = [['' for x in range(len(row))]
|
|
466
|
+
for _ in range(quantity_of_sub_rows)]
|
|
467
|
+
|
|
468
|
+
# Fill the sub-rows with the splitted cells.
|
|
469
|
+
#
|
|
470
|
+
# [['a'], ['b', 'c']] <-- splitted_cells
|
|
471
|
+
# ^ ^ ^
|
|
472
|
+
# sbr0 sbr0 sbr1
|
|
473
|
+
# <cell0> <--cell1-->
|
|
474
|
+
#
|
|
475
|
+
# [['a', 'b'], ['', 'c']] <-- sub_rows (being filled)
|
|
476
|
+
# ^ ^ ^ ^
|
|
477
|
+
# [0][0] [0][1] ^ [1][1] <-- sub_rows[sub_row][cell]
|
|
478
|
+
# [0][0] [1][0] ^ [1][1] <-- splitted_cells[cell][sub-row]
|
|
479
|
+
# ^
|
|
480
|
+
# There's no second sub-row in the first cell,
|
|
481
|
+
# so it's just left empty
|
|
482
|
+
#
|
|
483
|
+
for cell in range(len(splitted_cells)):
|
|
484
|
+
for sub_row in range(len(splitted_cells[cell])):
|
|
485
|
+
sub_rows[sub_row][cell] = splitted_cells[cell][sub_row]
|
|
486
|
+
|
|
487
|
+
return sub_rows
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def __wrap_cell(cell: Any) -> Union[List[str], Any]:
|
|
491
|
+
"""
|
|
492
|
+
Split in lines a cell if it has a new line character::
|
|
493
|
+
|
|
494
|
+
'a' -> ['a']
|
|
495
|
+
'a\\nb' -> ['a', 'b']
|
|
496
|
+
'a\\tb' -> ['a b']
|
|
497
|
+
'' -> ['']
|
|
498
|
+
123 -> [123]
|
|
499
|
+
None -> [None]
|
|
500
|
+
|
|
501
|
+
Tabs are expanded here, because a tab has no width of its own -- it
|
|
502
|
+
means "advance to the next stop", and where that stop is depends on a
|
|
503
|
+
console this string may never reach. Left alone it measured as nothing
|
|
504
|
+
and then displaced the border by however far the terminal moved the
|
|
505
|
+
cursor. Expanding against the start of the cell gives a width that is
|
|
506
|
+
the same everywhere the table is read.
|
|
507
|
+
"""
|
|
508
|
+
# Only if the cell is a string.
|
|
509
|
+
if isinstance(cell, str):
|
|
510
|
+
if cell != '':
|
|
511
|
+
# If the cell is not empty,
|
|
512
|
+
# apply the str method splitlines
|
|
513
|
+
return cell.expandtabs(TAB_SIZE).splitlines()
|
|
514
|
+
else:
|
|
515
|
+
# Return a list with empty string in case of empty cell.
|
|
516
|
+
return ['']
|
|
517
|
+
else:
|
|
518
|
+
# If the cell is not a string, return it as it is.
|
|
519
|
+
return [cell]
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
if __name__ == '__main__':
|
|
523
|
+
print('This shouldn\'t be printing.')
|