chromatic-python 0.3.0__tar.gz → 0.3.2__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.
- {chromatic_python-0.3.0/chromatic_python.egg-info → chromatic_python-0.3.2}/PKG-INFO +10 -31
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/_typing.py +37 -14
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/_version.py +2 -2
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/colorconv.py +26 -6
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/core.py +95 -33
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/core.pyi +63 -15
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/iterators.py +11 -5
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/palette.py +36 -12
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/palette.pyi +19 -3
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/__init__.py +8 -1
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/__init__.pyi +8 -1
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/userfont.py +9 -3
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/userfont.pyi +3 -1
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/demo.py +43 -17
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/image/_array.py +379 -368
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/image/_glyph_proc.py +2 -10
- {chromatic_python-0.3.0 → chromatic_python-0.3.2/chromatic_python.egg-info}/PKG-INFO +10 -31
- chromatic_python-0.3.2/chromatic_python.egg-info/requires.txt +9 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/pyproject.toml +17 -7
- chromatic_python-0.3.2/requirements.txt +53 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/tests/test_color_str.py +17 -5
- chromatic_python-0.3.0/chromatic_python.egg-info/requires.txt +0 -9
- chromatic_python-0.3.0/requirements.txt +0 -9
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/.gitattributes +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/.gitignore +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/LICENSE +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/README.md +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/__init__.py +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/__init__.pyi +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/__init__.py +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/color/__init__.pyi +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/_fetchers.py +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/butterfly.jpg +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/escher.png +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/fonts/consolas.ttf +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/fonts/vga437.ttf +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/goblin_virus.png +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/registry.json +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/data/userfont.schema.json +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/image/__init__.py +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/image/__init__.pyi +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic/image/_curses.py +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic_python.egg-info/SOURCES.txt +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic_python.egg-info/dependency_links.txt +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/chromatic_python.egg-info/top_level.txt +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/logo/logo.ANS +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/logo/logo.PNG +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/setup.cfg +0 -0
- {chromatic_python-0.3.0 → chromatic_python-0.3.2}/tests/__init__.py +0 -0
|
@@ -1,29 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: ANSI art image processing and colored terminal text
|
|
5
5
|
Author: crypt0lith
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2024 crypt0lith
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
6
|
Project-URL: Homepage, https://github.com/crypt0lith/chromatic
|
|
28
7
|
Keywords: ansi,ascii,art,font,image,terminal,parser
|
|
29
8
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -33,15 +12,15 @@ Classifier: Typing :: Typed
|
|
|
33
12
|
Requires-Python: >=3.12
|
|
34
13
|
Description-Content-Type: text/markdown
|
|
35
14
|
License-File: LICENSE
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist: scikit-
|
|
42
|
-
Requires-Dist:
|
|
43
|
-
Requires-Dist:
|
|
44
|
-
Requires-Dist: lazy_loader
|
|
15
|
+
Requires-Dist: numpy<2.6,>=1.25.2
|
|
16
|
+
Requires-Dist: pillow!=11.2.*,<12.0,>=10.1
|
|
17
|
+
Requires-Dist: networkx<4.0,>=3.0
|
|
18
|
+
Requires-Dist: scipy<2.0,>=1.11.4
|
|
19
|
+
Requires-Dist: opencv-python
|
|
20
|
+
Requires-Dist: scikit-image
|
|
21
|
+
Requires-Dist: scikit-learn
|
|
22
|
+
Requires-Dist: fonttools
|
|
23
|
+
Requires-Dist: lazy_loader
|
|
45
24
|
Dynamic: license-file
|
|
46
25
|
|
|
47
26
|

|
|
@@ -50,8 +50,12 @@ _T_co = TypeVar('_T_co', covariant=True)
|
|
|
50
50
|
_T_contra = TypeVar('_T_contra', contravariant=True)
|
|
51
51
|
_AnyNumber_co = TypeVar('_AnyNumber_co', number, Number, covariant=True)
|
|
52
52
|
|
|
53
|
-
type ArrayReducerFunc[_SCT: generic] = Callable[
|
|
54
|
-
|
|
53
|
+
type ArrayReducerFunc[_SCT: generic] = Callable[
|
|
54
|
+
Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]
|
|
55
|
+
]
|
|
56
|
+
type ShapedNDArray[_Shape: tuple[int, ...], _SCT = generic] = ndarray[
|
|
57
|
+
_Shape, dtype[_SCT]
|
|
58
|
+
]
|
|
55
59
|
type MatrixLike[_SCT: generic] = ShapedNDArray[TupleOf2[int], _SCT]
|
|
56
60
|
type SquareMatrix[_I: int, _SCT: generic] = ShapedNDArray[TupleOf2[_I], _SCT]
|
|
57
61
|
type GlyphArray[_SCT: generic] = SquareMatrix[L[24], _SCT]
|
|
@@ -100,7 +104,11 @@ def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
|
|
|
100
104
|
name_slots = ["{%d.__name__!r}" % n for n in range(n_expected)]
|
|
101
105
|
if n_expected > 1:
|
|
102
106
|
name_slots[-1] = f"or {name_slots[-1]}"
|
|
103
|
-
names = (
|
|
107
|
+
names = (
|
|
108
|
+
(', ' if n_expected > 2 else ' ')
|
|
109
|
+
.join([context.strip(), *name_slots])
|
|
110
|
+
.format(*expected)
|
|
111
|
+
)
|
|
104
112
|
if not obj_repr:
|
|
105
113
|
if not isinstance(err_obj, type):
|
|
106
114
|
err_obj = type(err_obj)
|
|
@@ -122,7 +130,10 @@ def is_matching_type(value, typ):
|
|
|
122
130
|
return value in args
|
|
123
131
|
elif isinstance(typ, TypeVar):
|
|
124
132
|
if typ.__constraints__:
|
|
125
|
-
return any(
|
|
133
|
+
return any(
|
|
134
|
+
is_matching_type(value, constraint)
|
|
135
|
+
for constraint in typ.__constraints__
|
|
136
|
+
)
|
|
126
137
|
else:
|
|
127
138
|
return True
|
|
128
139
|
elif origin is type:
|
|
@@ -180,7 +191,9 @@ def is_matching_typed_dict(__d: dict, typed_dict: type[dict]) -> tuple[bool, str
|
|
|
180
191
|
field = __d.get(name)
|
|
181
192
|
if field is None or is_matching_type(field, typ):
|
|
182
193
|
continue
|
|
183
|
-
return False, type_error_msg(
|
|
194
|
+
return False, type_error_msg(
|
|
195
|
+
field, typ, context=f'keyword argument {name!r} of type'
|
|
196
|
+
)
|
|
184
197
|
return True, ''
|
|
185
198
|
|
|
186
199
|
|
|
@@ -231,9 +244,9 @@ class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
|
|
|
231
244
|
|
|
232
245
|
|
|
233
246
|
_SUBTYPE_CACHE: _BoundedDict[int, ...] = _BoundedDict()
|
|
234
|
-
_ATTR_GETTERS: _BoundedDict[
|
|
235
|
-
|
|
236
|
-
)
|
|
247
|
+
_ATTR_GETTERS: _BoundedDict[
|
|
248
|
+
..., tuple[Callable[[Iterable], NamedTuple], op.attrgetter]
|
|
249
|
+
] = _BoundedDict()
|
|
237
250
|
|
|
238
251
|
|
|
239
252
|
def _unique_attrs(obj) -> Optional['NamedTuple']:
|
|
@@ -278,7 +291,8 @@ def _sort_attrs(obj, tp_name, attr_names):
|
|
|
278
291
|
try:
|
|
279
292
|
sig = inspect.signature(type(obj))
|
|
280
293
|
indices = (
|
|
281
|
-
dict.fromkeys(field_names, inf)
|
|
294
|
+
dict.fromkeys(field_names, inf)
|
|
295
|
+
| {p: i for i, p in enumerate(sig.parameters)}
|
|
282
296
|
).values()
|
|
283
297
|
for names in (attr_names, field_names):
|
|
284
298
|
names.sort(key=dict(zip(names, indices)).__getitem__)
|
|
@@ -300,7 +314,9 @@ def _sort_attrs(obj, tp_name, attr_names):
|
|
|
300
314
|
while sig_start not in line:
|
|
301
315
|
line = next(lines)
|
|
302
316
|
_, _, params = line.partition(sig_start)
|
|
303
|
-
params, _, _ = (
|
|
317
|
+
params, _, _ = (
|
|
318
|
+
s.translate(no_square_parens) for s in params.partition(')')
|
|
319
|
+
)
|
|
304
320
|
maybe_sigs.add(params)
|
|
305
321
|
except StopIteration:
|
|
306
322
|
break
|
|
@@ -308,15 +324,20 @@ def _sort_attrs(obj, tp_name, attr_names):
|
|
|
308
324
|
if maybe_sigs:
|
|
309
325
|
if len(maybe_sigs) > 1:
|
|
310
326
|
sig = max(
|
|
311
|
-
maybe_sigs,
|
|
327
|
+
maybe_sigs,
|
|
328
|
+
key=lambda s: sum(1 for sub in s.split(', ') if sub in field_names),
|
|
312
329
|
)
|
|
313
330
|
else:
|
|
314
331
|
sig = maybe_sigs.pop()
|
|
315
332
|
positions = {x: i for i, x in enumerate(sig.split(', ')) if x}
|
|
316
333
|
sorted_field_names = sorted(field_names, key=lambda k: positions.get(k, inf))
|
|
317
|
-
transitions = {
|
|
334
|
+
transitions = {
|
|
335
|
+
idx: sorted_field_names.index(x) for idx, x in enumerate(field_names)
|
|
336
|
+
}
|
|
318
337
|
field_names = sorted_field_names
|
|
319
|
-
attr_names = [
|
|
338
|
+
attr_names = [
|
|
339
|
+
attr_names[k] for k in map(transitions.__getitem__, range(len(attr_names)))
|
|
340
|
+
]
|
|
320
341
|
for Names in (attr_names, field_names):
|
|
321
342
|
name_attr = next((s for s in Names if s.strip('_') == 'name'), None)
|
|
322
343
|
if name_attr is not None:
|
|
@@ -353,7 +374,9 @@ def subtype[_T](typ: _T) -> _T:
|
|
|
353
374
|
args_list = list(args)
|
|
354
375
|
if (
|
|
355
376
|
literals := [
|
|
356
|
-
idx
|
|
377
|
+
idx
|
|
378
|
+
for idx, elem in enumerate(args)
|
|
379
|
+
if isinstance(elem, _LiteralGenericType)
|
|
357
380
|
]
|
|
358
381
|
) and len(literals) > 1:
|
|
359
382
|
|
|
@@ -29,7 +29,14 @@ from typing import Final, Literal, SupportsInt, TypeGuard
|
|
|
29
29
|
|
|
30
30
|
import numpy as np
|
|
31
31
|
|
|
32
|
-
from .._typing import
|
|
32
|
+
from .._typing import (
|
|
33
|
+
Float3Tuple,
|
|
34
|
+
FloatSequence,
|
|
35
|
+
Int3Tuple,
|
|
36
|
+
RGBPixel,
|
|
37
|
+
RGBVectorLike,
|
|
38
|
+
ShapedNDArray,
|
|
39
|
+
)
|
|
33
40
|
|
|
34
41
|
|
|
35
42
|
@lru_cache
|
|
@@ -107,7 +114,10 @@ def lab2xyz(lab: FloatSequence) -> Float3Tuple:
|
|
|
107
114
|
x, y, z = map(
|
|
108
115
|
mul,
|
|
109
116
|
(95.047, 100.0, 108.883),
|
|
110
|
-
map(
|
|
117
|
+
map(
|
|
118
|
+
lambda i: (lambda j: j if j > 0.008856 else (i - 16 / 116) / 7.787)(i**3),
|
|
119
|
+
(x, y, z),
|
|
120
|
+
),
|
|
111
121
|
)
|
|
112
122
|
return x, y, z
|
|
113
123
|
|
|
@@ -127,7 +137,9 @@ def rgb2xyz(rgb: RGBPixel) -> Float3Tuple:
|
|
|
127
137
|
|
|
128
138
|
|
|
129
139
|
def xyz2rgb(xyz: ShapedNDArray[tuple[Literal[3]], np.float64]) -> Int3Tuple:
|
|
130
|
-
r, g, b = (
|
|
140
|
+
r, g, b = (
|
|
141
|
+
np.clip(M_XYZ2RGB @ np.array(xyz, dtype=np.float64), 0.0, 1.0) * 255.0
|
|
142
|
+
).astype(int)
|
|
131
143
|
return r, g, b
|
|
132
144
|
|
|
133
145
|
|
|
@@ -147,7 +159,13 @@ def hsl2rgb(hsl: FloatSequence) -> Int3Tuple:
|
|
|
147
159
|
mid2 = v - vsf
|
|
148
160
|
r, g, b = (
|
|
149
161
|
round(x * 0xFF)
|
|
150
|
-
for x in [
|
|
162
|
+
for x in [
|
|
163
|
+
[v, mid1, m],
|
|
164
|
+
[mid2, v, m],
|
|
165
|
+
[m, v, mid1],
|
|
166
|
+
[m, mid2, v],
|
|
167
|
+
[mid1, m, v],
|
|
168
|
+
][sextant]
|
|
151
169
|
)
|
|
152
170
|
else:
|
|
153
171
|
r, g, b = [round(L * 0xFF)] * 3
|
|
@@ -273,11 +291,13 @@ def _4b_lookup() -> dict[Int3Tuple, Int3Tuple]:
|
|
|
273
291
|
|
|
274
292
|
rgb_4b_arr = np.asarray(ANSI_4BIT_RGB)
|
|
275
293
|
quants = np.stack(
|
|
276
|
-
np.meshgrid(*np.repeat(np.arange(32).reshape([1, -1]), 3, 0), indexing='ij'),
|
|
294
|
+
np.meshgrid(*np.repeat(np.arange(32).reshape([1, -1]), 3, 0), indexing='ij'),
|
|
295
|
+
axis=-1,
|
|
277
296
|
).reshape([-1, 3])
|
|
278
297
|
nearest_colors = rgb_4b_arr[np.argmin(rgb_dist(quants * 8, rgb_4b_arr), axis=1)]
|
|
279
298
|
table: dict = {
|
|
280
|
-
tuple(map(int, color)): tuple(map(int, nearest_colors[i]))
|
|
299
|
+
tuple(map(int, color)): tuple(map(int, nearest_colors[i]))
|
|
300
|
+
for i, color in enumerate(quants)
|
|
281
301
|
}
|
|
282
302
|
return table
|
|
283
303
|
|
|
@@ -149,7 +149,9 @@ _ANSI16C_I2KV = cast(
|
|
|
149
149
|
dict[int, tuple[ColorDictKeys, Int3Tuple]],
|
|
150
150
|
{
|
|
151
151
|
v: (k, ansi_4bit_to_rgb(v))
|
|
152
|
-
for x in (
|
|
152
|
+
for x in (
|
|
153
|
+
zip(('fg', 'bg'), (j, j + 10)) for i in (30, 90) for j in range(i, i + 8)
|
|
154
|
+
)
|
|
153
155
|
for (k, v) in x
|
|
154
156
|
},
|
|
155
157
|
)
|
|
@@ -391,7 +393,9 @@ AnsiColorFormat: TypeAlias = ansicolor4Bit | ansicolor8Bit | ansicolor24Bit
|
|
|
391
393
|
AnsiColorType: TypeAlias = type[AnsiColorFormat]
|
|
392
394
|
AnsiColorParam: TypeAlias = AnsiColorAlias | AnsiColorType
|
|
393
395
|
_AnsiColor_co = TypeVar('_AnsiColor_co', bound=colorbytes, covariant=True)
|
|
394
|
-
_ANSI_COLOR_TYPES = cast(
|
|
396
|
+
_ANSI_COLOR_TYPES = cast(
|
|
397
|
+
frozenset[AnsiColorType], frozenset(colorbytes.__subclasses__())
|
|
398
|
+
)
|
|
395
399
|
_ANSI_FORMAT_MAP = {k: x for x in _ANSI_COLOR_TYPES for k in [x, x.alias]}
|
|
396
400
|
|
|
397
401
|
|
|
@@ -407,7 +411,9 @@ def _is_ansi_type(typ: type):
|
|
|
407
411
|
def sgr_re_pattern():
|
|
408
412
|
uint8_re = r"(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)"
|
|
409
413
|
ansicolor_re = f"[3-4]8;(?:2(?:;{uint8_re}){{3}}|5;{uint8_re})"
|
|
410
|
-
sgr_param_re =
|
|
414
|
+
sgr_param_re = (
|
|
415
|
+
rf"(?:{ansicolor_re}|10[0-7]|9[0-7]|6[0-3]|5[02-5]|2[0-68-9]|[13-4]\d|\d)"
|
|
416
|
+
)
|
|
411
417
|
|
|
412
418
|
return re.compile(rf"\x1b\[(?:{sgr_param_re}(?:;{sgr_param_re})*)?m")
|
|
413
419
|
|
|
@@ -436,7 +442,10 @@ def _split_ansi_escape(__s: str) -> list[tuple['SgrSequence', str]] | None:
|
|
|
436
442
|
out = tmp
|
|
437
443
|
if out and len(out) % 2 != 0:
|
|
438
444
|
out.append({SgrSequence: str, str: SgrSequence}[type(out[-1])]())
|
|
439
|
-
return [
|
|
445
|
+
return [
|
|
446
|
+
(a, b) if isinstance(a, SgrSequence) else (b, a)
|
|
447
|
+
for a, b in zip(out[::2], out[1::2])
|
|
448
|
+
]
|
|
440
449
|
|
|
441
450
|
|
|
442
451
|
def _unwrap_ansi_escape(__b: bytes | bytearray):
|
|
@@ -458,10 +467,12 @@ def get_ansi_type(typ):
|
|
|
458
467
|
from .._typing import unionize
|
|
459
468
|
|
|
460
469
|
repr_getter = lambda t: (t if isinstance(t, type) else type(t))
|
|
461
|
-
msg =
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
470
|
+
msg = (
|
|
471
|
+
"Expected {.__name__!r} or {}, got {.__name__!r} object instead".format(
|
|
472
|
+
str,
|
|
473
|
+
type[unionize(set(map(repr_getter, _ANSI_FORMAT_MAP.values())))],
|
|
474
|
+
repr_getter(typ),
|
|
475
|
+
)
|
|
465
476
|
)
|
|
466
477
|
err = TypeError(msg)
|
|
467
478
|
err.__cause__ = e.__cause__
|
|
@@ -597,10 +608,17 @@ def _get_sgr_nums(__x: bytes) -> list[int]:
|
|
|
597
608
|
"""
|
|
598
609
|
if __x.isdigit():
|
|
599
610
|
return [int(__x)]
|
|
600
|
-
__x = __x.removeprefix(CSI)[
|
|
611
|
+
__x = __x.removeprefix(CSI)[
|
|
612
|
+
: idx if ~(idx := __x.find(0x6D)) else None
|
|
613
|
+
].removesuffix(b'm')
|
|
601
614
|
length = len(__x)
|
|
602
615
|
mask_indices = enumerate(
|
|
603
|
-
map(
|
|
616
|
+
map(
|
|
617
|
+
bool,
|
|
618
|
+
int.to_bytes(
|
|
619
|
+
~int.from_bytes(b';' * length) & int.from_bytes(__x), length=length
|
|
620
|
+
),
|
|
621
|
+
)
|
|
604
622
|
)
|
|
605
623
|
res = []
|
|
606
624
|
buf = bytearray()
|
|
@@ -645,7 +663,9 @@ def _iter_normalized_sgr[_T: (
|
|
|
645
663
|
)
|
|
646
664
|
|
|
647
665
|
|
|
648
|
-
def _co_yield_colorbytes(
|
|
666
|
+
def _co_yield_colorbytes(
|
|
667
|
+
__iter: Iterator[int],
|
|
668
|
+
) -> Generator[bytes | AnsiColorFormat, int, None]:
|
|
649
669
|
d: dict[int, ColorDictKeys] = {38: 'fg', 48: 'bg'}
|
|
650
670
|
obj = b''
|
|
651
671
|
while True:
|
|
@@ -737,7 +757,8 @@ class SgrSequence(MutableSequence[SgrParamBuffer]):
|
|
|
737
757
|
def ansi_type(self):
|
|
738
758
|
if self.is_color():
|
|
739
759
|
typ, _ = max(
|
|
740
|
-
Counter(type(x._value) for x in self if x.is_color()).items(),
|
|
760
|
+
Counter(type(x._value) for x in self if x.is_color()).items(),
|
|
761
|
+
key=lambda x: x[1],
|
|
741
762
|
)
|
|
742
763
|
return typ
|
|
743
764
|
|
|
@@ -751,7 +772,8 @@ class SgrSequence(MutableSequence[SgrParamBuffer]):
|
|
|
751
772
|
)
|
|
752
773
|
case ColorStr():
|
|
753
774
|
return color_chain._from_masks_unchecked(
|
|
754
|
-
[(self, ''), (other._sgr, other.base_str)],
|
|
775
|
+
[(self, ''), (other._sgr, other.base_str)],
|
|
776
|
+
self.ansi_type() or DEFAULT_ANSI,
|
|
755
777
|
)
|
|
756
778
|
case str():
|
|
757
779
|
return ColorStr(f"{self}{other}")
|
|
@@ -831,7 +853,9 @@ class SgrSequence(MutableSequence[SgrParamBuffer]):
|
|
|
831
853
|
elts[SgrParamBuffer(elt)] = None
|
|
832
854
|
|
|
833
855
|
self._sgr_params = list(elts)
|
|
834
|
-
self._rgb_dict = {
|
|
856
|
+
self._rgb_dict = {
|
|
857
|
+
k: v for xs in colors.values() for k, v in xs._rgb_dict.items()
|
|
858
|
+
}
|
|
835
859
|
|
|
836
860
|
def __iter__(self):
|
|
837
861
|
return iter(self._sgr_params)
|
|
@@ -850,7 +874,9 @@ class SgrSequence(MutableSequence[SgrParamBuffer]):
|
|
|
850
874
|
__value: ...
|
|
851
875
|
xs = list(_iter_sgr(__value))
|
|
852
876
|
if len(xs) != 1:
|
|
853
|
-
err = ValueError(
|
|
877
|
+
err = ValueError(
|
|
878
|
+
f"parsed {len(xs)} sgr parameters, expected only 1: {xs!r}"
|
|
879
|
+
)
|
|
854
880
|
raise err
|
|
855
881
|
self._sgr_params[__index] = SgrParamBuffer(xs.pop())
|
|
856
882
|
self._update_colors()
|
|
@@ -1009,19 +1035,29 @@ def _make_colorstr[_T: ColorStr](cls: type[_T], obj=_unset, *args, **kwargs) ->
|
|
|
1009
1035
|
else:
|
|
1010
1036
|
[typ] = e.args
|
|
1011
1037
|
err = TypeError(
|
|
1012
|
-
"expected integer or vector of 3 integers, "
|
|
1038
|
+
"expected integer or vector of 3 integers, "
|
|
1039
|
+
f"got {typ.__name__!r} object instead"
|
|
1013
1040
|
)
|
|
1014
1041
|
err.__cause__ = e.__cause__
|
|
1015
1042
|
raise err
|
|
1016
|
-
inst: Any = str.__new__(
|
|
1017
|
-
|
|
1043
|
+
inst: Any = str.__new__(
|
|
1044
|
+
cls, ''.join([str(sgr), base_str, SGR_RESET_S if reset else ''])
|
|
1045
|
+
)
|
|
1046
|
+
inst.__dict__ |= {
|
|
1047
|
+
'_sgr': sgr,
|
|
1048
|
+
'_base_str': base_str,
|
|
1049
|
+
'_ansi_type': ansi_type,
|
|
1050
|
+
'_reset': reset,
|
|
1051
|
+
}
|
|
1018
1052
|
return inst
|
|
1019
1053
|
|
|
1020
1054
|
|
|
1021
1055
|
class ColorStr(str):
|
|
1022
1056
|
def _weak_var_update(self, **kwargs):
|
|
1023
1057
|
if not kwargs.keys() <= {'base_str', 'sgr', 'reset'}:
|
|
1024
|
-
raise ValueError(
|
|
1058
|
+
raise ValueError(
|
|
1059
|
+
f'unexpected keys: {(kwargs.keys() - {'base_str', 'sgr', 'reset'})}'
|
|
1060
|
+
)
|
|
1025
1061
|
sgr = kwargs.get('sgr', self._sgr)
|
|
1026
1062
|
base_str = kwargs.get('base_str', self.base_str)
|
|
1027
1063
|
suffix = SGR_RESET_S if kwargs.get('reset', self.reset) else ''
|
|
@@ -1055,7 +1091,8 @@ class ColorStr(str):
|
|
|
1055
1091
|
sgr = SgrSequence(self._sgr)
|
|
1056
1092
|
sgr.rgb_dict = sgr._rgb_dict, ansi_type
|
|
1057
1093
|
inst = str.__new__(
|
|
1058
|
-
type(self),
|
|
1094
|
+
type(self),
|
|
1095
|
+
''.join([str(sgr), self.base_str, SGR_RESET_S if self.reset else '']),
|
|
1059
1096
|
)
|
|
1060
1097
|
inst.__dict__ |= vars(self) | {'_sgr': sgr, '_ansi_type': ansi_type}
|
|
1061
1098
|
return inst
|
|
@@ -1122,7 +1159,9 @@ class ColorStr(str):
|
|
|
1122
1159
|
('Red text', '0x00FF00')
|
|
1123
1160
|
"""
|
|
1124
1161
|
if not kwargs.keys() <= {'absolute', 'fg', 'bg'}:
|
|
1125
|
-
raise ValueError(
|
|
1162
|
+
raise ValueError(
|
|
1163
|
+
f"unexpected keywords: {(kwargs.keys() - {'absolute', 'fg', 'bg'})}"
|
|
1164
|
+
)
|
|
1126
1165
|
if kwargs.pop('absolute', False):
|
|
1127
1166
|
if not (args or kwargs):
|
|
1128
1167
|
return (
|
|
@@ -1167,14 +1206,18 @@ class ColorStr(str):
|
|
|
1167
1206
|
if b'%d' % SgrParameter.DOUBLE_UNDERLINE in self._sgr:
|
|
1168
1207
|
return self
|
|
1169
1208
|
elif b'%d' % SgrParameter.SINGLE_UNDERLINE in self._sgr:
|
|
1170
|
-
return self.update_sgr(
|
|
1209
|
+
return self.update_sgr(
|
|
1210
|
+
SgrParameter.SINGLE_UNDERLINE, SgrParameter.DOUBLE_UNDERLINE
|
|
1211
|
+
)
|
|
1171
1212
|
else:
|
|
1172
1213
|
return self.update_sgr(SgrParameter.DOUBLE_UNDERLINE)
|
|
1173
1214
|
else:
|
|
1174
1215
|
if b'%d' % SgrParameter.SINGLE_UNDERLINE in self._sgr:
|
|
1175
1216
|
return self
|
|
1176
1217
|
elif b'%d' % SgrParameter.DOUBLE_UNDERLINE in self._sgr:
|
|
1177
|
-
return self.update_sgr(
|
|
1218
|
+
return self.update_sgr(
|
|
1219
|
+
SgrParameter.DOUBLE_UNDERLINE, SgrParameter.SINGLE_UNDERLINE
|
|
1220
|
+
)
|
|
1178
1221
|
else:
|
|
1179
1222
|
return self.update_sgr(SgrParameter.SINGLE_UNDERLINE)
|
|
1180
1223
|
|
|
@@ -1245,7 +1288,8 @@ class ColorStr(str):
|
|
|
1245
1288
|
else:
|
|
1246
1289
|
sgr.append(bx)
|
|
1247
1290
|
inst = super().__new__(
|
|
1248
|
-
type(self),
|
|
1291
|
+
type(self),
|
|
1292
|
+
''.join([str(sgr), self.base_str, SGR_RESET_S if self.reset else '']),
|
|
1249
1293
|
)
|
|
1250
1294
|
inst.__dict__ |= vars(self) | {
|
|
1251
1295
|
'_sgr': sgr,
|
|
@@ -1321,7 +1365,9 @@ class ColorStr(str):
|
|
|
1321
1365
|
|
|
1322
1366
|
def join(self, __iterable):
|
|
1323
1367
|
return self._weak_var_update(
|
|
1324
|
-
base_str=self.base_str.join(
|
|
1368
|
+
base_str=self.base_str.join(
|
|
1369
|
+
getattr(elt, 'base_str', elt) for elt in __iterable
|
|
1370
|
+
)
|
|
1325
1371
|
)
|
|
1326
1372
|
|
|
1327
1373
|
def ljust(self, __width, __fillchar=" "):
|
|
@@ -1334,7 +1380,9 @@ class ColorStr(str):
|
|
|
1334
1380
|
return self._weak_var_update(base_str=self.base_str.lstrip(__chars))
|
|
1335
1381
|
|
|
1336
1382
|
def partition(self, __sep):
|
|
1337
|
-
lhs, sep, rhs = (
|
|
1383
|
+
lhs, sep, rhs = (
|
|
1384
|
+
self._weak_var_update(base_str=s) for s in self.base_str.partition(__sep)
|
|
1385
|
+
)
|
|
1338
1386
|
return lhs, sep, rhs
|
|
1339
1387
|
|
|
1340
1388
|
def removeprefix(self, __prefix):
|
|
@@ -1344,7 +1392,9 @@ class ColorStr(str):
|
|
|
1344
1392
|
return self._weak_var_update(base_str=self.base_str.removesuffix(__prefix))
|
|
1345
1393
|
|
|
1346
1394
|
def replace(self, __old, __new, __count=-1):
|
|
1347
|
-
return self._weak_var_update(
|
|
1395
|
+
return self._weak_var_update(
|
|
1396
|
+
base_str=self.base_str.replace(__old, __new, __count)
|
|
1397
|
+
)
|
|
1348
1398
|
|
|
1349
1399
|
def rfind(self, __sub, *args):
|
|
1350
1400
|
return self.base_str.rfind(__sub, *args)
|
|
@@ -1359,7 +1409,9 @@ class ColorStr(str):
|
|
|
1359
1409
|
return self._weak_var_update(base_str=self.base_str.rstrip(__chars))
|
|
1360
1410
|
|
|
1361
1411
|
def rpartition(self, __sep):
|
|
1362
|
-
lhs, sep, rhs = (
|
|
1412
|
+
lhs, sep, rhs = (
|
|
1413
|
+
self._weak_var_update(base_str=s) for s in self.base_str.rpartition(__sep)
|
|
1414
|
+
)
|
|
1363
1415
|
return lhs, sep, rhs
|
|
1364
1416
|
|
|
1365
1417
|
def rsplit(self, sep=None, maxsplit=-1):
|
|
@@ -1376,7 +1428,8 @@ class ColorStr(str):
|
|
|
1376
1428
|
|
|
1377
1429
|
def splitlines(self, keepends=False):
|
|
1378
1430
|
return [
|
|
1379
|
-
self._weak_var_update(base_str=s)
|
|
1431
|
+
self._weak_var_update(base_str=s)
|
|
1432
|
+
for s in self.base_str.splitlines(keepends=keepends)
|
|
1380
1433
|
]
|
|
1381
1434
|
|
|
1382
1435
|
def startswith(self, __prefix, *args):
|
|
@@ -1476,11 +1529,15 @@ class ColorStr(str):
|
|
|
1476
1529
|
k: L['fg', 'bg']
|
|
1477
1530
|
if isinstance(other, type(self)):
|
|
1478
1531
|
xor_dict = {
|
|
1479
|
-
k: int2rgb(
|
|
1532
|
+
k: int2rgb(
|
|
1533
|
+
Color.from_rgb(self.rgb_dict[k]) ^ Color.from_rgb(other.rgb_dict[k])
|
|
1534
|
+
)
|
|
1480
1535
|
for k in self.rgb_dict.keys() & other.rgb_dict
|
|
1481
1536
|
}
|
|
1482
1537
|
elif isinstance(other, int):
|
|
1483
|
-
xor_dict = {
|
|
1538
|
+
xor_dict = {
|
|
1539
|
+
k: int2rgb(Color.from_rgb(v) ^ other) for k, v in self.rgb_dict.items()
|
|
1540
|
+
}
|
|
1484
1541
|
else:
|
|
1485
1542
|
return NotImplemented
|
|
1486
1543
|
if not xor_dict:
|
|
@@ -1596,7 +1653,10 @@ class color_chain:
|
|
|
1596
1653
|
elif isinstance(other, str):
|
|
1597
1654
|
if len(self._masks) > 0:
|
|
1598
1655
|
return self._from_masks_unchecked(
|
|
1599
|
-
[
|
|
1656
|
+
[
|
|
1657
|
+
*self.masks[:-1],
|
|
1658
|
+
(self._masks[-1][0], self._masks[-1][1] + other),
|
|
1659
|
+
],
|
|
1600
1660
|
ansi_type=self._ansi_type,
|
|
1601
1661
|
)
|
|
1602
1662
|
return self._from_masks_unchecked(
|
|
@@ -1615,7 +1675,9 @@ class color_chain:
|
|
|
1615
1675
|
if not kwargs.keys() <= {'fg', 'bg'}:
|
|
1616
1676
|
raise ValueError
|
|
1617
1677
|
sgr = SgrSequence(__sgr)
|
|
1618
|
-
sgr.rgb_dict = {
|
|
1678
|
+
sgr.rgb_dict = {
|
|
1679
|
+
k: v for k, v in kwargs.items() if v is not None
|
|
1680
|
+
}, self._ansi_type
|
|
1619
1681
|
self._masks = [(sgr, '')]
|
|
1620
1682
|
|
|
1621
1683
|
def __radd__(self, other):
|