flexablelib 2.0.0__tar.gz → 2.0.3__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.
- {flexablelib-2.0.0 → flexablelib-2.0.3}/CHANGELOG.md +80 -61
- {flexablelib-2.0.0/flexablelib.egg-info → flexablelib-2.0.3}/PKG-INFO +11 -24
- {flexablelib-2.0.0 → flexablelib-2.0.3}/README.md +8 -21
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/cells.py +3 -2
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/columns.py +17 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/merges.py +26 -9
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/readers.py +37 -6
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/table.py +17 -3
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/writers.py +22 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3/flexablelib.egg-info}/PKG-INFO +11 -24
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib.egg-info/SOURCES.txt +1 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/package.json +5 -5
- {flexablelib-2.0.0 → flexablelib-2.0.3}/setup.py +2 -2
- flexablelib-2.0.3/tests/test_excel.py +110 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_features.py +40 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_merges.py +59 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/LICENSE +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/MANIFEST.in +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/__init__.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/_speedups.c +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/_width_tables.h +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/colors.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/fast.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/options.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/style_compositions.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/table_strings.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/text_width.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib/utils.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib.egg-info/dependency_links.txt +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib.egg-info/requires.txt +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/flexablelib.egg-info/top_level.txt +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/setup.cfg +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_cells.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_columns.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_issues.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_readme_examples.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_styles.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_text_width.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_two_pass_measurement.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tests/test_upstream_issues.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tools/benchmark.py +0 -0
- {flexablelib-2.0.0 → flexablelib-2.0.3}/tools/generate_width_tables.py +0 -0
|
@@ -2,7 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
## [2.0.
|
|
5
|
+
## [2.0.3](https://github.com/Opus-Perpetuus/flexablelib/compare/v2.0.2...v2.0.3) (2026-10-28)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* ``to_excel`` / ``from_excel`` preserve merged cell ranges (openpyxl)
|
|
10
|
+
|
|
11
|
+
## [2.0.2](https://github.com/Opus-Perpetuus/flexablelib/compare/v2.0.1...v2.0.2) (2026-10-28)
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* redraw vertical-merge vertices as edges/tees instead of leftover corners
|
|
16
|
+
|
|
17
|
+
## [2.0.1](https://github.com/Opus-Perpetuus/flexablelib/compare/v2.0.0...v2.0.1) (2026-10-28)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* `missing_val` is now an alias of `missing_value` (docs and constructor kwarg name no longer silently no-op)
|
|
23
|
+
|
|
24
|
+
## [2.0.0](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.4.1...v2.0.0) (2026-10-28)
|
|
6
25
|
|
|
7
26
|
|
|
8
27
|
### ⚠ BREAKING CHANGES
|
|
@@ -19,7 +38,7 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
19
38
|
|
|
20
39
|
* colour merge tests pin `use_colors=True` so CI without a TTY still asserts ANSI codes
|
|
21
40
|
|
|
22
|
-
## [1.4.1](https://github.com/Opus-Perpetuus/
|
|
41
|
+
## [1.4.1](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.4.0...v1.4.1) (2026-08-03)
|
|
23
42
|
|
|
24
43
|
|
|
25
44
|
### Bug Fixes
|
|
@@ -27,7 +46,7 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
27
46
|
* `column_max_width` now wraps or trims cell content to the cap (was only
|
|
28
47
|
changing the measured width number, so long cells still overflowed)
|
|
29
48
|
|
|
30
|
-
## [1.4.0](https://github.com/Opus-Perpetuus/
|
|
49
|
+
## [1.4.0](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.3.0...v1.4.0) (2026-08-03)
|
|
31
50
|
|
|
32
51
|
|
|
33
52
|
### Features
|
|
@@ -45,9 +64,9 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
45
64
|
|
|
46
65
|
### Bug Fixes
|
|
47
66
|
|
|
48
|
-
* float columns keep decimal alignment when the table is shrunk ([#23](https://github.com/Opus-Perpetuus/
|
|
49
|
-
* alignment options are actually applied; `bool_align` no longer wrote the float slot ([#4](https://github.com/Opus-Perpetuus/
|
|
50
|
-
* empty rows and columns share one emptiness criterion ([#9](https://github.com/Opus-Perpetuus/
|
|
67
|
+
* float columns keep decimal alignment when the table is shrunk ([#23](https://github.com/Opus-Perpetuus/flexablelib/issues/23))
|
|
68
|
+
* alignment options are actually applied; `bool_align` no longer wrote the float slot ([#4](https://github.com/Opus-Perpetuus/flexablelib/issues/4))
|
|
69
|
+
* empty rows and columns share one emptiness criterion ([#9](https://github.com/Opus-Perpetuus/flexablelib/issues/9))
|
|
51
70
|
* column named `"i"` no longer collides with the index column
|
|
52
71
|
* exponential numbers align with the float column
|
|
53
72
|
* tabs, emoji VS16, empty header-only tables, markdown alignment colons, and other upstream regressions
|
|
@@ -60,117 +79,117 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
60
79
|
* `docs/ISSUES.md` maps every tracker issue to its fix and tests
|
|
61
80
|
* README Known Issues and ARCHITECTURE Known gaps brought in line with current behaviour
|
|
62
81
|
|
|
63
|
-
## [1.3.0](https://github.com/Opus-Perpetuus/
|
|
82
|
+
## [1.3.0](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.2.0...v1.3.0) (2026-08-03)
|
|
64
83
|
|
|
65
84
|
|
|
66
85
|
### Features
|
|
67
86
|
|
|
68
|
-
* merge cells across columns, down rows, or both, via `merge_cells()` ([39f6e56](https://github.com/Opus-Perpetuus/
|
|
87
|
+
* merge cells across columns, down rows, or both, via `merge_cells()` ([39f6e56](https://github.com/Opus-Perpetuus/flexablelib/commit/39f6e56))
|
|
69
88
|
|
|
70
89
|
|
|
71
90
|
### Performance Improvements
|
|
72
91
|
|
|
73
|
-
* skip the redundant second measuring pass when the table already fits, and cut per-cell helper overhead; 2000x4 renders in 16.9ms against 30.6ms, making this the fastest of flexablelib, prettytable, tabulate and pandas .to_string() on the same data ([88b01f0](https://github.com/Opus-Perpetuus/
|
|
92
|
+
* skip the redundant second measuring pass when the table already fits, and cut per-cell helper overhead; 2000x4 renders in 16.9ms against 30.6ms, making this the fastest of flexablelib, prettytable, tabulate and pandas .to_string() on the same data ([88b01f0](https://github.com/Opus-Perpetuus/flexablelib/commit/88b01f0))
|
|
74
93
|
|
|
75
94
|
|
|
76
95
|
### Bug Fixes
|
|
77
96
|
|
|
78
|
-
* the index column no longer continues counting across renders; it is reset explicitly rather than by a side effect of deep-copying the table ([88b01f0](https://github.com/Opus-Perpetuus/
|
|
79
|
-
* `is_some_instance` returned None instead of False when nothing matched ([88b01f0](https://github.com/Opus-Perpetuus/
|
|
97
|
+
* the index column no longer continues counting across renders; it is reset explicitly rather than by a side effect of deep-copying the table ([88b01f0](https://github.com/Opus-Perpetuus/flexablelib/commit/88b01f0))
|
|
98
|
+
* `is_some_instance` returned None instead of False when nothing matched ([88b01f0](https://github.com/Opus-Perpetuus/flexablelib/commit/88b01f0))
|
|
80
99
|
|
|
81
|
-
## [1.2.0](https://github.com/Opus-Perpetuus/
|
|
100
|
+
## [1.2.0](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.1.5...v1.2.0) (2026-08-03)
|
|
82
101
|
|
|
83
102
|
|
|
84
103
|
### Features
|
|
85
104
|
|
|
86
|
-
* ANSI colour for headers, borders, columns, rows and individual cells, with NO_COLOR and TTY detection ([f7ea1c9](https://github.com/Opus-Perpetuus/
|
|
87
|
-
* read from CSV, HTML, dicts, pandas and Excel; write CSV, Markdown, self-contained paginated HTML and Excel ([c54fa06](https://github.com/Opus-Perpetuus/
|
|
88
|
-
* parse numeric strings via `parse_str_numbers`, absorbing celulartable's type parsing ([ce5362b](https://github.com/Opus-Perpetuus/
|
|
89
|
-
* `max_width` and `too_narrow_message` for tables that cannot fit ([f8309b8](https://github.com/Opus-Perpetuus/
|
|
90
|
-
* `Table.missing` sentinel for building tables with gaps ([c54fa06](https://github.com/Opus-Perpetuus/
|
|
105
|
+
* ANSI colour for headers, borders, columns, rows and individual cells, with NO_COLOR and TTY detection ([f7ea1c9](https://github.com/Opus-Perpetuus/flexablelib/commit/f7ea1c9))
|
|
106
|
+
* read from CSV, HTML, dicts, pandas and Excel; write CSV, Markdown, self-contained paginated HTML and Excel ([c54fa06](https://github.com/Opus-Perpetuus/flexablelib/commit/c54fa06))
|
|
107
|
+
* parse numeric strings via `parse_str_numbers`, absorbing celulartable's type parsing ([ce5362b](https://github.com/Opus-Perpetuus/flexablelib/commit/ce5362b))
|
|
108
|
+
* `max_width` and `too_narrow_message` for tables that cannot fit ([f8309b8](https://github.com/Opus-Perpetuus/flexablelib/commit/f8309b8))
|
|
109
|
+
* `Table.missing` sentinel for building tables with gaps ([c54fa06](https://github.com/Opus-Perpetuus/flexablelib/commit/c54fa06))
|
|
91
110
|
|
|
92
111
|
|
|
93
112
|
### Bug Fixes
|
|
94
113
|
|
|
95
|
-
* measure and pad cells by visible width, fixing alignment for CJK, Hangul and emoji ([7a350d2](https://github.com/Opus-Perpetuus/
|
|
96
|
-
* exclude hidden rows from column width measurement ([#22](https://github.com/Opus-Perpetuus/
|
|
97
|
-
* shrink the widest column first instead of every column in proportion ([#16](https://github.com/Opus-Perpetuus/
|
|
98
|
-
* never hand a column a width budget of zero, which raised from textwrap ([#14](https://github.com/Opus-Perpetuus/
|
|
99
|
-
* with auto_wrap off, the trimmed table now fits the terminal (Known Issue #2) ([842f64e](https://github.com/Opus-Perpetuus/
|
|
114
|
+
* measure and pad cells by visible width, fixing alignment for CJK, Hangul and emoji ([7a350d2](https://github.com/Opus-Perpetuus/flexablelib/commit/7a350d2))
|
|
115
|
+
* exclude hidden rows from column width measurement ([#22](https://github.com/Opus-Perpetuus/flexablelib/issues/22)) ([e480f4e](https://github.com/Opus-Perpetuus/flexablelib/commit/e480f4e))
|
|
116
|
+
* shrink the widest column first instead of every column in proportion ([#16](https://github.com/Opus-Perpetuus/flexablelib/issues/16)) ([842f64e](https://github.com/Opus-Perpetuus/flexablelib/commit/842f64e))
|
|
117
|
+
* never hand a column a width budget of zero, which raised from textwrap ([#14](https://github.com/Opus-Perpetuus/flexablelib/issues/14)) ([842f64e](https://github.com/Opus-Perpetuus/flexablelib/commit/842f64e))
|
|
118
|
+
* with auto_wrap off, the trimmed table now fits the terminal (Known Issue #2) ([842f64e](https://github.com/Opus-Perpetuus/flexablelib/commit/842f64e))
|
|
100
119
|
|
|
101
120
|
|
|
102
121
|
### Performance Improvements
|
|
103
122
|
|
|
104
|
-
* C extension for text measurement, 17-34x on the hot path, with a pure-Python fallback ([23981d4](https://github.com/Opus-Perpetuus/
|
|
123
|
+
* C extension for text measurement, 17-34x on the hot path, with a pure-Python fallback ([23981d4](https://github.com/Opus-Perpetuus/flexablelib/commit/23981d4))
|
|
105
124
|
|
|
106
|
-
### [1.1.5](https://github.com/Opus-Perpetuus/
|
|
125
|
+
### [1.1.5](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.1.4...v1.1.5) (2022-07-11)
|
|
107
126
|
|
|
108
127
|
|
|
109
128
|
### Bug Fixes
|
|
110
129
|
|
|
111
|
-
* Wasn't possible to add empty rows. ([7a94a3f](https://github.com/Opus-Perpetuus/
|
|
130
|
+
* Wasn't possible to add empty rows. ([7a94a3f](https://github.com/Opus-Perpetuus/flexablelib/commit/7a94a3f94438ebdab16d21dd80d4c1bc66be872e))
|
|
112
131
|
|
|
113
|
-
### [1.1.4](https://github.com/Opus-Perpetuus/
|
|
132
|
+
### [1.1.4](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.1.3...v1.1.4) (2022-07-11)
|
|
114
133
|
|
|
115
134
|
|
|
116
135
|
### Bug Fixes
|
|
117
136
|
|
|
118
|
-
* Accidental import. ([47e798a](https://github.com/Opus-Perpetuus/
|
|
119
|
-
* Auto trimming fixed. ([92e3a66](https://github.com/Opus-Perpetuus/
|
|
120
|
-
* Auto-wrapping fixed. ([4b8d631](https://github.com/Opus-Perpetuus/
|
|
121
|
-
* Autwrapping with index shown fixed. ([cbcb70c](https://github.com/Opus-Perpetuus/
|
|
122
|
-
* Columns not showing wen passing less headers in class args. ([1559fe4](https://github.com/Opus-Perpetuus/
|
|
123
|
-
* Passing extra headers causes an IndexError. ([e5a3e42](https://github.com/Opus-Perpetuus/
|
|
124
|
-
* Using rows and headers arguments gives error. ([0479cd5](https://github.com/Opus-Perpetuus/
|
|
137
|
+
* Accidental import. ([47e798a](https://github.com/Opus-Perpetuus/flexablelib/commit/47e798a9019b918b47ace50d5fe5683864e5e886))
|
|
138
|
+
* Auto trimming fixed. ([92e3a66](https://github.com/Opus-Perpetuus/flexablelib/commit/92e3a668fb931da0225b375d03c72f2ce9e357d0))
|
|
139
|
+
* Auto-wrapping fixed. ([4b8d631](https://github.com/Opus-Perpetuus/flexablelib/commit/4b8d631dc9f7dbe65053e25b854dc61c7d6fbb64))
|
|
140
|
+
* Autwrapping with index shown fixed. ([cbcb70c](https://github.com/Opus-Perpetuus/flexablelib/commit/cbcb70c8a08c5572553a3b46cad822cc316d5025))
|
|
141
|
+
* Columns not showing wen passing less headers in class args. ([1559fe4](https://github.com/Opus-Perpetuus/flexablelib/commit/1559fe41f347130e44f07c67cdd464ca23f0cf68))
|
|
142
|
+
* Passing extra headers causes an IndexError. ([e5a3e42](https://github.com/Opus-Perpetuus/flexablelib/commit/e5a3e427cf226d6eb4e58fb658d5068be1700edd))
|
|
143
|
+
* Using rows and headers arguments gives error. ([0479cd5](https://github.com/Opus-Perpetuus/flexablelib/commit/0479cd569396db5a08c9b51be7816b53e0e0f8f9))
|
|
125
144
|
|
|
126
|
-
### [1.1.3](https://github.com/Opus-Perpetuus/
|
|
145
|
+
### [1.1.3](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.1.2...v1.1.3) (2022-05-07)
|
|
127
146
|
|
|
128
147
|
|
|
129
148
|
### Bug Fixes
|
|
130
149
|
|
|
131
|
-
* more accidental imports ([25e4c88](https://github.com/Opus-Perpetuus/
|
|
150
|
+
* more accidental imports ([25e4c88](https://github.com/Opus-Perpetuus/flexablelib/commit/25e4c88e0feab8f154547ae159d0d7d8fff0750b))
|
|
132
151
|
|
|
133
|
-
### [1.1.2](https://github.com/Opus-Perpetuus/
|
|
152
|
+
### [1.1.2](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.1.1...v1.1.2) (2022-05-07)
|
|
134
153
|
|
|
135
154
|
|
|
136
155
|
### Bug Fixes
|
|
137
156
|
|
|
138
|
-
* accidental import. ([736ab4d](https://github.com/Opus-Perpetuus/
|
|
157
|
+
* accidental import. ([736ab4d](https://github.com/Opus-Perpetuus/flexablelib/commit/736ab4dc281cb766f9726512e3505a5bb17fc0cc))
|
|
139
158
|
|
|
140
|
-
### [1.1.1](https://github.com/Opus-Perpetuus/
|
|
159
|
+
### [1.1.1](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.1.0...v1.1.1) (2022-05-07)
|
|
141
160
|
|
|
142
161
|
|
|
143
162
|
### Bug Fixes
|
|
144
163
|
|
|
145
|
-
* adding column of different size causing misplaced missing values. ([66abc50](https://github.com/Opus-Perpetuus/
|
|
146
|
-
* unintended info printing on console. ([560af0a](https://github.com/Opus-Perpetuus/
|
|
164
|
+
* adding column of different size causing misplaced missing values. ([66abc50](https://github.com/Opus-Perpetuus/flexablelib/commit/66abc5082488c734ffd9329be09f9dc6ea86683b))
|
|
165
|
+
* unintended info printing on console. ([560af0a](https://github.com/Opus-Perpetuus/flexablelib/commit/560af0aa8016ab71ad11f9f7ce29375543c192a9))
|
|
147
166
|
|
|
148
|
-
## [1.1.0](https://github.com/Opus-Perpetuus/
|
|
167
|
+
## [1.1.0](https://github.com/Opus-Perpetuus/flexablelib/compare/v1.0.0...v1.1.0) (2022-05-07)
|
|
149
168
|
|
|
150
169
|
|
|
151
170
|
### Features
|
|
152
171
|
|
|
153
|
-
* added auto-wrapping for string columns. ([0cacdef](https://github.com/Opus-Perpetuus/
|
|
154
|
-
* added float parsing and alignment ([1a65acd](https://github.com/Opus-Perpetuus/
|
|
155
|
-
* added show_index option ([8b8d328](https://github.com/Opus-Perpetuus/
|
|
156
|
-
* auto-wrap depends on the auto_wrap option. ([a1e09e5](https://github.com/Opus-Perpetuus/
|
|
157
|
-
* column data trimming added. ([f53503b](https://github.com/Opus-Perpetuus/
|
|
158
|
-
* show_empty_columns, show_empty_rows options ([a7e4288](https://github.com/Opus-Perpetuus/
|
|
172
|
+
* added auto-wrapping for string columns. ([0cacdef](https://github.com/Opus-Perpetuus/flexablelib/commit/0cacdef34c738d0d611440ab393f474f37858ae3))
|
|
173
|
+
* added float parsing and alignment ([1a65acd](https://github.com/Opus-Perpetuus/flexablelib/commit/1a65acd7a3fc7deb7331de4bed20a9d276d1a87d))
|
|
174
|
+
* added show_index option ([8b8d328](https://github.com/Opus-Perpetuus/flexablelib/commit/8b8d328af591a7d0edd288d80af029837f300f81))
|
|
175
|
+
* auto-wrap depends on the auto_wrap option. ([a1e09e5](https://github.com/Opus-Perpetuus/flexablelib/commit/a1e09e50ec913343b5b48b679cf08128ba74a383))
|
|
176
|
+
* column data trimming added. ([f53503b](https://github.com/Opus-Perpetuus/flexablelib/commit/f53503bc0b1bf3bd342286ba5cb16354ab8ee4ab))
|
|
177
|
+
* show_empty_columns, show_empty_rows options ([a7e4288](https://github.com/Opus-Perpetuus/flexablelib/commit/a7e428845254d68368e9bce6a7fbb4a59fe4046f))
|
|
159
178
|
|
|
160
179
|
|
|
161
180
|
### Bug Fixes
|
|
162
181
|
|
|
163
|
-
* add_column omitting columns_with_i ([4cc900d](https://github.com/Opus-Perpetuus/
|
|
164
|
-
* auto-wrapping doesn't wrap columns "equally". ([9ba68ab](https://github.com/Opus-Perpetuus/
|
|
165
|
-
* empty column and row hiding working bad ([76979e9](https://github.com/Opus-Perpetuus/
|
|
166
|
-
* float columns weren't considering header size ([f8ffce3](https://github.com/Opus-Perpetuus/
|
|
167
|
-
* header wrapping wasn't working ([09b5136](https://github.com/Opus-Perpetuus/
|
|
168
|
-
* hiding empty r/c when showing i behaving bad ([823776a](https://github.com/Opus-Perpetuus/
|
|
169
|
-
* index displays incorrectly when hiding empty rows ([6054dda](https://github.com/Opus-Perpetuus/
|
|
170
|
-
* large missing val. in float col. causes bad alignment ([565d58c](https://github.com/Opus-Perpetuus/
|
|
171
|
-
* missing value was a normal value and was affecting alignment ([a01d4fa](https://github.com/Opus-Perpetuus/
|
|
172
|
-
* read_json not closing json ([6702f53](https://github.com/Opus-Perpetuus/
|
|
173
|
-
* table with index wasn't auto-wrapping ([23457a5](https://github.com/Opus-Perpetuus/
|
|
174
|
-
* when displaying index and adding rows table data messes up. ([3c0a724](https://github.com/Opus-Perpetuus/
|
|
175
|
-
* when showing i table data displays incomplete. ([b33ed42](https://github.com/Opus-Perpetuus/
|
|
176
|
-
* wrapping wasn't working ([cf63647](https://github.com/Opus-Perpetuus/
|
|
182
|
+
* add_column omitting columns_with_i ([4cc900d](https://github.com/Opus-Perpetuus/flexablelib/commit/4cc900d25c494fbc2dcf4b58a7c7794c42d22b81))
|
|
183
|
+
* auto-wrapping doesn't wrap columns "equally". ([9ba68ab](https://github.com/Opus-Perpetuus/flexablelib/commit/9ba68aba7c8c7494c33682368921802bc5167436))
|
|
184
|
+
* empty column and row hiding working bad ([76979e9](https://github.com/Opus-Perpetuus/flexablelib/commit/76979e9b803491e6335cdcca8f7417f99abf8542))
|
|
185
|
+
* float columns weren't considering header size ([f8ffce3](https://github.com/Opus-Perpetuus/flexablelib/commit/f8ffce38a2b97d409ca7ba37677280d8ff329c68))
|
|
186
|
+
* header wrapping wasn't working ([09b5136](https://github.com/Opus-Perpetuus/flexablelib/commit/09b513653f89dc61331301c2be5b1506366d0fdc))
|
|
187
|
+
* hiding empty r/c when showing i behaving bad ([823776a](https://github.com/Opus-Perpetuus/flexablelib/commit/823776ae5b1ca9b9210a8cd8337847a79fd5625f))
|
|
188
|
+
* index displays incorrectly when hiding empty rows ([6054dda](https://github.com/Opus-Perpetuus/flexablelib/commit/6054ddae743df58907802c1467f8993d937c7df7))
|
|
189
|
+
* large missing val. in float col. causes bad alignment ([565d58c](https://github.com/Opus-Perpetuus/flexablelib/commit/565d58c9db550d63d634c1f49c972a7c8beb7b8f))
|
|
190
|
+
* missing value was a normal value and was affecting alignment ([a01d4fa](https://github.com/Opus-Perpetuus/flexablelib/commit/a01d4fa1b7dcc03f3ba78f31b9984ba7a7f4d334))
|
|
191
|
+
* read_json not closing json ([6702f53](https://github.com/Opus-Perpetuus/flexablelib/commit/6702f5330f11d1f2a70b811e68f9a26447a065f6))
|
|
192
|
+
* table with index wasn't auto-wrapping ([23457a5](https://github.com/Opus-Perpetuus/flexablelib/commit/23457a542733a40931f875db76f2a62939ffa93e))
|
|
193
|
+
* when displaying index and adding rows table data messes up. ([3c0a724](https://github.com/Opus-Perpetuus/flexablelib/commit/3c0a7245dd09597ac46028bf31a03d37014c0ccc))
|
|
194
|
+
* when showing i table data displays incomplete. ([b33ed42](https://github.com/Opus-Perpetuus/flexablelib/commit/b33ed42d064b1875e70357dedc1b2005ba396379))
|
|
195
|
+
* wrapping wasn't working ([cf63647](https://github.com/Opus-Perpetuus/flexablelib/commit/cf63647a741dec0867cb702d75d16c3b0301d3e1))
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flexablelib
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary: Flexible
|
|
5
|
-
Home-page: https://github.com/Opus-Perpetuus/
|
|
3
|
+
Version: 2.0.3
|
|
4
|
+
Summary: Flexible console and HTML tables for Python
|
|
5
|
+
Home-page: https://github.com/Opus-Perpetuus/flexablelib
|
|
6
6
|
Download-URL:
|
|
7
7
|
Author: Benjamin Ramirez
|
|
8
8
|
Author-email: chilerito12@gmail.com
|
|
@@ -52,7 +52,7 @@ Dynamic: summary
|
|
|
52
52
|
</p>
|
|
53
53
|
|
|
54
54
|
<p align="center">
|
|
55
|
-
<em>Flexible Table Library —
|
|
55
|
+
<em>Flexible Table Library — console and HTML tables for Python.</em>
|
|
56
56
|
</p>
|
|
57
57
|
|
|
58
58
|
<p align="center">
|
|
@@ -69,15 +69,7 @@ Dynamic: summary
|
|
|
69
69
|
|
|
70
70
|
Flexible Table Library
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
The idea started as an attempt to reproduce the behavior of the [PM2](https://pm2.keymetrics.io) package when it displays tables to show data. Later, heavy inspiration came
|
|
75
|
-
of two other python packages:
|
|
76
|
-
|
|
77
|
-
- [Jazzband's](https://github.com/jazzband) [prettytable](https://github.com/jazzband/prettytable) (The names are similar by accident). Uses a class too.
|
|
78
|
-
- [Astanin's](https://github.com/astanin) [tabulate](https://github.com/astanin/python-tabulate). A very simple to use and efficient package.
|
|
79
|
-
|
|
80
|
-
A big part of the behavior of this package was replicated from these.
|
|
72
|
+
Python package for formatting tabular data as console and HTML tables: border styles, cell merges, ANSI colors, per-type and decimal alignment, width fitting, I/O helpers, and optional C-accelerated text measurement.
|
|
81
73
|
|
|
82
74
|
# Installation
|
|
83
75
|
``pip install flexablelib`` on Windows.
|
|
@@ -326,23 +318,18 @@ from flexablelib.fast import implementation
|
|
|
326
318
|
print(implementation()) # 'C extension' or 'pure Python'
|
|
327
319
|
```
|
|
328
320
|
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
None of the three historical known issues remain:
|
|
321
|
+
# Notes
|
|
332
322
|
|
|
333
|
-
- A column named ``"i"``
|
|
334
|
-
|
|
335
|
-
-
|
|
336
|
-
the float column.
|
|
337
|
-
- Shrinking a float column drops decimals instead of chopping characters, so
|
|
338
|
-
decimal alignment survives a terminal fit ([#23](https://github.com/Opus-Perpetuus/prettyTables/issues/23)).
|
|
323
|
+
- A column named ``"i"`` does not collide with the index column (`utils.IndexColumnTitle`).
|
|
324
|
+
- Exponential numbers align on the decimal / exponent axis with the rest of the float column.
|
|
325
|
+
- Shrinking a float column drops decimals instead of chopping characters, so decimal alignment survives a terminal fit ([#23](https://github.com/Opus-Perpetuus/flexablelib/issues/23)).
|
|
339
326
|
|
|
340
|
-
Open work and the
|
|
327
|
+
Open work and the issue map: [docs/ISSUES.md](docs/ISSUES.md).
|
|
341
328
|
|
|
342
329
|
# Project Layout
|
|
343
330
|
|
|
344
331
|
```
|
|
345
|
-
|
|
332
|
+
flexablelib/ # GitHub repository
|
|
346
333
|
├── flexablelib/ the Python package (no runtime dependencies)
|
|
347
334
|
│ ├── __init__.py public API: Table, TableComposition, SeparatorLine
|
|
348
335
|
│ ├── table.py the Table class — state and render orchestration
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<em>Flexible Table Library —
|
|
6
|
+
<em>Flexible Table Library — console and HTML tables for Python.</em>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
@@ -20,15 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
Flexible Table Library
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
The idea started as an attempt to reproduce the behavior of the [PM2](https://pm2.keymetrics.io) package when it displays tables to show data. Later, heavy inspiration came
|
|
26
|
-
of two other python packages:
|
|
27
|
-
|
|
28
|
-
- [Jazzband's](https://github.com/jazzband) [prettytable](https://github.com/jazzband/prettytable) (The names are similar by accident). Uses a class too.
|
|
29
|
-
- [Astanin's](https://github.com/astanin) [tabulate](https://github.com/astanin/python-tabulate). A very simple to use and efficient package.
|
|
30
|
-
|
|
31
|
-
A big part of the behavior of this package was replicated from these.
|
|
23
|
+
Python package for formatting tabular data as console and HTML tables: border styles, cell merges, ANSI colors, per-type and decimal alignment, width fitting, I/O helpers, and optional C-accelerated text measurement.
|
|
32
24
|
|
|
33
25
|
# Installation
|
|
34
26
|
``pip install flexablelib`` on Windows.
|
|
@@ -277,23 +269,18 @@ from flexablelib.fast import implementation
|
|
|
277
269
|
print(implementation()) # 'C extension' or 'pure Python'
|
|
278
270
|
```
|
|
279
271
|
|
|
280
|
-
#
|
|
281
|
-
|
|
282
|
-
None of the three historical known issues remain:
|
|
272
|
+
# Notes
|
|
283
273
|
|
|
284
|
-
- A column named ``"i"``
|
|
285
|
-
|
|
286
|
-
-
|
|
287
|
-
the float column.
|
|
288
|
-
- Shrinking a float column drops decimals instead of chopping characters, so
|
|
289
|
-
decimal alignment survives a terminal fit ([#23](https://github.com/Opus-Perpetuus/prettyTables/issues/23)).
|
|
274
|
+
- A column named ``"i"`` does not collide with the index column (`utils.IndexColumnTitle`).
|
|
275
|
+
- Exponential numbers align on the decimal / exponent axis with the rest of the float column.
|
|
276
|
+
- Shrinking a float column drops decimals instead of chopping characters, so decimal alignment survives a terminal fit ([#23](https://github.com/Opus-Perpetuus/flexablelib/issues/23)).
|
|
290
277
|
|
|
291
|
-
Open work and the
|
|
278
|
+
Open work and the issue map: [docs/ISSUES.md](docs/ISSUES.md).
|
|
292
279
|
|
|
293
280
|
# Project Layout
|
|
294
281
|
|
|
295
282
|
```
|
|
296
|
-
|
|
283
|
+
flexablelib/ # GitHub repository
|
|
297
284
|
├── flexablelib/ the Python package (no runtime dependencies)
|
|
298
285
|
│ ├── __init__.py public API: Table, TableComposition, SeparatorLine
|
|
299
286
|
│ ├── table.py the Table class — state and render orchestration
|
|
@@ -44,7 +44,7 @@ def _add_cell_spacing(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
|
44
44
|
if is_some_instance(cell, tuple, list):
|
|
45
45
|
# If it is, add the spacing to each part of the cell.
|
|
46
46
|
spaced = list(map(str, cell)) # Convert to strings to prevent errors.
|
|
47
|
-
for part_i, cell_part in enumerate(
|
|
47
|
+
for part_i, cell_part in enumerate(spaced):
|
|
48
48
|
# Add the spacing to the cell part.
|
|
49
49
|
# FIX should consider the cell_part length
|
|
50
50
|
if placeholder_size != 0 and part_i != 0:
|
|
@@ -54,10 +54,11 @@ def _add_cell_spacing(cell: Union[str, Union[List[str], Tuple[str]]],
|
|
|
54
54
|
else:
|
|
55
55
|
# If it is not, add the spacing to the cell.
|
|
56
56
|
# FIX should consider the cell length
|
|
57
|
+
cell_text = str(cell)
|
|
57
58
|
if placeholder_size != 0:
|
|
58
59
|
spaced = ''.join([left, diff, right])
|
|
59
60
|
else:
|
|
60
|
-
spaced = ''.join([left,
|
|
61
|
+
spaced = ''.join([left, cell_text, right])
|
|
61
62
|
|
|
62
63
|
return spaced
|
|
63
64
|
|
|
@@ -725,6 +725,23 @@ def __align_single_cell(cell: Union[Any, List[Any], Tuple[Any]],
|
|
|
725
725
|
float_column_sizes,
|
|
726
726
|
DEFAULT_FILL_CHAR,
|
|
727
727
|
)
|
|
728
|
+
elif to_where == COLUMN_ALIGNS.bytes:
|
|
729
|
+
# Bytes alignment is left-justified (see INDICATOR_RELATIONS). Without
|
|
730
|
+
# this branch the raw cell reaches _add_cell_spacing and TypeErrors on
|
|
731
|
+
# non-str values when callers set *_align / col_alignment to 'b'.
|
|
732
|
+
new_cell = _ljust_cell(
|
|
733
|
+
cell,
|
|
734
|
+
col_width,
|
|
735
|
+
DEFAULT_FILL_CHAR,
|
|
736
|
+
)
|
|
737
|
+
else:
|
|
738
|
+
# Unknown alignment code: still stringify and left-pad so compose()
|
|
739
|
+
# never blows up on a typo or a future alignment name.
|
|
740
|
+
new_cell = _ljust_cell(
|
|
741
|
+
cell,
|
|
742
|
+
col_width,
|
|
743
|
+
DEFAULT_FILL_CHAR,
|
|
744
|
+
)
|
|
728
745
|
# Add the margin (if any) to the cell.
|
|
729
746
|
new_cell = _add_cell_spacing(new_cell, margin, margin, 0)
|
|
730
747
|
|
|
@@ -204,7 +204,12 @@ def _region_bounds(region, layout, index_offset):
|
|
|
204
204
|
return None
|
|
205
205
|
|
|
206
206
|
first, last = drawn[0], drawn[-1]
|
|
207
|
-
return (
|
|
207
|
+
return (
|
|
208
|
+
(layout.spans[first][0], layout.spans[last][1]),
|
|
209
|
+
layout.spans[first],
|
|
210
|
+
first,
|
|
211
|
+
last,
|
|
212
|
+
)
|
|
208
213
|
|
|
209
214
|
|
|
210
215
|
def apply(lines, row_line_map, regions, column_widths, composition,
|
|
@@ -235,7 +240,7 @@ def apply(lines, row_line_map, regions, column_widths, composition,
|
|
|
235
240
|
bounds = _region_bounds(region, layout, index_offset)
|
|
236
241
|
if bounds is None:
|
|
237
242
|
continue
|
|
238
|
-
(start, end), (cell_start, cell_end), last_span = bounds
|
|
243
|
+
(start, end), (cell_start, cell_end), first_span, last_span = bounds
|
|
239
244
|
span_width = end - start - margin * 2
|
|
240
245
|
if span_width < 1:
|
|
241
246
|
continue
|
|
@@ -277,13 +282,16 @@ def apply(lines, row_line_map, regions, column_widths, composition,
|
|
|
277
282
|
# Paint over the separator lines that fall inside the block, so the
|
|
278
283
|
# merged region reads as one cell top to bottom.
|
|
279
284
|
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
285
|
+
# Blanking the horizontal run removes the arms that fed each vertex.
|
|
286
|
+
# Rebuild those vertices from the style's own characters:
|
|
287
|
+
# - right edge, more columns: body left ('├' where '┼' was)
|
|
288
|
+
# - right edge, table border: vertical right ('│' where '┤' was)
|
|
289
|
+
# - left edge, prior columns: body right ('┤' where '┼' was)
|
|
290
|
+
# - left edge, table border: vertical left ('│' where '├' was)
|
|
284
291
|
body_rule = composition.table_body_line
|
|
285
|
-
|
|
292
|
+
v_lines = composition.vertical_table_body_lines
|
|
286
293
|
joins_more_columns = last_span + 1 < len(layout.spans)
|
|
294
|
+
joins_prev_columns = first_span > 0
|
|
287
295
|
|
|
288
296
|
covered = set(target_lines)
|
|
289
297
|
for line_index in range(min(target_lines), max(target_lines)):
|
|
@@ -293,8 +301,17 @@ def apply(lines, row_line_map, regions, column_widths, composition,
|
|
|
293
301
|
if visible_width(line) < end:
|
|
294
302
|
continue
|
|
295
303
|
rewritten = _repaint(line, start, end, blank)
|
|
296
|
-
if
|
|
297
|
-
|
|
304
|
+
if body_rule is not None or v_lines is not None:
|
|
305
|
+
if visible_width(rewritten) > end:
|
|
306
|
+
if joins_more_columns and body_rule is not None and body_rule.left:
|
|
307
|
+
rewritten = _repaint(rewritten, end, end + 1, body_rule.left)
|
|
308
|
+
elif not joins_more_columns and v_lines is not None and v_lines.right:
|
|
309
|
+
rewritten = _repaint(rewritten, end, end + 1, v_lines.right)
|
|
310
|
+
if start > 0:
|
|
311
|
+
if joins_prev_columns and body_rule is not None and body_rule.right:
|
|
312
|
+
rewritten = _repaint(rewritten, start - 1, start, body_rule.right)
|
|
313
|
+
elif not joins_prev_columns and v_lines is not None and v_lines.left:
|
|
314
|
+
rewritten = _repaint(rewritten, start - 1, start, v_lines.left)
|
|
298
315
|
lines[line_index] = rewritten
|
|
299
316
|
|
|
300
317
|
return lines
|
|
@@ -344,7 +344,8 @@ def from_excel(table_class, path, sheet=None, has_header: bool = True,
|
|
|
344
344
|
'Install it with: pip install flexablelib[excel]'
|
|
345
345
|
)
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
# read_only hides merged cell ranges; we need them to rebuild merges.
|
|
348
|
+
workbook = load_workbook(filename=path, read_only=False, data_only=True)
|
|
348
349
|
try:
|
|
349
350
|
if sheet is None:
|
|
350
351
|
worksheet = workbook.active
|
|
@@ -357,14 +358,44 @@ def from_excel(table_class, path, sheet=None, has_header: bool = True,
|
|
|
357
358
|
['' if cell is None else cell for cell in row]
|
|
358
359
|
for row in worksheet.iter_rows(values_only=True)
|
|
359
360
|
]
|
|
361
|
+
merge_ranges = [
|
|
362
|
+
(merged.min_row, merged.min_col, merged.max_row, merged.max_col)
|
|
363
|
+
for merged in worksheet.merged_cells.ranges
|
|
364
|
+
]
|
|
360
365
|
finally:
|
|
361
366
|
workbook.close()
|
|
362
367
|
|
|
363
368
|
if not rows:
|
|
364
369
|
return table_class(**options)
|
|
365
370
|
if has_header:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
+
table = _new_table(table_class, rows[0], rows[1:], parse_numbers,
|
|
372
|
+
**options)
|
|
373
|
+
header_rows = 1
|
|
374
|
+
else:
|
|
375
|
+
width = max(len(row) for row in rows)
|
|
376
|
+
headers = [f'column {i + 1}' for i in range(width)]
|
|
377
|
+
table = _new_table(table_class, headers, rows, parse_numbers, **options)
|
|
378
|
+
header_rows = 0
|
|
379
|
+
|
|
380
|
+
# Excel rows/cols are 1-based. Body rows start after the header row when
|
|
381
|
+
# has_header is set. Skip merges that only cover the header.
|
|
382
|
+
for min_row, min_col, max_row, max_col in merge_ranges:
|
|
383
|
+
# Header-only or header-spanning merges are not table body merges.
|
|
384
|
+
if min_row <= header_rows:
|
|
385
|
+
continue
|
|
386
|
+
first_row = min_row - header_rows - 1
|
|
387
|
+
last_row = max_row - header_rows - 1
|
|
388
|
+
first_column = min_col - 1
|
|
389
|
+
last_column = max_col - 1
|
|
390
|
+
if first_row == last_row and first_column == last_column:
|
|
391
|
+
continue
|
|
392
|
+
if last_row < 0 or last_column < 0:
|
|
393
|
+
continue
|
|
394
|
+
if last_row >= table.row_count or last_column >= table.column_count:
|
|
395
|
+
continue
|
|
396
|
+
try:
|
|
397
|
+
table.merge_cells(first_row, first_column, last_row, last_column)
|
|
398
|
+
except ValueError:
|
|
399
|
+
# Overlaps or single cells from odd sheets: skip rather than fail.
|
|
400
|
+
continue
|
|
401
|
+
return table
|
|
@@ -1155,13 +1155,21 @@ class Table(object):
|
|
|
1155
1155
|
def missing_value(self) -> Any:
|
|
1156
1156
|
"""
|
|
1157
1157
|
A placeholder for empty cells.
|
|
1158
|
-
|
|
1158
|
+
|
|
1159
1159
|
Default is::
|
|
1160
|
-
|
|
1160
|
+
|
|
1161
1161
|
''
|
|
1162
|
+
|
|
1163
|
+
``missing_val`` is an alias of this attribute (same name as the
|
|
1164
|
+
``Table(..., missing_val=...)`` constructor argument).
|
|
1162
1165
|
"""
|
|
1163
1166
|
return self.__missing_value
|
|
1164
1167
|
|
|
1168
|
+
@property
|
|
1169
|
+
def missing_val(self) -> Any:
|
|
1170
|
+
"""Alias of :attr:`missing_value`."""
|
|
1171
|
+
return self.__missing_value
|
|
1172
|
+
|
|
1165
1173
|
@property
|
|
1166
1174
|
def str_align(self):
|
|
1167
1175
|
"""
|
|
@@ -1523,6 +1531,10 @@ class Table(object):
|
|
|
1523
1531
|
def missing_value(self, value):
|
|
1524
1532
|
self.__missing_value = value
|
|
1525
1533
|
|
|
1534
|
+
@missing_val.setter
|
|
1535
|
+
def missing_val(self, value):
|
|
1536
|
+
self.__missing_value = value
|
|
1537
|
+
|
|
1526
1538
|
@str_align.setter
|
|
1527
1539
|
def str_align(self, value):
|
|
1528
1540
|
self.__str_align = self.__checked_alignment(value, 'str_align')
|
|
@@ -4115,6 +4127,7 @@ class Table(object):
|
|
|
4115
4127
|
"""
|
|
4116
4128
|
Build a table from a worksheet in an .xlsx file.
|
|
4117
4129
|
|
|
4130
|
+
Excel merged ranges are restored as ``merge_cells`` regions.
|
|
4118
4131
|
Requires openpyxl: pip install flexablelib[excel]
|
|
4119
4132
|
"""
|
|
4120
4133
|
from .readers import from_excel
|
|
@@ -4183,7 +4196,8 @@ class Table(object):
|
|
|
4183
4196
|
Write an .xlsx workbook.
|
|
4184
4197
|
|
|
4185
4198
|
Numbers are written as numbers so the spreadsheet can compute with
|
|
4186
|
-
them. Requires openpyxl:
|
|
4199
|
+
them. ``merged_regions`` become real Excel merges. Requires openpyxl:
|
|
4200
|
+
pip install flexablelib[excel]
|
|
4187
4201
|
"""
|
|
4188
4202
|
from .writers import to_excel
|
|
4189
4203
|
return to_excel(self, path, sheet_name, include_index, autofit,
|
|
@@ -557,4 +557,26 @@ def to_excel(table, path, sheet_name: str = 'Sheet1',
|
|
|
557
557
|
if freeze_header:
|
|
558
558
|
worksheet.freeze_panes = 'A2'
|
|
559
559
|
|
|
560
|
+
# Mirror flexablelib merged regions as Excel merged cells. Table coordinates
|
|
561
|
+
# ignore the optional index column and the header row lives on Excel row 1.
|
|
562
|
+
col_offset = 2 if include_index else 1
|
|
563
|
+
for region in getattr(table, 'merged_regions', []):
|
|
564
|
+
start_row = region.first_row + 2
|
|
565
|
+
end_row = region.last_row + 2
|
|
566
|
+
start_col = region.first_column + col_offset
|
|
567
|
+
end_col = region.last_column + col_offset
|
|
568
|
+
top_left = worksheet.cell(row=start_row, column=start_col)
|
|
569
|
+
if region.value is not None:
|
|
570
|
+
top_left.value = _plain(region.value)
|
|
571
|
+
top_left.alignment = Alignment(
|
|
572
|
+
horizontal={'l': 'left', 'r': 'right', 'c': 'center'}.get(
|
|
573
|
+
region.align, 'center'),
|
|
574
|
+
vertical='center',
|
|
575
|
+
wrap_text=True,
|
|
576
|
+
)
|
|
577
|
+
worksheet.merge_cells(
|
|
578
|
+
start_row=start_row, start_column=start_col,
|
|
579
|
+
end_row=end_row, end_column=end_col,
|
|
580
|
+
)
|
|
581
|
+
|
|
560
582
|
workbook.save(path)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flexablelib
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary: Flexible
|
|
5
|
-
Home-page: https://github.com/Opus-Perpetuus/
|
|
3
|
+
Version: 2.0.3
|
|
4
|
+
Summary: Flexible console and HTML tables for Python
|
|
5
|
+
Home-page: https://github.com/Opus-Perpetuus/flexablelib
|
|
6
6
|
Download-URL:
|
|
7
7
|
Author: Benjamin Ramirez
|
|
8
8
|
Author-email: chilerito12@gmail.com
|
|
@@ -52,7 +52,7 @@ Dynamic: summary
|
|
|
52
52
|
</p>
|
|
53
53
|
|
|
54
54
|
<p align="center">
|
|
55
|
-
<em>Flexible Table Library —
|
|
55
|
+
<em>Flexible Table Library — console and HTML tables for Python.</em>
|
|
56
56
|
</p>
|
|
57
57
|
|
|
58
58
|
<p align="center">
|
|
@@ -69,15 +69,7 @@ Dynamic: summary
|
|
|
69
69
|
|
|
70
70
|
Flexible Table Library
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
The idea started as an attempt to reproduce the behavior of the [PM2](https://pm2.keymetrics.io) package when it displays tables to show data. Later, heavy inspiration came
|
|
75
|
-
of two other python packages:
|
|
76
|
-
|
|
77
|
-
- [Jazzband's](https://github.com/jazzband) [prettytable](https://github.com/jazzband/prettytable) (The names are similar by accident). Uses a class too.
|
|
78
|
-
- [Astanin's](https://github.com/astanin) [tabulate](https://github.com/astanin/python-tabulate). A very simple to use and efficient package.
|
|
79
|
-
|
|
80
|
-
A big part of the behavior of this package was replicated from these.
|
|
72
|
+
Python package for formatting tabular data as console and HTML tables: border styles, cell merges, ANSI colors, per-type and decimal alignment, width fitting, I/O helpers, and optional C-accelerated text measurement.
|
|
81
73
|
|
|
82
74
|
# Installation
|
|
83
75
|
``pip install flexablelib`` on Windows.
|
|
@@ -326,23 +318,18 @@ from flexablelib.fast import implementation
|
|
|
326
318
|
print(implementation()) # 'C extension' or 'pure Python'
|
|
327
319
|
```
|
|
328
320
|
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
None of the three historical known issues remain:
|
|
321
|
+
# Notes
|
|
332
322
|
|
|
333
|
-
- A column named ``"i"``
|
|
334
|
-
|
|
335
|
-
-
|
|
336
|
-
the float column.
|
|
337
|
-
- Shrinking a float column drops decimals instead of chopping characters, so
|
|
338
|
-
decimal alignment survives a terminal fit ([#23](https://github.com/Opus-Perpetuus/prettyTables/issues/23)).
|
|
323
|
+
- A column named ``"i"`` does not collide with the index column (`utils.IndexColumnTitle`).
|
|
324
|
+
- Exponential numbers align on the decimal / exponent axis with the rest of the float column.
|
|
325
|
+
- Shrinking a float column drops decimals instead of chopping characters, so decimal alignment survives a terminal fit ([#23](https://github.com/Opus-Perpetuus/flexablelib/issues/23)).
|
|
339
326
|
|
|
340
|
-
Open work and the
|
|
327
|
+
Open work and the issue map: [docs/ISSUES.md](docs/ISSUES.md).
|
|
341
328
|
|
|
342
329
|
# Project Layout
|
|
343
330
|
|
|
344
331
|
```
|
|
345
|
-
|
|
332
|
+
flexablelib/ # GitHub repository
|
|
346
333
|
├── flexablelib/ the Python package (no runtime dependencies)
|
|
347
334
|
│ ├── __init__.py public API: Table, TableComposition, SeparatorLine
|
|
348
335
|
│ ├── table.py the Table class — state and render orchestration
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flexablelib",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Flexible
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"description": "Flexible console and HTML tables for Python: styles, merges, colors, widths, decimal alignment.",
|
|
5
5
|
"main": "__init__.py",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/Opus-Perpetuus/
|
|
15
|
+
"url": "git+https://github.com/Opus-Perpetuus/flexablelib.git"
|
|
16
16
|
},
|
|
17
17
|
"author": "Kyostenas",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"bugs": {
|
|
20
|
-
"url": "https://github.com/Opus-Perpetuus/
|
|
20
|
+
"url": "https://github.com/Opus-Perpetuus/flexablelib/issues"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://github.com/Opus-Perpetuus/
|
|
22
|
+
"homepage": "https://github.com/Opus-Perpetuus/flexablelib#readme",
|
|
23
23
|
"config": {
|
|
24
24
|
"commitzen": {
|
|
25
25
|
"path": "cz-conventional-changelog"
|
|
@@ -18,12 +18,12 @@ setup(
|
|
|
18
18
|
name = 'flexablelib',
|
|
19
19
|
packages = ['flexablelib'],
|
|
20
20
|
version = read_json(f'.{os.sep}package.json')['version'],
|
|
21
|
-
description = 'Flexible
|
|
21
|
+
description = 'Flexible console and HTML tables for Python',
|
|
22
22
|
long_description=read_file('README.md'),
|
|
23
23
|
long_description_content_type='text/markdown',
|
|
24
24
|
author = 'Benjamin Ramirez',
|
|
25
25
|
author_email = 'chilerito12@gmail.com',
|
|
26
|
-
url = 'https://github.com/Opus-Perpetuus/
|
|
26
|
+
url = 'https://github.com/Opus-Perpetuus/flexablelib',
|
|
27
27
|
license='MIT',
|
|
28
28
|
download_url = '',
|
|
29
29
|
keywords = ['console', 'graphics'],
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Excel import / export.
|
|
3
|
+
|
|
4
|
+
Round-trips plain tables and merged regions through openpyxl workbooks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from flexablelib import Table
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
pytest.importorskip('openpyxl')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_table():
|
|
18
|
+
table = Table()
|
|
19
|
+
table.add_column('Region', ['Norte', 'Norte', 'Sur', 'Sur'])
|
|
20
|
+
table.add_column('Month', ['Jan', 'Feb', 'Jan', 'Feb'])
|
|
21
|
+
table.add_column('Sales', [120.5, 98.25, 210.0, 175.75])
|
|
22
|
+
return table
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_to_excel_and_from_excel_round_trip_values(tmp_path):
|
|
26
|
+
path = tmp_path / 'plain.xlsx'
|
|
27
|
+
table = build_table()
|
|
28
|
+
table.to_excel(path)
|
|
29
|
+
|
|
30
|
+
loaded = Table.from_excel(path)
|
|
31
|
+
|
|
32
|
+
assert loaded.headers == ['Region', 'Month', 'Sales']
|
|
33
|
+
assert loaded.rows[0][0] == 'Norte'
|
|
34
|
+
assert loaded.rows[0][2] == 120.5
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_to_excel_writes_merged_cells(tmp_path):
|
|
38
|
+
from openpyxl import load_workbook
|
|
39
|
+
|
|
40
|
+
path = tmp_path / 'merged.xlsx'
|
|
41
|
+
table = build_table()
|
|
42
|
+
table.merge_cells(0, 0, last_row=1, value='Norte')
|
|
43
|
+
table.merge_cells(0, 1, last_column=2, value='Q1')
|
|
44
|
+
table.to_excel(path)
|
|
45
|
+
|
|
46
|
+
workbook = load_workbook(path)
|
|
47
|
+
ranges = {str(r) for r in workbook.active.merged_cells.ranges}
|
|
48
|
+
workbook.close()
|
|
49
|
+
|
|
50
|
+
assert 'A2:A3' in ranges
|
|
51
|
+
assert 'B2:C2' in ranges
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_from_excel_restores_merged_regions(tmp_path):
|
|
55
|
+
path = tmp_path / 'round.xlsx'
|
|
56
|
+
table = build_table()
|
|
57
|
+
table.merge_cells(0, 0, last_row=1, value='Norte')
|
|
58
|
+
table.merge_cells(2, 0, last_column=1, value='Sur block')
|
|
59
|
+
table.to_excel(path)
|
|
60
|
+
|
|
61
|
+
loaded = Table.from_excel(path)
|
|
62
|
+
|
|
63
|
+
assert len(loaded.merged_regions) == 2
|
|
64
|
+
regions = {(r.first_row, r.first_column, r.last_row, r.last_column)
|
|
65
|
+
for r in loaded.merged_regions}
|
|
66
|
+
assert (0, 0, 1, 0) in regions
|
|
67
|
+
assert (2, 0, 2, 1) in regions
|
|
68
|
+
assert 'Norte' in str(loaded)
|
|
69
|
+
assert 'Sur block' in str(loaded)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_to_excel_shifts_merges_when_index_included(tmp_path):
|
|
73
|
+
from openpyxl import load_workbook
|
|
74
|
+
|
|
75
|
+
path = tmp_path / 'indexed.xlsx'
|
|
76
|
+
table = build_table()
|
|
77
|
+
table.show_index = True
|
|
78
|
+
table.merge_cells(0, 0, last_row=1, value='Norte')
|
|
79
|
+
table.to_excel(path, include_index=True)
|
|
80
|
+
|
|
81
|
+
workbook = load_workbook(path)
|
|
82
|
+
ranges = {str(r) for r in workbook.active.merged_cells.ranges}
|
|
83
|
+
workbook.close()
|
|
84
|
+
|
|
85
|
+
# Index column is A; body Region is B → B2:B3
|
|
86
|
+
assert 'B2:B3' in ranges
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_from_excel_sheet_selection(tmp_path):
|
|
90
|
+
from openpyxl import Workbook
|
|
91
|
+
|
|
92
|
+
path = tmp_path / 'multi.xlsx'
|
|
93
|
+
workbook = Workbook()
|
|
94
|
+
first = workbook.active
|
|
95
|
+
first.title = 'One'
|
|
96
|
+
first.append(['A'])
|
|
97
|
+
first.append([1])
|
|
98
|
+
second = workbook.create_sheet('Two')
|
|
99
|
+
second.append(['X', 'Y'])
|
|
100
|
+
second.append(['x1', 'y1'])
|
|
101
|
+
second.append(['x2', 'y2'])
|
|
102
|
+
second.merge_cells('A2:A3')
|
|
103
|
+
workbook.save(path)
|
|
104
|
+
workbook.close()
|
|
105
|
+
|
|
106
|
+
loaded = Table.from_excel(path, sheet='Two')
|
|
107
|
+
assert loaded.headers == ['X', 'Y']
|
|
108
|
+
assert len(loaded.merged_regions) == 1
|
|
109
|
+
region = loaded.merged_regions[0]
|
|
110
|
+
assert (region.first_row, region.first_column, region.last_row, region.last_column) == (0, 0, 1, 0)
|
|
@@ -216,3 +216,43 @@ def test_the_package_can_be_star_imported():
|
|
|
216
216
|
assert 'Table' in namespace
|
|
217
217
|
assert 'TableComposition' in namespace
|
|
218
218
|
assert 'SeparatorLine' in namespace
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_bytes_alignment_code_renders_without_typeerror():
|
|
222
|
+
"""``COLUMN_ALIGNS.bytes`` ('b') must justify like left, not crash.
|
|
223
|
+
|
|
224
|
+
Forcing ``*_align`` / ``col_alignment`` to ``'b'`` used to skip every
|
|
225
|
+
justify branch in ``__align_single_cell``, so the raw int/bytes cell hit
|
|
226
|
+
``_add_cell_spacing`` and raised ``TypeError``.
|
|
227
|
+
"""
|
|
228
|
+
table = Table()
|
|
229
|
+
table.add_column('Name', ['Jade', 'John'])
|
|
230
|
+
table.add_column('Age', [20, 30])
|
|
231
|
+
table.add_column('Blob', [b'abc', b'xy'])
|
|
232
|
+
table.col_alignment = 'b'
|
|
233
|
+
rendered = str(table)
|
|
234
|
+
assert 'Jade' in rendered
|
|
235
|
+
assert '20' in rendered
|
|
236
|
+
assert "b'abc'" in rendered or 'abc' in rendered
|
|
237
|
+
|
|
238
|
+
only_bytes = Table()
|
|
239
|
+
only_bytes.add_column('Blob', [b'hello', b'world'])
|
|
240
|
+
only_bytes.col_alignment = 'b'
|
|
241
|
+
assert 'hello' in str(only_bytes)
|
|
242
|
+
|
|
243
|
+
ints = Table()
|
|
244
|
+
ints.add_column('n', [1, 2, 3])
|
|
245
|
+
ints.int_align = 'b'
|
|
246
|
+
assert '1' in str(ints)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_missing_val_alias_matches_missing_value():
|
|
250
|
+
table = Table()
|
|
251
|
+
table.add_column('a', ['x'])
|
|
252
|
+
table.add_row([])
|
|
253
|
+
table.missing_val = 'n/a'
|
|
254
|
+
assert table.missing_value == 'n/a'
|
|
255
|
+
assert 'n/a' in str(table)
|
|
256
|
+
table.missing_value = '?'
|
|
257
|
+
assert table.missing_val == '?'
|
|
258
|
+
assert '?' in str(table)
|
|
@@ -51,6 +51,65 @@ def test_a_vertical_merge_removes_the_rules_between_its_rows():
|
|
|
51
51
|
assert rendered.count('Norte') == 1
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
def test_vertical_merge_turns_left_border_into_an_edge():
|
|
56
|
+
"""Vertices on a body rule inside a vertical merge must become edges.
|
|
57
|
+
|
|
58
|
+
The horizontal arms into the merged cell are gone, so a left-border
|
|
59
|
+
'├' becomes the vertical edge '│', and the right junction keeps the
|
|
60
|
+
three-way '├' that opens the remaining columns.
|
|
61
|
+
"""
|
|
62
|
+
table = build_table()
|
|
63
|
+
table.merge_cells(0, 0, last_row=1, value='Norte')
|
|
64
|
+
|
|
65
|
+
sep = next(
|
|
66
|
+
line for line in str(table).splitlines()
|
|
67
|
+
if '├───────┼────────┤' in line or line.startswith('│ ├')
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
assert sep.startswith('│')
|
|
71
|
+
assert '├───────┼────────┤' in sep
|
|
72
|
+
assert not sep.startswith('├')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_vertical_merge_in_middle_column_uses_left_and_right_tees():
|
|
76
|
+
table = build_table()
|
|
77
|
+
table.merge_cells(0, 1, last_row=1, value='M')
|
|
78
|
+
|
|
79
|
+
sep = next(line for line in str(table).splitlines() if ' ' in line and '┤' in line and line.count('├') >= 1)
|
|
80
|
+
|
|
81
|
+
assert '├────────┤ ├────────┤' in sep
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_vertical_merge_on_last_column_turns_right_border_into_an_edge():
|
|
85
|
+
table = build_table()
|
|
86
|
+
table.merge_cells(0, 2, last_row=1, value='S')
|
|
87
|
+
|
|
88
|
+
sep = next(line for line in str(table).splitlines() if line.endswith('│') and '┤ │' in line)
|
|
89
|
+
|
|
90
|
+
assert sep.endswith('│')
|
|
91
|
+
assert '├────────┼───────┤ │' in sep
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_vertical_merge_vertices_follow_grid_ascii_style():
|
|
95
|
+
table = build_table('grid')
|
|
96
|
+
table.merge_cells(0, 0, last_row=1, value='Norte')
|
|
97
|
+
|
|
98
|
+
sep = next(line for line in str(table).splitlines() if '+-------+' in line and line.startswith('|'))
|
|
99
|
+
|
|
100
|
+
assert sep.startswith('| +')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_vertical_merge_vertices_follow_bold_borderline_style():
|
|
104
|
+
table = build_table('bold_borderline')
|
|
105
|
+
table.merge_cells(0, 0, last_row=1, value='Norte')
|
|
106
|
+
|
|
107
|
+
sep = next(line for line in str(table).splitlines() if '╟───────┼────────╢' in line)
|
|
108
|
+
|
|
109
|
+
assert sep.startswith('║')
|
|
110
|
+
assert '║ ╟───────┼────────╢' in sep
|
|
111
|
+
|
|
112
|
+
|
|
54
113
|
def test_a_block_merge_covers_both_directions():
|
|
55
114
|
table = build_table()
|
|
56
115
|
table.merge_cells(0, 0, 1, 1, value='Block')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|