cellstr 0.2.1__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.
- cellstr-0.2.1/.gitignore +16 -0
- cellstr-0.2.1/CHANGELOG.md +53 -0
- cellstr-0.2.1/CONTRIBUTING.md +28 -0
- cellstr-0.2.1/GUIDE.md +149 -0
- cellstr-0.2.1/LICENSE +21 -0
- cellstr-0.2.1/LICENSE-UNICODE +39 -0
- cellstr-0.2.1/PKG-INFO +144 -0
- cellstr-0.2.1/README.md +108 -0
- cellstr-0.2.1/data/ucd/MANIFEST.json +65 -0
- cellstr-0.2.1/pyproject.toml +80 -0
- cellstr-0.2.1/scripts/fetch_upstream.py +40 -0
- cellstr-0.2.1/scripts/gen_ucd_tables.py +202 -0
- cellstr-0.2.1/src/cellstr/__init__.py +41 -0
- cellstr-0.2.1/src/cellstr/__main__.py +8 -0
- cellstr-0.2.1/src/cellstr/_ansi.py +62 -0
- cellstr-0.2.1/src/cellstr/_cells.py +113 -0
- cellstr-0.2.1/src/cellstr/_cli.py +184 -0
- cellstr-0.2.1/src/cellstr/_graphemes.py +101 -0
- cellstr-0.2.1/src/cellstr/_ops.py +147 -0
- cellstr-0.2.1/src/cellstr/_props.py +61 -0
- cellstr-0.2.1/src/cellstr/_truncate.py +130 -0
- cellstr-0.2.1/src/cellstr/_ucd_data.py +31 -0
- cellstr-0.2.1/src/cellstr/_ucd_part1.py +166 -0
- cellstr-0.2.1/src/cellstr/_width.py +111 -0
- cellstr-0.2.1/src/cellstr/py.typed +0 -0
- cellstr-0.2.1/tests/conftest.py +28 -0
- cellstr-0.2.1/tests/test_ansi.py +66 -0
- cellstr-0.2.1/tests/test_cli.py +90 -0
- cellstr-0.2.1/tests/test_contract.py +173 -0
- cellstr-0.2.1/tests/test_docs_examples.py +32 -0
- cellstr-0.2.1/tests/test_doctests.py +24 -0
- cellstr-0.2.1/tests/test_e2e_wheel.py +69 -0
- cellstr-0.2.1/tests/test_edges.py +67 -0
- cellstr-0.2.1/tests/test_fuzz.py +131 -0
- cellstr-0.2.1/tests/test_grapheme_conformance.py +57 -0
- cellstr-0.2.1/tests/test_ops.py +126 -0
- cellstr-0.2.1/tests/test_oracle_wcwidth.py +140 -0
- cellstr-0.2.1/tests/test_truncate.py +89 -0
- cellstr-0.2.1/tests/test_ucd_tables.py +79 -0
- cellstr-0.2.1/tests/test_width.py +141 -0
- cellstr-0.2.1/tests/test_width_contract.py +78 -0
cellstr-0.2.1/.gitignore
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
venv/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
|
|
14
|
+
# Unicode data files, fetched locally by scripts/fetch_upstream.py; only the manifest is tracked.
|
|
15
|
+
data/ucd/*
|
|
16
|
+
!data/ucd/MANIFEST.json
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.1]
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The Unicode Character Database files are no longer committed. `data/ucd/MANIFEST.json`
|
|
14
|
+
pins them to Unicode 18.0.0, plus the 15.1.0 EastAsianWidth.txt the oracle test uses
|
|
15
|
+
(URL, size, sha256, licence), and `scripts/fetch_upstream.py`
|
|
16
|
+
fetches them; the generator check and the conformance tests skip until then. The
|
|
17
|
+
generated tables stay in the package and the Unicode licence notice ships in the wheel.
|
|
18
|
+
- The sdist ships only the sources, tests, scripts and docs.
|
|
19
|
+
|
|
20
|
+
## [0.2.0]
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Grapheme-aware width: emoji ZWJ sequences, skin tone and tag sequences, keycaps and
|
|
25
|
+
regional indicator flags measure as one two-cell glyph; VS16 makes an emoji wide and
|
|
26
|
+
VS15 makes it narrow. New `iter_graphemes()` (UAX #29) and `grapheme_width()`.
|
|
27
|
+
- `ambiguous=1 | 2` on every function for East Asian Ambiguous characters.
|
|
28
|
+
- `tabsize=` on every function, and `expand_tabs()` that counts display cells.
|
|
29
|
+
- `truncate(..., position="end" | "start" | "middle")`, `truncate_detailed()` returning
|
|
30
|
+
a `Truncation`, and `fit()` to truncate or pad to an exact width.
|
|
31
|
+
- `iter_cells()` yielding typed `Cell` records (text, width, column, escape flag).
|
|
32
|
+
- Recognition of DCS, SOS, PM and APC strings, charset designations and the remaining
|
|
33
|
+
single-byte escapes, alongside CSI and OSC (BEL or ST terminated).
|
|
34
|
+
- The `cellstr` command: `measure`, `truncate`, `pad`, `fit` and `slice` over stdin lines.
|
|
35
|
+
- Width, grapheme and emoji tables generated from a committed Unicode 18.0.0 UCD snapshot
|
|
36
|
+
by `scripts/gen_ucd_tables.py`, with the Unicode License in `LICENSE-UNICODE`.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- `cell_len`, `cell_slice`, `truncate` and the padding functions measure grapheme
|
|
41
|
+
clusters, so emoji sequences change width: the family emoji was 6 cells and is now 2,
|
|
42
|
+
a flag was 2 and stays 2, an emoji with VS15 is now 1.
|
|
43
|
+
- `truncate` rejects a suffix that contains a tab or a line break.
|
|
44
|
+
- Width tables move from a hand-vendored Unicode 15.1 extract to generated Unicode
|
|
45
|
+
18.0.0 tables. Spacing marks (Mc) are one cell, as in Kuhn's wcwidth and glibc.
|
|
46
|
+
- `cell_slice` never splits a grapheme cluster, so accents stay on their letter.
|
|
47
|
+
- `ljust`, `rjust` and `center` accept any one-cell grapheme cluster as the fill.
|
|
48
|
+
|
|
49
|
+
## [0.1.0]
|
|
50
|
+
|
|
51
|
+
- First release: `cell_len`, `char_width`, `cell_slice`, `ljust`, `rjust`,
|
|
52
|
+
`center`, `truncate` and `strip_ansi`, with a vendored Unicode width table and
|
|
53
|
+
full type hints.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for taking the time. This project keeps a small, predictable shape.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install -r requirements-dev.txt
|
|
9
|
+
python scripts/fetch_upstream.py
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The fetch step is optional: it downloads the Unicode data files listed in
|
|
13
|
+
`data/ucd/MANIFEST.json` (checked against their sha256) into `data/ucd/`, which
|
|
14
|
+
git ignores. The generator check and the tests that need them skip until they are there.
|
|
15
|
+
|
|
16
|
+
## Before you open a pull request
|
|
17
|
+
|
|
18
|
+
- Keep public API in `src/cellstr/__init__.py`; internals live in `_`-prefixed modules.
|
|
19
|
+
- Add a test for the behaviour you change. Tests inject time, disk and network; they
|
|
20
|
+
never touch the real thing.
|
|
21
|
+
- Never edit `src/cellstr/_ucd_*.py` by hand: they are generated from the fetched `data/ucd/` files
|
|
22
|
+
by `python scripts/gen_ucd_tables.py` (see `data/ucd/MANIFEST.json`).
|
|
23
|
+
- Run `scripts/check.ps1` (format, lint, types, table freshness, tests) and make sure it
|
|
24
|
+
passes. The wheel end-to-end test needs network access for the build backend;
|
|
25
|
+
offline, run `pytest -m "not e2e"`.
|
|
26
|
+
- One logical change per commit, imperative subject line.
|
|
27
|
+
|
|
28
|
+
Open an issue first if the change is large or shifts the public API.
|
cellstr-0.2.1/GUIDE.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# cellstr guide
|
|
2
|
+
|
|
3
|
+
This guide explains how `cellstr` decides how wide text is, where terminals disagree, and
|
|
4
|
+
how to pick the policy that matches yours.
|
|
5
|
+
|
|
6
|
+
## The rules
|
|
7
|
+
|
|
8
|
+
Width is decided per extended grapheme cluster (UAX #29), the unit a reader sees as one
|
|
9
|
+
character. Escape sequences are never part of a cluster: they count as zero cells, and an
|
|
10
|
+
escape sequence between two code points ends the cluster before it.
|
|
11
|
+
|
|
12
|
+
**Single code points** follow Markus Kuhn's reference `wcwidth`
|
|
13
|
+
(https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c), applied to Unicode 18.0.0 data:
|
|
14
|
+
|
|
15
|
+
| Code points | Cells |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| U+0000, C0 and C1 controls, DEL | 0 (Kuhn returns -1 for controls; 0 is easier to add up) |
|
|
18
|
+
| Nonspacing and enclosing marks (General_Category Mn, Me) | 0 |
|
|
19
|
+
| Format characters (Cf), such as ZERO WIDTH SPACE and ZERO WIDTH JOINER | 0 |
|
|
20
|
+
| SOFT HYPHEN U+00AD | 1, or `ambiguous` since it is East Asian Ambiguous (it is visible) |
|
|
21
|
+
| Hangul medial vowels and final consonants U+1160..U+11FF, U+D7B0..U+D7FF | 0 |
|
|
22
|
+
| East_Asian_Width W (wide) and F (fullwidth) | 2 |
|
|
23
|
+
| East_Asian_Width A (ambiguous) | `ambiguous`, 1 by default |
|
|
24
|
+
| Everything else, including spacing marks (Mc) | 1 |
|
|
25
|
+
|
|
26
|
+
Unassigned code points follow the defaults in the header of EastAsianWidth.txt: wide in
|
|
27
|
+
the CJK ideograph blocks and in Planes 2 and 3, narrow elsewhere.
|
|
28
|
+
|
|
29
|
+
**Clusters of several code points** add up their code points, which keeps accents at zero
|
|
30
|
+
and a Hangul syllable built from jamo at two, except for emoji sequences, which draw as a
|
|
31
|
+
single glyph:
|
|
32
|
+
|
|
33
|
+
| Cluster | Example | Cells |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| Emoji + VS16 (U+FE0F, emoji presentation) | ❤️ | 2 |
|
|
36
|
+
| Emoji + VS15 (U+FE0E, text presentation) | ⌚︎ | 1 |
|
|
37
|
+
| Emoji modifier sequence (skin tone) | 👍🏽 | 2 |
|
|
38
|
+
| Emoji ZWJ sequence | 👨👩👧 | 2 |
|
|
39
|
+
| Emoji tag sequence (subdivision flag) | 🏴 | 2 |
|
|
40
|
+
| Pair of regional indicators (flag) | 🇯🇵 | 2 |
|
|
41
|
+
| Keycap sequence | 1️⃣ | 2 |
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from cellstr import char_width, grapheme_width
|
|
45
|
+
|
|
46
|
+
char_width("❤") # -> 1
|
|
47
|
+
grapheme_width("❤️") # -> 2
|
|
48
|
+
grapheme_width("⌚︎") # -> 1
|
|
49
|
+
grapheme_width("\U0001f44d\U0001f3fd") # -> 2
|
|
50
|
+
grapheme_width("각") # -> 2
|
|
51
|
+
char_width("é"), char_width("́") # -> (1, 0)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Where terminals disagree
|
|
55
|
+
|
|
56
|
+
No standard says how many cells a character takes, so terminals make different choices.
|
|
57
|
+
These are the cases that matter in practice.
|
|
58
|
+
|
|
59
|
+
**East Asian Ambiguous characters.** Greek and Cyrillic letters, box drawing, circled
|
|
60
|
+
digits and many symbols are one cell in Western use and two in legacy CJK use. Most
|
|
61
|
+
terminals let the user choose: xterm has the `cjkWidth` resource, GNOME Terminal and
|
|
62
|
+
other VTE terminals have an "Ambiguous-width characters" preference, and iTerm2 can treat
|
|
63
|
+
ambiguous characters as double width. Pass `ambiguous=2` (or `--ambiguous 2` on the
|
|
64
|
+
command line) when your users run such a setting, typically with a CJK locale.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from cellstr import cell_len, ljust
|
|
68
|
+
|
|
69
|
+
cell_len("── α") # -> 4
|
|
70
|
+
cell_len("── α", ambiguous=2) # -> 7
|
|
71
|
+
ljust("ж", 3, ambiguous=2) # -> "ж "
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Emoji ZWJ sequences.** A terminal that measures code point by code point, as glibc's
|
|
75
|
+
`wcwidth` does, advances the cursor six cells for the family emoji (2 + 0 + 2 + 0 + 2)
|
|
76
|
+
and often draws three separate faces. Terminals that segment grapheme clusters draw one
|
|
77
|
+
glyph in two cells. `cellstr` measures two. If your
|
|
78
|
+
terminal works per code point, measure with the recipe below.
|
|
79
|
+
|
|
80
|
+
**Variation selectors.** VS16 asks for emoji presentation. Terminals that ignore it keep
|
|
81
|
+
a text-default symbol such as U+2764 at one cell and let the colour glyph overflow into
|
|
82
|
+
the next cell; `cellstr` measures two. VS15 asks for text presentation: `cellstr` measures
|
|
83
|
+
one cell, while terminals that ignore it, and the `wcwidth` package, keep the emoji at two.
|
|
84
|
+
|
|
85
|
+
**Spacing marks.** Vowel signs in Indic scripts such as U+093F are spacing marks (Mc).
|
|
86
|
+
Kuhn's reference gives them one cell (only Mn, Me and Cf are zero there), the `wcwidth`
|
|
87
|
+
package gives them zero. `cellstr` gives one, so कि is two cells here.
|
|
88
|
+
|
|
89
|
+
**Code point by code point recipe.** For a terminal without grapheme support, add up
|
|
90
|
+
single code points yourself:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from cellstr import char_width, strip_ansi
|
|
94
|
+
|
|
95
|
+
text = "\U0001f468\U0001f469\U0001f467"
|
|
96
|
+
sum(char_width(ch) for ch in strip_ansi(text)) # -> 6
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Slicing, padding and truncation
|
|
100
|
+
|
|
101
|
+
- `cell_slice(text, start, stop)` keeps the clusters that sit fully inside the half-open
|
|
102
|
+
column range. A wide cluster cut by either edge is dropped whole, so the result can be
|
|
103
|
+
one cell short; `fit()` pads that cell back. Every escape sequence is kept, so a
|
|
104
|
+
hyperlink or colour that opens before the range still closes after it.
|
|
105
|
+
- `ljust`, `rjust` and `center` pad with a one-cell fill character and never shorten text.
|
|
106
|
+
- `truncate(..., position=...)` cuts at the end (keep the start), at the start (keep the
|
|
107
|
+
end, useful for log tails) or in the middle (keep both ends, useful for paths).
|
|
108
|
+
`truncate_detailed` returns a `Truncation` with the result, its width, the original
|
|
109
|
+
width and whether anything was cut.
|
|
110
|
+
- With `tabsize` set, tabs advance to the next tab stop, counted from the start of the
|
|
111
|
+
line, and become spaces in slices and padded output so the layout does not shift.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from cellstr import cell_slice, fit, truncate
|
|
115
|
+
|
|
116
|
+
link = "\x1b]8;;https://example.com\x1b\\docs\x1b]8;;\x1b\\"
|
|
117
|
+
cell_slice(link, 0, 2) # -> "\x1b]8;;https://example.com\x1b\\do\x1b]8;;\x1b\\"
|
|
118
|
+
log = "12:00:01 worker-7 job finished"
|
|
119
|
+
truncate(log, 16, position="start") # -> "... job finished"
|
|
120
|
+
fit("\x1b[1mok\x1b[0m", 4, align="center") # -> " \x1b[1mok\x1b[0m "
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Differences from the wcwidth package
|
|
124
|
+
|
|
125
|
+
`tests/test_oracle_wcwidth.py` compares every code point with `wcwidth` 0.2.13 (whose
|
|
126
|
+
newest tables are Unicode 15.1.0). The package is only a test oracle, never a runtime
|
|
127
|
+
dependency. Result: 1,112,200 of 1,114,112 code points agree (99.83 %). Every one of the
|
|
128
|
+
1,912 differences has a recorded reason, and the test fails on any new one:
|
|
129
|
+
|
|
130
|
+
| Reason | Code points |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| Assigned after Unicode 15.1 | 1,263 |
|
|
133
|
+
| East_Asian_Width changed after 15.1 (for example the trigrams U+2630..U+2637 became W) | 188 |
|
|
134
|
+
| General_Category changed after 15.1 (U+1171E became Mc) | 1 |
|
|
135
|
+
| Spacing marks (Mc): 1 here, as in Kuhn's reference, 0 in `wcwidth` | 452 |
|
|
136
|
+
| SOFT HYPHEN, LINE SEPARATOR, PARAGRAPH SEPARATOR: 1 here (Kuhn), 0 in `wcwidth` | 3 |
|
|
137
|
+
| Lone skin tone modifier: 2 here (it is East Asian Wide), 0 in `wcwidth` | 5 |
|
|
138
|
+
|
|
139
|
+
For strings, `cellstr` and `wcswidth` agree on ZWJ sequences, flags, skin tones, VS16
|
|
140
|
+
and keycaps. They differ on VS15 and spacing marks, as described above.
|
|
141
|
+
|
|
142
|
+
## Unicode data
|
|
143
|
+
|
|
144
|
+
The tables are built from the Unicode 18.0.0 data files, which are not committed:
|
|
145
|
+
`data/ucd/MANIFEST.json` pins their URLs, sizes and SHA-256 sums, and
|
|
146
|
+
`python scripts/fetch_upstream.py` fetches them into `data/ucd/` (the tests that read them
|
|
147
|
+
skip until then). To move to a new Unicode version, update the manifest, fetch, run
|
|
148
|
+
`python scripts/gen_ucd_tables.py` and the test suite. `tests/test_ucd_tables.py` fails if
|
|
149
|
+
the committed tables differ from a fresh run of the generator.
|
cellstr-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Javier Lianes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
UNICODE LICENSE V3
|
|
2
|
+
|
|
3
|
+
COPYRIGHT AND PERMISSION NOTICE
|
|
4
|
+
|
|
5
|
+
Copyright © 1991-2026 Unicode, Inc.
|
|
6
|
+
|
|
7
|
+
NOTICE TO USER: Carefully read the following legal agreement. BY
|
|
8
|
+
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
|
9
|
+
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
|
10
|
+
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
|
11
|
+
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
14
|
+
copy of data files and any associated documentation (the "Data Files") or
|
|
15
|
+
software and any associated documentation (the "Software") to deal in the
|
|
16
|
+
Data Files or Software without restriction, including without limitation
|
|
17
|
+
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
|
18
|
+
copies of the Data Files or Software, and to permit persons to whom the
|
|
19
|
+
Data Files or Software are furnished to do so, provided that either (a)
|
|
20
|
+
this copyright and permission notice appear with all copies of the Data
|
|
21
|
+
Files or Software, or (b) this copyright and permission notice appear in
|
|
22
|
+
associated Documentation.
|
|
23
|
+
|
|
24
|
+
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
25
|
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
26
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
27
|
+
THIRD PARTY RIGHTS.
|
|
28
|
+
|
|
29
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
|
30
|
+
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
|
31
|
+
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
32
|
+
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
33
|
+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
|
34
|
+
FILES OR SOFTWARE.
|
|
35
|
+
|
|
36
|
+
Except as contained in this notice, the name of a copyright holder shall
|
|
37
|
+
not be used in advertising or otherwise to promote the sale, use or other
|
|
38
|
+
dealings in these Data Files or Software without prior written
|
|
39
|
+
authorization of the copyright holder.
|
cellstr-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cellstr
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Display-width aware len, slice, pad and truncate for terminal text: wide CJK, emoji sequences, combining marks and ANSI codes
|
|
5
|
+
Project-URL: Homepage, https://lianes8studio.com
|
|
6
|
+
Project-URL: Repository, https://github.com/JaviLianes8/cellstr
|
|
7
|
+
Project-URL: Issues, https://github.com/JaviLianes8/cellstr/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/JaviLianes8/cellstr/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: LinkedIn, https://www.linkedin.com/in/jlianes/
|
|
10
|
+
Project-URL: Funding, https://buymeacoffee.com/jlianesglrs
|
|
11
|
+
Author-email: Javier Lianes <javier@lianes8studio.com>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
License-File: LICENSE-UNICODE
|
|
15
|
+
Keywords: ansi,cli,east-asian-width,emoji,grapheme,terminal,truncate,unicode,wcwidth,width
|
|
16
|
+
Classifier: Development Status :: 4 - Beta
|
|
17
|
+
Classifier: Environment :: Console
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Topic :: Terminals
|
|
25
|
+
Classifier: Topic :: Text Processing
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
33
|
+
Requires-Dist: twine; extra == 'dev'
|
|
34
|
+
Requires-Dist: wcwidth==0.2.13; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# cellstr
|
|
38
|
+
|
|
39
|
+
Display-width aware `len`, slicing, padding and truncation for terminal text: East Asian
|
|
40
|
+
wide characters, emoji sequences, combining marks, zero-width characters and ANSI escape
|
|
41
|
+
sequences, measured the way a terminal draws them. Typed, zero dependencies, Unicode 18.0.0.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pip install cellstr
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
`len`, slicing and `str.ljust` count code points, so they misalign any text that carries
|
|
52
|
+
colour codes, CJK characters, emoji or accents. `cellstr` counts display cells.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from cellstr import cell_len, cell_slice, fit, ljust, truncate
|
|
56
|
+
|
|
57
|
+
warn = "\x1b[31mWARN\x1b[0m" # red "WARN"
|
|
58
|
+
cell_len(warn) # -> 4
|
|
59
|
+
cell_len("你好") # -> 4
|
|
60
|
+
cell_len("👨👩👧") # -> 2
|
|
61
|
+
cell_len("🇯🇵") # -> 2
|
|
62
|
+
cell_len("é") # -> 1
|
|
63
|
+
|
|
64
|
+
cell_slice(warn, 0, 2) # -> "\x1b[31mWA\x1b[0m"
|
|
65
|
+
cell_slice("a你b", 0, 2) # -> "a"
|
|
66
|
+
cell_slice("x👨👩👧y", 0, 2) # -> "x"
|
|
67
|
+
|
|
68
|
+
ljust("你", 4) # -> "你 "
|
|
69
|
+
truncate("hello world", 8) # -> "hello..."
|
|
70
|
+
truncate("/usr/local/lib/python", 12, position="middle") # -> "/usr/...thon"
|
|
71
|
+
fit("张伟张伟", 5, suffix="") # -> "张伟 "
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
What it gets right:
|
|
75
|
+
|
|
76
|
+
- **Grapheme clusters.** A ZWJ family, a skin tone sequence, a flag, a keycap or a letter
|
|
77
|
+
with accents is one unit: measured once and never split by slicing or truncation.
|
|
78
|
+
- **Escape sequences.** SGR colours, OSC 8 hyperlinks, cursor movement and the other
|
|
79
|
+
ECMA-48 families count as nothing and are kept in place, so styled text stays styled.
|
|
80
|
+
- **Ambiguous width.** Greek, Cyrillic, box drawing and circled digits are one cell in
|
|
81
|
+
Western terminals and two in CJK ones. Pass `ambiguous=2` to any function.
|
|
82
|
+
- **Tabs.** Pass `tabsize=8` to measure tab stops; slicing and padding turn tabs into the
|
|
83
|
+
spaces they cover. `expand_tabs()` does it on its own.
|
|
84
|
+
- **Real Unicode data.** Tables are generated by a committed script from a committed
|
|
85
|
+
Unicode Character Database snapshot, and the full grapheme break test suite runs.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from cellstr import cell_len, iter_cells, truncate_detailed
|
|
89
|
+
|
|
90
|
+
cell_len("αβ─", ambiguous=2) # -> 6
|
|
91
|
+
cell_len("a\tb", tabsize=8) # -> 9
|
|
92
|
+
[c.width for c in iter_cells("a你🇯🇵")] # -> [1, 2, 2]
|
|
93
|
+
truncate_detailed("hello world", 8).truncated # -> True
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Public API: `cell_len`, `char_width`, `grapheme_width`, `iter_graphemes`, `iter_cells`
|
|
97
|
+
(`Cell`), `cell_slice`, `ljust`, `rjust`, `center`, `fit`, `truncate`,
|
|
98
|
+
`truncate_detailed` (`Truncation`), `expand_tabs`, `strip_ansi`, `UNICODE_VERSION`, and
|
|
99
|
+
the `Align` and `Position` literal types. Every function is fully typed and documented.
|
|
100
|
+
|
|
101
|
+
## Command line
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
$ printf 'abc\n你好\n' | cellstr measure
|
|
105
|
+
3
|
|
106
|
+
4
|
|
107
|
+
$ printf '/usr/local/lib/python3/site-packages\n' | cellstr truncate 16 --position middle
|
|
108
|
+
/usr/lo...ckages
|
|
109
|
+
$ printf 'id\n12345\n' | cellstr pad 8 --align right
|
|
110
|
+
id
|
|
111
|
+
12345
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Commands: `measure` (`--max`), `truncate N`, `pad N`, `fit N` and `slice START [STOP]`,
|
|
115
|
+
all with `--ambiguous` and `--tabsize`. `cellstr --help` lists runnable examples.
|
|
116
|
+
|
|
117
|
+
## Terminals disagree
|
|
118
|
+
|
|
119
|
+
There is no standard for how many cells a character takes, and terminals differ on
|
|
120
|
+
ambiguous-width characters, emoji ZWJ sequences and variation selectors. `cellstr`
|
|
121
|
+
follows Markus Kuhn's reference `wcwidth` for single code points and
|
|
122
|
+
measures emoji sequences as one glyph, as terminals with grapheme support draw them.
|
|
123
|
+
[GUIDE.md](GUIDE.md) has the exact rules, concrete examples of where terminals differ, a
|
|
124
|
+
recipe for code point by code point measuring, and every difference from the `wcwidth`
|
|
125
|
+
package.
|
|
126
|
+
|
|
127
|
+
## Why
|
|
128
|
+
|
|
129
|
+
`wcwidth` measures plain-text width well, but it does not give you a typed, cohesive set
|
|
130
|
+
of string operations: slicing by column that keeps escapes and graphemes intact, padding,
|
|
131
|
+
fitting a table cell and width-aware truncation with a suffix at either end or in the
|
|
132
|
+
middle. `strip-ansi` style tools remove escapes but know nothing about width. `cellstr`
|
|
133
|
+
does all of it in one small, fully typed, zero-dependency package.
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT. See [LICENSE](LICENSE). The property tables in `src/cellstr/_ucd_*.py` are generated
|
|
138
|
+
from the Unicode Character Database and are covered by the Unicode License v3, see [LICENSE-UNICODE](LICENSE-UNICODE).
|
|
139
|
+
|
|
140
|
+
## Links
|
|
141
|
+
|
|
142
|
+
- Website: https://lianes8studio.com
|
|
143
|
+
- GitHub: https://github.com/JaviLianes8
|
|
144
|
+
- LinkedIn: https://www.linkedin.com/in/jlianes/
|
cellstr-0.2.1/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# cellstr
|
|
2
|
+
|
|
3
|
+
Display-width aware `len`, slicing, padding and truncation for terminal text: East Asian
|
|
4
|
+
wide characters, emoji sequences, combining marks, zero-width characters and ANSI escape
|
|
5
|
+
sequences, measured the way a terminal draws them. Typed, zero dependencies, Unicode 18.0.0.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install cellstr
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
`len`, slicing and `str.ljust` count code points, so they misalign any text that carries
|
|
16
|
+
colour codes, CJK characters, emoji or accents. `cellstr` counts display cells.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from cellstr import cell_len, cell_slice, fit, ljust, truncate
|
|
20
|
+
|
|
21
|
+
warn = "\x1b[31mWARN\x1b[0m" # red "WARN"
|
|
22
|
+
cell_len(warn) # -> 4
|
|
23
|
+
cell_len("你好") # -> 4
|
|
24
|
+
cell_len("👨👩👧") # -> 2
|
|
25
|
+
cell_len("🇯🇵") # -> 2
|
|
26
|
+
cell_len("é") # -> 1
|
|
27
|
+
|
|
28
|
+
cell_slice(warn, 0, 2) # -> "\x1b[31mWA\x1b[0m"
|
|
29
|
+
cell_slice("a你b", 0, 2) # -> "a"
|
|
30
|
+
cell_slice("x👨👩👧y", 0, 2) # -> "x"
|
|
31
|
+
|
|
32
|
+
ljust("你", 4) # -> "你 "
|
|
33
|
+
truncate("hello world", 8) # -> "hello..."
|
|
34
|
+
truncate("/usr/local/lib/python", 12, position="middle") # -> "/usr/...thon"
|
|
35
|
+
fit("张伟张伟", 5, suffix="") # -> "张伟 "
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
What it gets right:
|
|
39
|
+
|
|
40
|
+
- **Grapheme clusters.** A ZWJ family, a skin tone sequence, a flag, a keycap or a letter
|
|
41
|
+
with accents is one unit: measured once and never split by slicing or truncation.
|
|
42
|
+
- **Escape sequences.** SGR colours, OSC 8 hyperlinks, cursor movement and the other
|
|
43
|
+
ECMA-48 families count as nothing and are kept in place, so styled text stays styled.
|
|
44
|
+
- **Ambiguous width.** Greek, Cyrillic, box drawing and circled digits are one cell in
|
|
45
|
+
Western terminals and two in CJK ones. Pass `ambiguous=2` to any function.
|
|
46
|
+
- **Tabs.** Pass `tabsize=8` to measure tab stops; slicing and padding turn tabs into the
|
|
47
|
+
spaces they cover. `expand_tabs()` does it on its own.
|
|
48
|
+
- **Real Unicode data.** Tables are generated by a committed script from a committed
|
|
49
|
+
Unicode Character Database snapshot, and the full grapheme break test suite runs.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from cellstr import cell_len, iter_cells, truncate_detailed
|
|
53
|
+
|
|
54
|
+
cell_len("αβ─", ambiguous=2) # -> 6
|
|
55
|
+
cell_len("a\tb", tabsize=8) # -> 9
|
|
56
|
+
[c.width for c in iter_cells("a你🇯🇵")] # -> [1, 2, 2]
|
|
57
|
+
truncate_detailed("hello world", 8).truncated # -> True
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Public API: `cell_len`, `char_width`, `grapheme_width`, `iter_graphemes`, `iter_cells`
|
|
61
|
+
(`Cell`), `cell_slice`, `ljust`, `rjust`, `center`, `fit`, `truncate`,
|
|
62
|
+
`truncate_detailed` (`Truncation`), `expand_tabs`, `strip_ansi`, `UNICODE_VERSION`, and
|
|
63
|
+
the `Align` and `Position` literal types. Every function is fully typed and documented.
|
|
64
|
+
|
|
65
|
+
## Command line
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
$ printf 'abc\n你好\n' | cellstr measure
|
|
69
|
+
3
|
|
70
|
+
4
|
|
71
|
+
$ printf '/usr/local/lib/python3/site-packages\n' | cellstr truncate 16 --position middle
|
|
72
|
+
/usr/lo...ckages
|
|
73
|
+
$ printf 'id\n12345\n' | cellstr pad 8 --align right
|
|
74
|
+
id
|
|
75
|
+
12345
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Commands: `measure` (`--max`), `truncate N`, `pad N`, `fit N` and `slice START [STOP]`,
|
|
79
|
+
all with `--ambiguous` and `--tabsize`. `cellstr --help` lists runnable examples.
|
|
80
|
+
|
|
81
|
+
## Terminals disagree
|
|
82
|
+
|
|
83
|
+
There is no standard for how many cells a character takes, and terminals differ on
|
|
84
|
+
ambiguous-width characters, emoji ZWJ sequences and variation selectors. `cellstr`
|
|
85
|
+
follows Markus Kuhn's reference `wcwidth` for single code points and
|
|
86
|
+
measures emoji sequences as one glyph, as terminals with grapheme support draw them.
|
|
87
|
+
[GUIDE.md](GUIDE.md) has the exact rules, concrete examples of where terminals differ, a
|
|
88
|
+
recipe for code point by code point measuring, and every difference from the `wcwidth`
|
|
89
|
+
package.
|
|
90
|
+
|
|
91
|
+
## Why
|
|
92
|
+
|
|
93
|
+
`wcwidth` measures plain-text width well, but it does not give you a typed, cohesive set
|
|
94
|
+
of string operations: slicing by column that keeps escapes and graphemes intact, padding,
|
|
95
|
+
fitting a table cell and width-aware truncation with a suffix at either end or in the
|
|
96
|
+
middle. `strip-ansi` style tools remove escapes but know nothing about width. `cellstr`
|
|
97
|
+
does all of it in one small, fully typed, zero-dependency package.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT. See [LICENSE](LICENSE). The property tables in `src/cellstr/_ucd_*.py` are generated
|
|
102
|
+
from the Unicode Character Database and are covered by the Unicode License v3, see [LICENSE-UNICODE](LICENSE-UNICODE).
|
|
103
|
+
|
|
104
|
+
## Links
|
|
105
|
+
|
|
106
|
+
- Website: https://lianes8studio.com
|
|
107
|
+
- GitHub: https://github.com/JaviLianes8
|
|
108
|
+
- LinkedIn: https://www.linkedin.com/in/jlianes/
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"DerivedCoreProperties.txt": {
|
|
3
|
+
"title": "Unicode Character Database 18.0.0, DerivedCoreProperties.txt",
|
|
4
|
+
"url": "https://www.unicode.org/Public/18.0.0/ucd/DerivedCoreProperties.txt",
|
|
5
|
+
"revision": "Unicode 18.0.0",
|
|
6
|
+
"fetched": "2026-10-13",
|
|
7
|
+
"size": 1159889,
|
|
8
|
+
"sha256": "09c928886a178fcafd93c29e4bd59073a058e5a100b716d425cb563ab50f68c9",
|
|
9
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
10
|
+
},
|
|
11
|
+
"DerivedGeneralCategory.txt": {
|
|
12
|
+
"title": "Unicode Character Database 18.0.0, DerivedGeneralCategory.txt",
|
|
13
|
+
"url": "https://www.unicode.org/Public/18.0.0/ucd/extracted/DerivedGeneralCategory.txt",
|
|
14
|
+
"revision": "Unicode 18.0.0",
|
|
15
|
+
"fetched": "2026-10-13",
|
|
16
|
+
"size": 282143,
|
|
17
|
+
"sha256": "d6b151d2d40ee9b1876d26f417980f45ffae47b6055ccf7203cb31f07a030f94",
|
|
18
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
19
|
+
},
|
|
20
|
+
"EastAsianWidth.txt": {
|
|
21
|
+
"title": "Unicode Character Database 18.0.0, EastAsianWidth.txt",
|
|
22
|
+
"url": "https://www.unicode.org/Public/18.0.0/ucd/EastAsianWidth.txt",
|
|
23
|
+
"revision": "Unicode 18.0.0",
|
|
24
|
+
"fetched": "2026-10-13",
|
|
25
|
+
"size": 204705,
|
|
26
|
+
"sha256": "a0cf29eacd00cfcaec4381c6b7c281685f18dbb4e7ff82b4076ccb342ca839aa",
|
|
27
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
28
|
+
},
|
|
29
|
+
"GraphemeBreakProperty.txt": {
|
|
30
|
+
"title": "Unicode Character Database 18.0.0, GraphemeBreakProperty.txt",
|
|
31
|
+
"url": "https://www.unicode.org/Public/18.0.0/ucd/auxiliary/GraphemeBreakProperty.txt",
|
|
32
|
+
"revision": "Unicode 18.0.0",
|
|
33
|
+
"fetched": "2026-10-13",
|
|
34
|
+
"size": 100009,
|
|
35
|
+
"sha256": "0839dcb79e4ac639ecd538b1abf7c9d22e3f9dd265b7e182d33627aa4d75b45a",
|
|
36
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
37
|
+
},
|
|
38
|
+
"GraphemeBreakTest.txt": {
|
|
39
|
+
"title": "Unicode Character Database 18.0.0, GraphemeBreakTest.txt",
|
|
40
|
+
"url": "https://www.unicode.org/Public/18.0.0/ucd/auxiliary/GraphemeBreakTest.txt",
|
|
41
|
+
"revision": "Unicode 18.0.0",
|
|
42
|
+
"fetched": "2026-10-13",
|
|
43
|
+
"size": 137090,
|
|
44
|
+
"sha256": "b0cf047ee94485bbdc846de2b902f5f8a815f6b674f9d04223cddadd91c9df31",
|
|
45
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
46
|
+
},
|
|
47
|
+
"emoji-data.txt": {
|
|
48
|
+
"title": "Unicode Character Database 18.0.0, emoji-data.txt",
|
|
49
|
+
"url": "https://www.unicode.org/Public/18.0.0/ucd/emoji/emoji-data.txt",
|
|
50
|
+
"revision": "Unicode 18.0.0",
|
|
51
|
+
"fetched": "2026-10-13",
|
|
52
|
+
"size": 108719,
|
|
53
|
+
"sha256": "80d00f8e616a0ef27fd6b8de3b758c06383b5d917e2977709578e68baf733bf1",
|
|
54
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
55
|
+
},
|
|
56
|
+
"EastAsianWidth-15.1.0.txt": {
|
|
57
|
+
"title": "Unicode Character Database 15.1.0, EastAsianWidth.txt",
|
|
58
|
+
"url": "https://www.unicode.org/Public/15.1.0/ucd/EastAsianWidth.txt",
|
|
59
|
+
"revision": "Unicode 15.1.0",
|
|
60
|
+
"fetched": "2026-10-13",
|
|
61
|
+
"size": 194188,
|
|
62
|
+
"sha256": "b08191401dc125f4e84ef262a95754faae6b737c79538e17ea9664a63434e94e",
|
|
63
|
+
"licence": "Unicode License v3 (https://www.unicode.org/license.txt), see LICENSE-UNICODE"
|
|
64
|
+
}
|
|
65
|
+
}
|