chromatic-python 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chromatic/__init__.py +67 -0
- chromatic/_typing.py +8 -5
- chromatic/_version.py +2 -2
- chromatic/ascii/_array.py +71 -35
- chromatic/color/core.py +66 -72
- chromatic/color/core.pyi +18 -13
- chromatic/color/palette.py +49 -47
- chromatic/color/palette.pyi +3 -14
- chromatic/data/__init__.py +5 -15
- chromatic/demo.py +5 -5
- {chromatic_python-0.2.1.dist-info → chromatic_python-0.2.2.dist-info}/METADATA +9 -8
- {chromatic_python-0.2.1.dist-info → chromatic_python-0.2.2.dist-info}/RECORD +15 -15
- {chromatic_python-0.2.1.dist-info → chromatic_python-0.2.2.dist-info}/WHEEL +1 -1
- {chromatic_python-0.2.1.dist-info → chromatic_python-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {chromatic_python-0.2.1.dist-info → chromatic_python-0.2.2.dist-info}/top_level.txt +0 -0
chromatic/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ from . import ascii, color, data
|
|
|
7
7
|
from .ascii import (
|
|
8
8
|
AnsiImage,
|
|
9
9
|
ansi2img,
|
|
10
|
+
ansify,
|
|
10
11
|
ansi_quantize,
|
|
11
12
|
ascii2img,
|
|
12
13
|
ascii_printable,
|
|
@@ -40,3 +41,69 @@ from .color import (
|
|
|
40
41
|
from .data import register_user_font
|
|
41
42
|
|
|
42
43
|
__all__ = []
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
import os
|
|
47
|
+
import sys
|
|
48
|
+
from functools import lru_cache, wraps
|
|
49
|
+
from types import ModuleType
|
|
50
|
+
|
|
51
|
+
def find_modules(path: str):
|
|
52
|
+
from setuptools import find_packages
|
|
53
|
+
from pkgutil import iter_modules
|
|
54
|
+
|
|
55
|
+
tree: dict[str, dict | ModuleType] = {__name__: {'__module__': sys.modules[__name__]}}
|
|
56
|
+
children = set()
|
|
57
|
+
for pkg in find_packages(path):
|
|
58
|
+
children.add(pkg)
|
|
59
|
+
pkg_path = path + '/' + pkg.replace('.', '/')
|
|
60
|
+
if sys.version_info.major == 2 or (
|
|
61
|
+
sys.version_info.major == 3 and sys.version_info.minor < 6
|
|
62
|
+
):
|
|
63
|
+
for _, name, ispkg in iter_modules([pkg_path]):
|
|
64
|
+
if not ispkg:
|
|
65
|
+
children.add(f"{pkg}.{name}")
|
|
66
|
+
else:
|
|
67
|
+
for info in iter_modules([pkg_path]):
|
|
68
|
+
if not info.ispkg:
|
|
69
|
+
children.add(f"{pkg}.{info.name}")
|
|
70
|
+
for child in children:
|
|
71
|
+
name = f"{__name__}.{child}"
|
|
72
|
+
depth = tree
|
|
73
|
+
for node in name.split('.'):
|
|
74
|
+
if node not in depth:
|
|
75
|
+
depth[node] = {}
|
|
76
|
+
depth = depth[node]
|
|
77
|
+
if name in sys.modules:
|
|
78
|
+
depth['__module__'] = sys.modules[name]
|
|
79
|
+
return tree
|
|
80
|
+
|
|
81
|
+
def publicize_modules(modulename: str, tree: dict[str, dict | ModuleType]):
|
|
82
|
+
def is_local(obj: object):
|
|
83
|
+
if isinstance(obj, ModuleType):
|
|
84
|
+
return obj.__spec__.parent == modulename
|
|
85
|
+
elif hasattr(obj, '__module__'):
|
|
86
|
+
return obj.__module__ == modulename
|
|
87
|
+
return obj is not None
|
|
88
|
+
|
|
89
|
+
for name, subtree in tree.items():
|
|
90
|
+
if name == '__module__' and isinstance(subtree, ModuleType):
|
|
91
|
+
submodule = subtree
|
|
92
|
+
init_dir = dir(submodule)
|
|
93
|
+
|
|
94
|
+
@wraps(submodule.__dir__)
|
|
95
|
+
def wrapped():
|
|
96
|
+
s = set(attr for attr in init_dir if is_local(getattr(submodule, attr, None)))
|
|
97
|
+
if hasattr(submodule, '__all__'):
|
|
98
|
+
s.update(submodule.__all__)
|
|
99
|
+
return list(s)
|
|
100
|
+
|
|
101
|
+
sys.modules[submodule.__name__].__dir__ = wrapped
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
publicize_modules(f"{modulename}.{name}", subtree)
|
|
105
|
+
|
|
106
|
+
publicize_modules(*find_modules(os.path.split(__file__)[0]).popitem())
|
|
107
|
+
|
|
108
|
+
finally:
|
|
109
|
+
del find_modules, publicize_modules
|
chromatic/_typing.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import operator as op
|
|
4
5
|
import re
|
|
5
6
|
import types
|
|
6
7
|
from collections import OrderedDict, namedtuple
|
|
7
8
|
from collections.abc import Callable as ABC_Callable
|
|
8
9
|
from functools import reduce, wraps
|
|
9
10
|
from numbers import Number
|
|
10
|
-
from operator import attrgetter, or_ as bitwise_or
|
|
11
11
|
from typing import (
|
|
12
12
|
Any,
|
|
13
13
|
Callable,
|
|
@@ -79,7 +79,7 @@ FontArgType: TypeAlias = FreeTypeFont | UserFont | str | int
|
|
|
79
79
|
|
|
80
80
|
def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
|
|
81
81
|
n_expected = len(expected)
|
|
82
|
-
name_slots = [
|
|
82
|
+
name_slots = ['{%d.__qualname__!r}' % n for n in range(n_expected)]
|
|
83
83
|
if name_slots and n_expected > 1:
|
|
84
84
|
name_slots[-1] = f"or {name_slots[-1]}"
|
|
85
85
|
names = (', ' if n_expected > 2 else ' ').join([context.strip()] + name_slots).format(*expected)
|
|
@@ -183,7 +183,7 @@ class SupportsUnion(Protocol[_T_contra, _T_co]):
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
|
|
186
|
-
return reduce(
|
|
186
|
+
return reduce(op.or_, __iterable)
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
_GenericAlias = type(Type[...]) | types.GenericAlias
|
|
@@ -213,7 +213,7 @@ class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
|
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
_SUBTYPE_CACHE: _BoundedDict[int, ...] = _BoundedDict()
|
|
216
|
-
_ATTR_GETTERS: _BoundedDict[..., tuple[Callable[[Iterable], NamedTuple], attrgetter]] = (
|
|
216
|
+
_ATTR_GETTERS: _BoundedDict[..., tuple[Callable[[Iterable], NamedTuple], op.attrgetter]] = (
|
|
217
217
|
_BoundedDict()
|
|
218
218
|
)
|
|
219
219
|
|
|
@@ -247,7 +247,10 @@ def _unique_attrs(obj) -> Optional['NamedTuple']:
|
|
|
247
247
|
+ 'Attrs'
|
|
248
248
|
).strip('_')
|
|
249
249
|
UniqueAttrs = cast(NamedTuple, namedtuple(tup_name, field_names))
|
|
250
|
-
_ATTR_GETTERS[tp] = constructor, getter =
|
|
250
|
+
_ATTR_GETTERS[tp] = [constructor, getter] = [
|
|
251
|
+
UniqueAttrs._make,
|
|
252
|
+
op.attrgetter(*attr_names), # noqa
|
|
253
|
+
]
|
|
251
254
|
return constructor(getter(obj))
|
|
252
255
|
|
|
253
256
|
|
chromatic/_version.py
CHANGED
chromatic/ascii/_array.py
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
__all__ = [
|
|
4
|
+
'ansi2img',
|
|
5
|
+
'ansify',
|
|
6
|
+
'ansi_quantize',
|
|
7
|
+
'ascii2img',
|
|
8
|
+
'contrast_stretch',
|
|
9
|
+
'equalize_white_point',
|
|
10
|
+
'get_font_key',
|
|
11
|
+
'get_font_object',
|
|
12
|
+
'img2ansi',
|
|
13
|
+
'img2ascii',
|
|
14
|
+
'read_ans',
|
|
15
|
+
'render_ans',
|
|
16
|
+
'render_font_char',
|
|
17
|
+
'render_font_str',
|
|
18
|
+
'reshape_ansi',
|
|
19
|
+
'scale_saturation',
|
|
20
|
+
'shuffle_char_set',
|
|
21
|
+
'_scaled_hu_moments',
|
|
22
|
+
'to_sgr_array',
|
|
23
|
+
'AnsiImage',
|
|
24
|
+
'_otsu_mask',
|
|
25
|
+
]
|
|
26
|
+
|
|
3
27
|
import math
|
|
4
28
|
import os.path
|
|
5
29
|
import random
|
|
@@ -62,29 +86,6 @@ from ..data import UserFont
|
|
|
62
86
|
if TYPE_CHECKING:
|
|
63
87
|
from _typeshed import AnyStr_co
|
|
64
88
|
|
|
65
|
-
__all__ = [
|
|
66
|
-
'ansi2img',
|
|
67
|
-
'ansi_quantize',
|
|
68
|
-
'ascii2img',
|
|
69
|
-
'contrast_stretch',
|
|
70
|
-
'equalize_white_point',
|
|
71
|
-
'get_font_key',
|
|
72
|
-
'get_font_object',
|
|
73
|
-
'img2ansi',
|
|
74
|
-
'img2ascii',
|
|
75
|
-
'read_ans',
|
|
76
|
-
'render_ans',
|
|
77
|
-
'render_font_char',
|
|
78
|
-
'render_font_str',
|
|
79
|
-
'reshape_ansi',
|
|
80
|
-
'scale_saturation',
|
|
81
|
-
'shuffle_char_set',
|
|
82
|
-
'_scaled_hu_moments',
|
|
83
|
-
'to_sgr_array',
|
|
84
|
-
'AnsiImage',
|
|
85
|
-
'_otsu_mask',
|
|
86
|
-
]
|
|
87
|
-
|
|
88
89
|
|
|
89
90
|
def get_font_key(font: FreeTypeFont):
|
|
90
91
|
"""Obtain a unique tuple pair from a FreeTypeFont object.
|
|
@@ -597,6 +598,7 @@ def img2ascii(
|
|
|
597
598
|
return ascii_str
|
|
598
599
|
|
|
599
600
|
|
|
601
|
+
@rgb_dispatch
|
|
600
602
|
def img2ansi(
|
|
601
603
|
__img: RGBImageLike | PathLike[str] | str,
|
|
602
604
|
__font: FontArgType = 'arial.ttf',
|
|
@@ -605,7 +607,7 @@ def img2ansi(
|
|
|
605
607
|
ansi_type: AnsiColorParam = DEFAULT_ANSI,
|
|
606
608
|
sort_glyphs: bool | type[reversed] = True,
|
|
607
609
|
equalize: bool | Literal['white_point'] = True,
|
|
608
|
-
bg: Color | Int3Tuple = (0, 0, 0),
|
|
610
|
+
bg: Color | Int3Tuple | str = (0, 0, 0),
|
|
609
611
|
):
|
|
610
612
|
"""Convert an image to an ANSI array.
|
|
611
613
|
|
|
@@ -665,7 +667,7 @@ def img2ansi(
|
|
|
665
667
|
"""
|
|
666
668
|
if ansi_type is not DEFAULT_ANSI:
|
|
667
669
|
ansi_type = get_ansi_type(ansi_type)
|
|
668
|
-
bg_wrapper = ColorStr('{}', color_spec={'bg': bg}, ansi_type=ansi_type,
|
|
670
|
+
bg_wrapper = ColorStr('{}', color_spec={'bg': bg}, ansi_type=ansi_type, reset=False)
|
|
669
671
|
base_ascii, color_arr = img2ascii(__img, __font, factor, char_set, sort_glyphs, ret_img=True)
|
|
670
672
|
lines = base_ascii.splitlines()
|
|
671
673
|
h, w = map(len, (lines, lines[0]))
|
|
@@ -697,7 +699,7 @@ def ascii2img(
|
|
|
697
699
|
font_size=24,
|
|
698
700
|
*,
|
|
699
701
|
fg: Int3Tuple | str = (0, 0, 0),
|
|
700
|
-
bg: Int3Tuple | str = (
|
|
702
|
+
bg: Int3Tuple | str = (0xFF, 0xFF, 0xFF),
|
|
701
703
|
):
|
|
702
704
|
"""Render a literal string as an image.
|
|
703
705
|
|
|
@@ -803,7 +805,7 @@ def ansi2img(
|
|
|
803
805
|
x_offset = 0
|
|
804
806
|
for color_str in row:
|
|
805
807
|
text_width = font.getbbox(color_str.base_str)[2]
|
|
806
|
-
if color_str
|
|
808
|
+
if getattr(color_str, '_sgr_').is_reset():
|
|
807
809
|
fg_default = None
|
|
808
810
|
bg_default = input_bg
|
|
809
811
|
if fg_color := getattr(color_str.fg, 'rgb', fg_default):
|
|
@@ -823,6 +825,38 @@ def ansi2img(
|
|
|
823
825
|
return img
|
|
824
826
|
|
|
825
827
|
|
|
828
|
+
def ansify(
|
|
829
|
+
__img: RGBImageLike | PathLike[str] | str,
|
|
830
|
+
/,
|
|
831
|
+
*,
|
|
832
|
+
font: FontArgType = UserFont.IBM_VGA_437_8X16,
|
|
833
|
+
font_size: int = 16,
|
|
834
|
+
factor: int = 200,
|
|
835
|
+
char_set: Iterable[str] = None,
|
|
836
|
+
sort_glyphs: bool | type[reversed] = True,
|
|
837
|
+
ansi_type: AnsiColorParam = DEFAULT_ANSI,
|
|
838
|
+
equalize: bool | Literal['white_point'] = True,
|
|
839
|
+
fg: Int3Tuple | str = (170, 170, 170),
|
|
840
|
+
bg: Int3Tuple | str | Literal['auto'] = (0, 0, 0),
|
|
841
|
+
):
|
|
842
|
+
return ansi2img(
|
|
843
|
+
img2ansi(
|
|
844
|
+
__img,
|
|
845
|
+
font,
|
|
846
|
+
factor=factor,
|
|
847
|
+
char_set=char_set,
|
|
848
|
+
ansi_type=ansi_type,
|
|
849
|
+
sort_glyphs=sort_glyphs,
|
|
850
|
+
equalize=equalize,
|
|
851
|
+
bg=bg,
|
|
852
|
+
),
|
|
853
|
+
font,
|
|
854
|
+
font_size=font_size,
|
|
855
|
+
fg_default=fg,
|
|
856
|
+
bg_default=bg,
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
|
|
826
860
|
def _is_array(__obj: Any) -> TypeGuard[ndarray]:
|
|
827
861
|
return isinstance(__obj, ndarray)
|
|
828
862
|
|
|
@@ -913,6 +947,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = DEFAULT_ANSI):
|
|
|
913
947
|
resets = frozenset(resets_btok)
|
|
914
948
|
pad_esc = {0x1B: '\x00\x1b'}
|
|
915
949
|
x = []
|
|
950
|
+
sgr: SgrSequence | None
|
|
916
951
|
for line in __str.splitlines():
|
|
917
952
|
xs = []
|
|
918
953
|
ansi_meta = {}
|
|
@@ -927,7 +962,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = DEFAULT_ANSI):
|
|
|
927
962
|
else:
|
|
928
963
|
cs = new_cs(s)
|
|
929
964
|
if sgr is None:
|
|
930
|
-
sgr = cs
|
|
965
|
+
sgr: SgrSequence = getattr(cs, '_sgr_')
|
|
931
966
|
if sgr.is_reset():
|
|
932
967
|
ansi_meta.clear()
|
|
933
968
|
for k in resets.intersection(sgr.values()):
|
|
@@ -947,7 +982,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = DEFAULT_ANSI):
|
|
|
947
982
|
if not ansi_meta.get('bright'):
|
|
948
983
|
ansi_meta['bright'] = True
|
|
949
984
|
new_sgr = SgrSequence(
|
|
950
|
-
sgr.values() + [p._value_ for p in prev
|
|
985
|
+
sgr.values() + [p._value_ for p in getattr(prev, '_sgr_') if p.is_color()]
|
|
951
986
|
)
|
|
952
987
|
prev = cs = new_cs(cs.base_str, new_sgr)
|
|
953
988
|
xs.append(cs.as_ansi_type(ansi_typ))
|
|
@@ -1009,9 +1044,10 @@ def read_ans[AnyStr: (str, bytes)](__path: PathLike[AnyStr] | AnyStr, encoding='
|
|
|
1009
1044
|
class AnsiImage:
|
|
1010
1045
|
|
|
1011
1046
|
@classmethod
|
|
1012
|
-
def open[
|
|
1013
|
-
|
|
1014
|
-
|
|
1047
|
+
def open[AnyStr: (
|
|
1048
|
+
str,
|
|
1049
|
+
bytes,
|
|
1050
|
+
)](
|
|
1015
1051
|
cls,
|
|
1016
1052
|
fp: PathLike[AnyStr] | AnyStr,
|
|
1017
1053
|
shape: TupleOf2[int],
|
|
@@ -1076,7 +1112,7 @@ class AnsiImage:
|
|
|
1076
1112
|
if not __table:
|
|
1077
1113
|
return self
|
|
1078
1114
|
table = {
|
|
1079
|
-
k:
|
|
1115
|
+
k: v if v not in frozenset(x for c in ' \t\n\r\v\f' for x in (c, ord(c))) else ' '
|
|
1080
1116
|
for (k, v) in __table.items()
|
|
1081
1117
|
if k != ord('\n')
|
|
1082
1118
|
}
|
|
@@ -1098,8 +1134,8 @@ class AnsiImage:
|
|
|
1098
1134
|
return getattr(self, attr_name)[0]
|
|
1099
1135
|
lines = []
|
|
1100
1136
|
for line in self.data:
|
|
1101
|
-
buffer = []
|
|
1102
1137
|
if line:
|
|
1138
|
+
buffer = []
|
|
1103
1139
|
initial = line[0]
|
|
1104
1140
|
for s in line[1:]:
|
|
1105
1141
|
if s.ansi_partition()[::2] == initial.ansi_partition()[::2]:
|
|
@@ -1109,7 +1145,7 @@ class AnsiImage:
|
|
|
1109
1145
|
initial = s
|
|
1110
1146
|
buffer.append(initial)
|
|
1111
1147
|
lines.append(''.join(buffer))
|
|
1112
|
-
setattr(self, attr_name, ('\n'.join(lines) + SGR_RESET, self.shape))
|
|
1148
|
+
setattr(self, attr_name, ('\n'.join(lines) + SGR_RESET.decode(), self.shape))
|
|
1113
1149
|
return getattr(self, attr_name)[0]
|
|
1114
1150
|
|
|
1115
1151
|
|
chromatic/color/core.py
CHANGED
|
@@ -61,7 +61,8 @@ from .._typing import (
|
|
|
61
61
|
os.system('')
|
|
62
62
|
|
|
63
63
|
CSI: Final[bytes] = b'['
|
|
64
|
-
SGR_RESET: Final[
|
|
64
|
+
SGR_RESET: Final[bytes] = b'[0m'
|
|
65
|
+
SGR_RESET_S: Final[str] = SGR_RESET.decode()
|
|
65
66
|
|
|
66
67
|
# ansi color global lookups
|
|
67
68
|
# ansi 4bit {color code (int) ==> (key, RGB)}
|
|
@@ -503,7 +504,8 @@ def get_ansi_type(typ):
|
|
|
503
504
|
|
|
504
505
|
def rgb2ansi_escape(ret_format, mode, rgb):
|
|
505
506
|
ret_format = get_ansi_type(ret_format)
|
|
506
|
-
|
|
507
|
+
if len(rgb) != 3:
|
|
508
|
+
raise ValueError('length of RGB value is not 3')
|
|
507
509
|
try:
|
|
508
510
|
if ret_format is ansicolor4Bit:
|
|
509
511
|
return b'%d' % _ANSI16C_KV2I[mode, nearest_ansi_4bit_rgb(rgb)]
|
|
@@ -633,9 +635,11 @@ def _get_sgr_bitmask[_T: (bytes, bytearray, Buffer)](__x: _T) -> list[int]:
|
|
|
633
635
|
return allocated
|
|
634
636
|
|
|
635
637
|
|
|
636
|
-
def _iter_normalized_sgr[
|
|
637
|
-
|
|
638
|
-
|
|
638
|
+
def _iter_normalized_sgr[_T: (
|
|
639
|
+
Buffer,
|
|
640
|
+
SgrParamWrapper,
|
|
641
|
+
int,
|
|
642
|
+
)](__iter: Buffer | Iterable[_T]) -> Iterator[AnsiColorFormat | int]:
|
|
639
643
|
if isinstance(__iter, Buffer):
|
|
640
644
|
yield from _get_sgr_bitmask(__iter)
|
|
641
645
|
else:
|
|
@@ -894,7 +898,8 @@ class SgrSequence(Sequence[SgrParamWrapper]):
|
|
|
894
898
|
if ansi_type is None:
|
|
895
899
|
is_diff_ansi_typ = lambda _: False
|
|
896
900
|
else:
|
|
897
|
-
|
|
901
|
+
if ansi_type not in _ANSI_COLOR_TYPES:
|
|
902
|
+
raise TypeError
|
|
898
903
|
is_diff_ansi_typ = lambda v: type(v) is not ansi_type
|
|
899
904
|
|
|
900
905
|
for x in _iter_sgr(__iter):
|
|
@@ -1013,15 +1018,17 @@ _VALID_KEYS: frozenset[str] = unionize(
|
|
|
1013
1018
|
)
|
|
1014
1019
|
|
|
1015
1020
|
|
|
1016
|
-
def _solve_color_spec[
|
|
1017
|
-
|
|
1018
|
-
|
|
1021
|
+
def _solve_color_spec[_ColorSpec: (
|
|
1022
|
+
_CSpecType,
|
|
1023
|
+
SgrSequence,
|
|
1024
|
+
)](color_spec: Optional[_ColorSpec], ansi_type: AnsiColorType):
|
|
1019
1025
|
keys: list[str] = ['bg', 'fg']
|
|
1020
1026
|
|
|
1021
1027
|
def resolve(value, *, key=None):
|
|
1022
1028
|
nonlocal keys
|
|
1023
1029
|
if key is not None:
|
|
1024
|
-
|
|
1030
|
+
if key not in _VALID_KEYS:
|
|
1031
|
+
raise ValueError(f"expected one of {_VALID_KEYS}, got {key!r}")
|
|
1025
1032
|
if key in keys:
|
|
1026
1033
|
keys.remove(key)
|
|
1027
1034
|
match value:
|
|
@@ -1031,7 +1038,8 @@ def _solve_color_spec[
|
|
|
1031
1038
|
r, g, b = (x & 0xFF for x in rgb)
|
|
1032
1039
|
yield (key or keys.pop(), (r, g, b))
|
|
1033
1040
|
case np.ndarray() as colors:
|
|
1034
|
-
|
|
1041
|
+
if colors.shape[-1] % 3 != 0:
|
|
1042
|
+
raise ValueError('array does not contain RGB values')
|
|
1035
1043
|
it = np.uint8(colors).flat
|
|
1036
1044
|
for _ in range(colors.ndim):
|
|
1037
1045
|
yield (key or keys.pop(), tuple(int(next(it)) for _ in range(3)))
|
|
@@ -1076,7 +1084,7 @@ def _get_color_str_vars(
|
|
|
1076
1084
|
if csi_count := color_spec.count(CSI):
|
|
1077
1085
|
if csi_count > 1:
|
|
1078
1086
|
color_spec, _, byte_str = (
|
|
1079
|
-
color_spec.removeprefix(CSI).removesuffix(SGR_RESET
|
|
1087
|
+
color_spec.removeprefix(CSI).removesuffix(SGR_RESET).partition(b'm')
|
|
1080
1088
|
)
|
|
1081
1089
|
if color_spec.count(CSI) > 1:
|
|
1082
1090
|
raise ValueError(
|
|
@@ -1095,26 +1103,12 @@ def _get_color_str_vars(
|
|
|
1095
1103
|
return sgr_params, base_str
|
|
1096
1104
|
|
|
1097
1105
|
|
|
1098
|
-
class _ColorStrWeakVars(TypedDict, total=False):
|
|
1099
|
-
_base_str_: str
|
|
1100
|
-
_sgr_: SgrSequence
|
|
1101
|
-
_no_reset_: bool
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
1106
|
class _AnsiBytesGetter:
|
|
1105
1107
|
|
|
1106
1108
|
def __get__(self, instance: Optional['ColorStr'], objtype=None):
|
|
1107
1109
|
if instance is None:
|
|
1108
1110
|
return
|
|
1109
|
-
return instance
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
class _SgrParamsGetter:
|
|
1113
|
-
|
|
1114
|
-
def __get__(self, instance: Optional['ColorStr'], objtype=None):
|
|
1115
|
-
if instance is None:
|
|
1116
|
-
return
|
|
1117
|
-
return instance._sgr_._sgr_params_
|
|
1111
|
+
return bytes(getattr(instance, '_sgr_'))
|
|
1118
1112
|
|
|
1119
1113
|
|
|
1120
1114
|
class _ColorDictGetter:
|
|
@@ -1122,7 +1116,7 @@ class _ColorDictGetter:
|
|
|
1122
1116
|
def __get__(self, instance: Optional['ColorStr'], objtype=None):
|
|
1123
1117
|
if instance is None:
|
|
1124
1118
|
return
|
|
1125
|
-
return {k: Color.from_rgb(v) for k, v in instance
|
|
1119
|
+
return {k: Color.from_rgb(v) for k, v in getattr(instance, '_sgr_').rgb_dict.items()}
|
|
1126
1120
|
|
|
1127
1121
|
|
|
1128
1122
|
class ColorStr(str):
|
|
@@ -1130,16 +1124,16 @@ class ColorStr(str):
|
|
|
1130
1124
|
def _weak_var_update(self, **kwargs):
|
|
1131
1125
|
if kwargs.keys().isdisjoint(inst_vars := vars(self)):
|
|
1132
1126
|
raise ValueError(f"unexpected keys: {kwargs.keys() - inst_vars.keys()}") from None
|
|
1133
|
-
sgr = kwargs.get('_sgr_', self
|
|
1134
|
-
base_str = kwargs.get('_base_str_', self.
|
|
1135
|
-
suffix =
|
|
1127
|
+
sgr = kwargs.get('_sgr_', getattr(self, '_sgr_'))
|
|
1128
|
+
base_str = kwargs.get('_base_str_', self.base_str)
|
|
1129
|
+
suffix = SGR_RESET_S if kwargs.get('_reset_', self.reset) else ''
|
|
1136
1130
|
inst = super().__new__(ColorStr, ''.join([str(sgr), base_str, suffix]))
|
|
1137
|
-
inst.__dict__ |=
|
|
1131
|
+
inst.__dict__ |= inst_vars | kwargs
|
|
1138
1132
|
return cast(ColorStr, inst)
|
|
1139
1133
|
|
|
1140
1134
|
def ansi_partition(self):
|
|
1141
1135
|
"""Returns the 3-tuple: SGR sequence prefix, base string, SGR reset (or empty string)."""
|
|
1142
|
-
return str(self
|
|
1136
|
+
return str(getattr(self, '_sgr_')), self.base_str, SGR_RESET_S if self.reset else ''
|
|
1143
1137
|
|
|
1144
1138
|
def as_ansi_type(self, __ansi_type):
|
|
1145
1139
|
"""Convert all ANSI color codes in the :class:`ColorStr` to a single ANSI type.
|
|
@@ -1157,24 +1151,24 @@ class ColorStr(str):
|
|
|
1157
1151
|
|
|
1158
1152
|
"""
|
|
1159
1153
|
ansi_type = get_ansi_type(__ansi_type)
|
|
1160
|
-
if self
|
|
1154
|
+
if getattr(self, '_sgr_').is_color():
|
|
1161
1155
|
new_params = []
|
|
1162
1156
|
new_rgb = {}
|
|
1163
|
-
for p in self
|
|
1157
|
+
for p in getattr(self, '_sgr_'):
|
|
1164
1158
|
if p.is_color() and type(p._value_) is not ansi_type:
|
|
1165
1159
|
new_ansi = ansi_type(p._value_)
|
|
1166
1160
|
new_rgb |= new_ansi.rgb_dict
|
|
1167
1161
|
new_params.append(SgrParamWrapper(new_ansi))
|
|
1168
1162
|
else:
|
|
1169
1163
|
new_params.append(p)
|
|
1170
|
-
if new_params == self
|
|
1164
|
+
if new_params == list(getattr(self, '_sgr_')):
|
|
1171
1165
|
return self
|
|
1172
1166
|
new_sgr = SgrSequence()
|
|
1173
1167
|
for name, value in zip(('_sgr_params_', '_rgb_dict_'), (new_params, new_rgb)):
|
|
1174
1168
|
setattr(new_sgr, name, value)
|
|
1175
1169
|
inst = super().__new__(
|
|
1176
1170
|
type(self),
|
|
1177
|
-
''.join([str(new_sgr), self.
|
|
1171
|
+
''.join([str(new_sgr), self.base_str, SGR_RESET_S if self.reset else '']),
|
|
1178
1172
|
)
|
|
1179
1173
|
for name, value in {**vars(self), '_sgr_': new_sgr, '_ansi_type_': ansi_type}.items():
|
|
1180
1174
|
setattr(inst, name, value)
|
|
@@ -1241,7 +1235,7 @@ class ColorStr(str):
|
|
|
1241
1235
|
"""
|
|
1242
1236
|
if __value:
|
|
1243
1237
|
if isinstance(__value, ColorStr):
|
|
1244
|
-
kwargs = __value
|
|
1238
|
+
kwargs = getattr(__value, '_color_dict_')
|
|
1245
1239
|
else:
|
|
1246
1240
|
raise TypeError(
|
|
1247
1241
|
f"expected positional argument of type {ColorStr.__qualname__!r}, "
|
|
@@ -1255,7 +1249,7 @@ class ColorStr(str):
|
|
|
1255
1249
|
kwargs = {k: v if v is None else Color(v) for k, v in kwargs.items()}
|
|
1256
1250
|
except Exception as e:
|
|
1257
1251
|
raise e from None
|
|
1258
|
-
sgr = SgrSequence(self
|
|
1252
|
+
sgr = SgrSequence(getattr(self, '_sgr_'))
|
|
1259
1253
|
if bool(absolute):
|
|
1260
1254
|
del sgr.rgb_dict
|
|
1261
1255
|
sgr.rgb_dict = (self.ansi_format, kwargs)
|
|
@@ -1297,7 +1291,7 @@ class ColorStr(str):
|
|
|
1297
1291
|
Examples
|
|
1298
1292
|
--------
|
|
1299
1293
|
>>> # creating an empty ColorStr object
|
|
1300
|
-
>>> empty_cs = ColorStr(
|
|
1294
|
+
>>> empty_cs = ColorStr(reset=True)
|
|
1301
1295
|
>>> empty_cs.ansi
|
|
1302
1296
|
b''
|
|
1303
1297
|
|
|
@@ -1327,7 +1321,7 @@ class ColorStr(str):
|
|
|
1327
1321
|
return self
|
|
1328
1322
|
if any(not isinstance(x, int) or x in _ANSI256_KEY2I.values() for x in p):
|
|
1329
1323
|
raise ValueError
|
|
1330
|
-
new_sgr = SgrSequence(self
|
|
1324
|
+
new_sgr = SgrSequence(getattr(self, '_sgr_'))
|
|
1331
1325
|
for x in p:
|
|
1332
1326
|
if x in new_sgr:
|
|
1333
1327
|
new_sgr.pop(new_sgr.index(x))
|
|
@@ -1345,8 +1339,7 @@ class ColorStr(str):
|
|
|
1345
1339
|
else:
|
|
1346
1340
|
ansi_type = self.ansi_format
|
|
1347
1341
|
inst = super().__new__(
|
|
1348
|
-
type(self),
|
|
1349
|
-
''.join([str(new_sgr), self._base_str_, '' if self._no_reset_ else SGR_RESET]),
|
|
1342
|
+
type(self), ''.join([str(new_sgr), self.base_str, SGR_RESET_S if self.reset else ''])
|
|
1350
1343
|
)
|
|
1351
1344
|
inst.__dict__ |= {**vars(self), '_sgr_': new_sgr, '_ansi_type_': ansi_type}
|
|
1352
1345
|
return cast(ColorStr, inst)
|
|
@@ -1354,11 +1347,11 @@ class ColorStr(str):
|
|
|
1354
1347
|
def __add__(self, other):
|
|
1355
1348
|
if type(self) is type(other):
|
|
1356
1349
|
return self._weak_var_update(
|
|
1357
|
-
_sgr_=self
|
|
1358
|
-
_base_str_=''.join([self.
|
|
1350
|
+
_sgr_=getattr(self, '_sgr_') + other._sgr_,
|
|
1351
|
+
_base_str_=''.join([self.base_str, other._base_str_]),
|
|
1359
1352
|
)
|
|
1360
1353
|
if isinstance(other, str):
|
|
1361
|
-
return self._weak_var_update(_base_str_=''.join([self.
|
|
1354
|
+
return self._weak_var_update(_base_str_=''.join([self.base_str, other]))
|
|
1362
1355
|
if isinstance(other, SgrParameter):
|
|
1363
1356
|
return self.update_sgr(other)
|
|
1364
1357
|
if hasattr(other, '_sgr_'):
|
|
@@ -1373,10 +1366,10 @@ class ColorStr(str):
|
|
|
1373
1366
|
def __contains__(self, __key: str):
|
|
1374
1367
|
if type(__key) is not str:
|
|
1375
1368
|
return False
|
|
1376
|
-
if __key == str(self
|
|
1369
|
+
if __key == str(getattr(self, '_sgr_')):
|
|
1377
1370
|
return True
|
|
1378
|
-
if __key ==
|
|
1379
|
-
return
|
|
1371
|
+
if __key == SGR_RESET_S:
|
|
1372
|
+
return self.reset
|
|
1380
1373
|
return self.base_str.__contains__(__key)
|
|
1381
1374
|
|
|
1382
1375
|
def __eq__(self, other):
|
|
@@ -1395,7 +1388,7 @@ class ColorStr(str):
|
|
|
1395
1388
|
return self._weak_var_update(_base_str_=self.base_str[__key])
|
|
1396
1389
|
|
|
1397
1390
|
def __hash__(self):
|
|
1398
|
-
return
|
|
1391
|
+
return ''.join(self.ansi_partition()).__hash__()
|
|
1399
1392
|
|
|
1400
1393
|
# noinspection PyUnusedLocal
|
|
1401
1394
|
def __init__(self, obj=None, color_spec=None, **kwargs):
|
|
@@ -1429,9 +1422,9 @@ class ColorStr(str):
|
|
|
1429
1422
|
* ANSI format can also be changed on instances using :meth:`ColorStr.as_ansi_type`
|
|
1430
1423
|
* Reformatting recursively applies to `alt_spec` if `alt_spec` is not None
|
|
1431
1424
|
|
|
1432
|
-
|
|
1433
|
-
If
|
|
1434
|
-
Default is
|
|
1425
|
+
reset : bool
|
|
1426
|
+
If False, create the :class:`ColorStr` without concatenating an SGR 'reset' sequence.
|
|
1427
|
+
Default is True (new instances get concatenated with reset sequences).
|
|
1435
1428
|
|
|
1436
1429
|
Returns
|
|
1437
1430
|
-------
|
|
@@ -1479,10 +1472,10 @@ class ColorStr(str):
|
|
|
1479
1472
|
def __matmul__(self, other):
|
|
1480
1473
|
"""Return a new :class:`ColorStr` with the base string of `self` and colors of `other`"""
|
|
1481
1474
|
if type(self) is type(other):
|
|
1482
|
-
return self._weak_var_update(_sgr_=other
|
|
1475
|
+
return self._weak_var_update(_sgr_=getattr(other, '_sgr_'), _reset_=other.reset)
|
|
1483
1476
|
raise TypeError(
|
|
1484
1477
|
'unsupported operand type(s) for @: '
|
|
1485
|
-
"{!r} and {!r}".format(*(type
|
|
1478
|
+
"{.__qualname__!r} and {.__qualname__!r}".format(*map(type, (self, other)))
|
|
1486
1479
|
)
|
|
1487
1480
|
|
|
1488
1481
|
def __mod__(self, __value):
|
|
@@ -1493,8 +1486,8 @@ class ColorStr(str):
|
|
|
1493
1486
|
|
|
1494
1487
|
def __invert__(self):
|
|
1495
1488
|
"""Return a copy of `self` with inverted colors (XORed by '0xFFFFFF')"""
|
|
1496
|
-
sgr = SgrSequence(self
|
|
1497
|
-
sgr.rgb_dict = (self.ansi_format, {k: ~v for k, v in self
|
|
1489
|
+
sgr = SgrSequence(getattr(self, '_sgr_'))
|
|
1490
|
+
sgr.rgb_dict = (self.ansi_format, {k: ~v for k, v in getattr(self, '_color_dict_').items()})
|
|
1498
1491
|
return self._weak_var_update(_sgr_=sgr)
|
|
1499
1492
|
|
|
1500
1493
|
def __new__(cls, obj=None, color_spec=None, **kwargs):
|
|
@@ -1507,9 +1500,11 @@ class ColorStr(str):
|
|
|
1507
1500
|
for name, value in vars(color_spec).items():
|
|
1508
1501
|
setattr(inst, name, value)
|
|
1509
1502
|
return inst
|
|
1510
|
-
d = {
|
|
1511
|
-
|
|
1512
|
-
|
|
1503
|
+
d = {
|
|
1504
|
+
'_ansi_type_': ansi_type or DEFAULT_ANSI,
|
|
1505
|
+
'_reset_': bool(kwargs.get('reset', True)),
|
|
1506
|
+
}
|
|
1507
|
+
suffix = SGR_RESET_S if d['_reset_'] else ''
|
|
1513
1508
|
if obj is not None:
|
|
1514
1509
|
if not isinstance(obj, str):
|
|
1515
1510
|
obj = str(obj, encoding='ansi') if isinstance(obj, Buffer) else str(obj)
|
|
@@ -1559,48 +1554,47 @@ class ColorStr(str):
|
|
|
1559
1554
|
}
|
|
1560
1555
|
if not diff_dict:
|
|
1561
1556
|
return self
|
|
1562
|
-
sgr = SgrSequence(self
|
|
1557
|
+
sgr = SgrSequence(getattr(self, '_sgr_'))
|
|
1563
1558
|
sgr.rgb_dict = self.ansi_format, diff_dict
|
|
1564
1559
|
return self._weak_var_update(_sgr_=sgr)
|
|
1565
1560
|
|
|
1566
1561
|
_ansi_ = _AnsiBytesGetter()
|
|
1567
1562
|
_color_dict_ = _ColorDictGetter()
|
|
1568
|
-
_sgr_params_ = _SgrParamsGetter()
|
|
1569
1563
|
|
|
1570
1564
|
@property
|
|
1571
1565
|
def ansi(self):
|
|
1572
|
-
return self
|
|
1566
|
+
return getattr(self, '_ansi_')
|
|
1573
1567
|
|
|
1574
1568
|
@property
|
|
1575
1569
|
def ansi_format(self):
|
|
1576
|
-
return self
|
|
1570
|
+
return getattr(self, '_ansi_type_')
|
|
1577
1571
|
|
|
1578
1572
|
@property
|
|
1579
1573
|
def base_str(self):
|
|
1580
1574
|
"""The non-ANSI part of the string"""
|
|
1581
|
-
return self
|
|
1575
|
+
return getattr(self, '_base_str_')
|
|
1582
1576
|
|
|
1583
1577
|
@property
|
|
1584
1578
|
def bg(self):
|
|
1585
1579
|
"""Background color"""
|
|
1586
|
-
return self
|
|
1580
|
+
return getattr(self, '_color_dict_').get('bg')
|
|
1587
1581
|
|
|
1588
1582
|
@property
|
|
1589
1583
|
def fg(self):
|
|
1590
1584
|
"""Foreground color"""
|
|
1591
|
-
return self
|
|
1585
|
+
return getattr(self, '_color_dict_').get('fg')
|
|
1592
1586
|
|
|
1593
1587
|
@property
|
|
1594
|
-
def
|
|
1595
|
-
return self
|
|
1588
|
+
def reset(self):
|
|
1589
|
+
return getattr(self, '_reset_')
|
|
1596
1590
|
|
|
1597
1591
|
@property
|
|
1598
1592
|
def rgb_dict(self):
|
|
1599
|
-
return {k: v.rgb for k, v in self
|
|
1593
|
+
return {k: v.rgb for k, v in getattr(self, '_color_dict_').items()}
|
|
1600
1594
|
|
|
1601
1595
|
|
|
1602
1596
|
def _color_str_to_mask(cs: ColorStr) -> tuple[SgrSequence, str]:
|
|
1603
|
-
return cs
|
|
1597
|
+
return getattr(cs, '_sgr_'), cs.base_str
|
|
1604
1598
|
|
|
1605
1599
|
|
|
1606
1600
|
class color_chain:
|
|
@@ -1728,7 +1722,7 @@ class color_chain:
|
|
|
1728
1722
|
|
|
1729
1723
|
def __str__(self):
|
|
1730
1724
|
return ''.join(
|
|
1731
|
-
str(ColorStr(base_str, color_spec=sgr, ansi_type=self._ansi_type_,
|
|
1725
|
+
str(ColorStr(base_str, color_spec=sgr, ansi_type=self._ansi_type_, reset=False))
|
|
1732
1726
|
for sgr, base_str in self.masks
|
|
1733
1727
|
)
|
|
1734
1728
|
|
chromatic/color/core.pyi
CHANGED
|
@@ -99,7 +99,7 @@ class ColorStr(str):
|
|
|
99
99
|
def __hash__(self) -> int: ...
|
|
100
100
|
def __init__(
|
|
101
101
|
self,
|
|
102
|
-
obj: object =
|
|
102
|
+
obj: object = ...,
|
|
103
103
|
color_spec: Union[_ColorSpec, ColorStr] = None,
|
|
104
104
|
**kwargs: Unpack[_ColorStrKwargs],
|
|
105
105
|
) -> None: ...
|
|
@@ -111,7 +111,7 @@ class ColorStr(str):
|
|
|
111
111
|
def __invert__(self) -> ColorStr: ...
|
|
112
112
|
def __new__(
|
|
113
113
|
cls,
|
|
114
|
-
obj: object =
|
|
114
|
+
obj: object = ...,
|
|
115
115
|
color_spec: Union[_ColorSpec, ColorStr] = None,
|
|
116
116
|
**kwargs: Unpack[_ColorStrKwargs],
|
|
117
117
|
) -> ColorStr: ...
|
|
@@ -136,7 +136,7 @@ class ColorStr(str):
|
|
|
136
136
|
@property
|
|
137
137
|
def fg(self) -> Optional[Color]: ...
|
|
138
138
|
@property
|
|
139
|
-
def
|
|
139
|
+
def reset(self) -> bool: ...
|
|
140
140
|
@property
|
|
141
141
|
def rgb_dict(self) -> dict[ColorDictKeys, Int3Tuple]: ...
|
|
142
142
|
|
|
@@ -277,9 +277,13 @@ class SgrSequence(Sequence[SgrParamWrapper]):
|
|
|
277
277
|
def __getitem__(self, item: SupportsIndex) -> SgrParamWrapper: ...
|
|
278
278
|
@overload
|
|
279
279
|
def __getitem__(self, item: slice) -> list[SgrParamWrapper]: ...
|
|
280
|
-
def __init__[
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
def __init__[_T: (
|
|
281
|
+
SgrParamWrapper,
|
|
282
|
+
Buffer,
|
|
283
|
+
int,
|
|
284
|
+
), _AnsiType: type[AnsiColorFormat]](
|
|
285
|
+
self, __iter: Iterable[_T] = None, *, ansi_type: _AnsiType = None
|
|
286
|
+
) -> None: ...
|
|
283
287
|
def __iter__(self) -> Iterator[SgrParamWrapper]: ...
|
|
284
288
|
def __len__(self) -> int: ...
|
|
285
289
|
def __radd__[_T: (SgrSequence, str)](self, other: _T) -> _T: ...
|
|
@@ -307,9 +311,9 @@ class SgrSequence(Sequence[SgrParamWrapper]):
|
|
|
307
311
|
|
|
308
312
|
# noinspection PyUnresolvedReferences
|
|
309
313
|
@rgb_dict.setter
|
|
310
|
-
def rgb_dict[
|
|
311
|
-
|
|
312
|
-
|
|
314
|
+
def rgb_dict[_AnsiColorType: type[AnsiColorFormat]](
|
|
315
|
+
self, __value: tuple[_AnsiColorType, dict[ColorDictKeys, Optional[Color]]]
|
|
316
|
+
) -> None: ...
|
|
313
317
|
|
|
314
318
|
class _ColorDict(TypedDict, total=False):
|
|
315
319
|
bg: Optional[Color | AnsiColorFormat]
|
|
@@ -317,15 +321,16 @@ class _ColorDict(TypedDict, total=False):
|
|
|
317
321
|
|
|
318
322
|
class _ColorStrKwargs(TypedDict, total=False):
|
|
319
323
|
ansi_type: Optional[AnsiColorAlias | type[AnsiColorFormat]]
|
|
320
|
-
|
|
324
|
+
reset: bool
|
|
321
325
|
|
|
322
326
|
class _ColorStrWeakVars(TypedDict, total=False):
|
|
323
327
|
_base_str_: str
|
|
324
|
-
|
|
328
|
+
_reset_: bool
|
|
325
329
|
_sgr_: SgrSequence
|
|
326
330
|
|
|
327
|
-
CSI: Final[bytes]
|
|
328
|
-
SGR_RESET: Final[
|
|
331
|
+
CSI: Final[bytes]
|
|
332
|
+
SGR_RESET: Final[bytes]
|
|
333
|
+
SGR_RESET_S: Final[str]
|
|
329
334
|
DEFAULT_ANSI: Final[type[ansicolor8Bit | ansicolor4Bit]]
|
|
330
335
|
|
|
331
336
|
_ANSI16C_BRIGHT: Final[frozenset[int]]
|
chromatic/color/palette.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from functools import lru_cache, update_wrapper
|
|
1
|
+
from functools import lru_cache, update_wrapper, wraps
|
|
2
2
|
from inspect import getfullargspec, getmodule, isbuiltin, signature
|
|
3
3
|
from types import FunctionType, MappingProxyType
|
|
4
4
|
from typing import Callable, Iterator, Sequence, TYPE_CHECKING, Union, cast, dataclass_transform
|
|
@@ -109,16 +109,18 @@ def _check_if_ns_member(cls: type) -> Callable[[str], bool]:
|
|
|
109
109
|
return lambda x: member_type == anno_dict.get(x)
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
def _ns_from_iter[
|
|
113
|
-
_KT, _VT
|
|
114
|
-
|
|
115
|
-
[type[DynamicNamespace[_VT]]], type[DynamicNamespace[_VT]]
|
|
116
|
-
]:
|
|
112
|
+
def _ns_from_iter[_KT, _VT](
|
|
113
|
+
__iter: Iterator[_KT] | Callable[[], Iterator[_KT]], member_type: type[_VT] = object
|
|
114
|
+
) -> Callable[[type[DynamicNamespace[_VT]]], type[DynamicNamespace[_VT]]]:
|
|
117
115
|
def decorator(cls: type[DynamicNamespace[_VT]]):
|
|
118
116
|
anno = cls.__annotations__
|
|
119
117
|
type_params = cls.__type_params__
|
|
120
118
|
m_iter = __iter() if callable(__iter) else iter(__iter)
|
|
121
|
-
members: Iterator[_KT] =
|
|
119
|
+
members: Iterator[_KT] = (
|
|
120
|
+
m_iter
|
|
121
|
+
if (member_type is object or not isinstance(member_type, type))
|
|
122
|
+
else map(member_type, m_iter)
|
|
123
|
+
)
|
|
122
124
|
d = dict(zip((k for k, v in anno.items() if v in type_params), members))
|
|
123
125
|
cls.__init__ = update_wrapper(
|
|
124
126
|
lambda *args, **kwargs: cls.__base__.__init__(*args, **(kwargs | d)), cls.__init__
|
|
@@ -430,73 +432,73 @@ class _color_ns_getter:
|
|
|
430
432
|
) from None
|
|
431
433
|
|
|
432
434
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
color_ns = cast(MappingProxyType[str, Int3Tuple], _color_ns_getter())
|
|
439
|
-
|
|
440
|
-
__signature__ = _dummy_signature
|
|
435
|
+
def rgb_dispatch[**P, R](
|
|
436
|
+
__f: Callable[P, R] = None, /, *, var_names: Sequence[str] = ()
|
|
437
|
+
) -> Callable[P, R]:
|
|
438
|
+
dummy_func = lambda *args, **kwargs: ...
|
|
439
|
+
[rgb_args, variadic] = [set[str]() for _ in range(2)]
|
|
441
440
|
|
|
442
|
-
def
|
|
443
|
-
|
|
444
|
-
|
|
441
|
+
def fix_signature():
|
|
442
|
+
nonlocal var_names, rgb_args, variadic
|
|
443
|
+
if isinstance(var_names, str):
|
|
444
|
+
var_names = (var_names,)
|
|
445
445
|
if not callable(__f):
|
|
446
446
|
raise ValueError
|
|
447
|
-
self._func = __f
|
|
448
447
|
try:
|
|
449
|
-
argspec = getfullargspec(
|
|
450
|
-
sig = signature(
|
|
448
|
+
argspec = getfullargspec(__f)
|
|
449
|
+
sig = signature(__f)
|
|
451
450
|
except TypeError:
|
|
452
|
-
if not (isbuiltin(
|
|
451
|
+
if not (isbuiltin(__f) or getattr(__f, '__module__', '') == 'builtins'):
|
|
453
452
|
raise
|
|
454
|
-
argspec = getfullargspec(
|
|
455
|
-
sig =
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
all_args =
|
|
459
|
-
|
|
460
|
-
{'*': argspec.varargs, '**': argspec.varkw}.get(arg) or arg for arg in
|
|
453
|
+
argspec = getfullargspec(dummy_func)
|
|
454
|
+
sig = signature(dummy_func)
|
|
455
|
+
variadic = {argspec.varargs, argspec.varkw}
|
|
456
|
+
variadic.discard(None)
|
|
457
|
+
all_args = variadic.union(argspec.args + argspec.kwonlyargs)
|
|
458
|
+
rgb_args = all_args.intersection(
|
|
459
|
+
{'*': argspec.varargs, '**': argspec.varkw}.get(arg) or arg for arg in var_names
|
|
461
460
|
)
|
|
462
|
-
if not
|
|
461
|
+
if not rgb_args:
|
|
463
462
|
keys = frozenset({'fg', 'bg'})
|
|
464
463
|
for arg in all_args:
|
|
465
464
|
if (arg[:2] in keys) or (arg[-2:] in keys):
|
|
466
|
-
|
|
467
|
-
|
|
465
|
+
rgb_args.add(arg)
|
|
466
|
+
variadic &= rgb_args
|
|
468
467
|
parameters = []
|
|
469
468
|
for name, param in sig.parameters.items():
|
|
470
|
-
if name not in
|
|
469
|
+
if name not in rgb_args or param.annotation is param.empty:
|
|
471
470
|
parameters.append(param)
|
|
472
471
|
else:
|
|
473
472
|
anno = param.annotation
|
|
474
473
|
parameters.append(
|
|
475
474
|
param.replace(
|
|
476
475
|
annotation=str
|
|
477
|
-
| eval(getattr(anno, '__name__', str(anno)), getmodule(
|
|
476
|
+
| eval(getattr(anno, '__name__', str(anno)), getmodule(__f).__dict__)
|
|
478
477
|
)
|
|
479
478
|
)
|
|
480
|
-
|
|
481
|
-
|
|
479
|
+
return sig.replace(parameters=parameters)
|
|
480
|
+
|
|
481
|
+
__f.__signature__ = fix_signature()
|
|
482
|
+
color_ns = cast(MappingProxyType[str, Int3Tuple], _color_ns_getter())
|
|
482
483
|
|
|
483
|
-
|
|
484
|
-
|
|
484
|
+
@wraps(__f)
|
|
485
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
486
|
+
bound = getattr(__f, '__signature__').bind(*args, **kwargs)
|
|
485
487
|
bound.apply_defaults()
|
|
486
488
|
for arg, value in bound.arguments.items():
|
|
487
|
-
if arg not in
|
|
489
|
+
if arg not in rgb_args:
|
|
488
490
|
continue
|
|
489
|
-
if arg in
|
|
491
|
+
if arg in variadic:
|
|
490
492
|
bound.arguments[arg] = (
|
|
491
|
-
tuple(
|
|
493
|
+
tuple(color_ns[v] if v in color_ns else v for v in value)
|
|
492
494
|
if isinstance(value, tuple)
|
|
493
|
-
else {
|
|
494
|
-
k: self.color_ns[v] if v in self.color_ns else v for k, v in value.items()
|
|
495
|
-
}
|
|
495
|
+
else {k: color_ns[v] if v in color_ns else v for k, v in value.items()}
|
|
496
496
|
)
|
|
497
|
-
elif value in
|
|
498
|
-
bound.arguments[arg] =
|
|
499
|
-
return
|
|
497
|
+
elif value in color_ns:
|
|
498
|
+
bound.arguments[arg] = color_ns[value]
|
|
499
|
+
return __f(*bound.args, **bound.kwargs)
|
|
500
|
+
|
|
501
|
+
return wrapper
|
|
500
502
|
|
|
501
503
|
|
|
502
504
|
# fmt: off
|
chromatic/color/palette.pyi
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
__all__ = ['Back', 'ColorNamespace', 'Fore', 'Style', 'rgb_dispatch', 'named_color']
|
|
2
2
|
|
|
3
|
-
import inspect
|
|
4
3
|
from collections.abc import Sequence
|
|
5
|
-
from types import
|
|
6
|
-
from typing import Callable, Iterator, Literal, TypeAlias, TypeVar, Union
|
|
4
|
+
from types import FunctionType
|
|
5
|
+
from typing import Callable, Iterator, Literal, TypeAlias, TypeVar, Union
|
|
7
6
|
|
|
8
7
|
from .core import Color, ColorStr, color_chain
|
|
9
8
|
from .._typing import Int3Tuple
|
|
@@ -298,17 +297,7 @@ named_color: Union[
|
|
|
298
297
|
dict[Literal['24b'], Callable[[_ColorName], Color]],
|
|
299
298
|
]
|
|
300
299
|
|
|
301
|
-
|
|
302
|
-
color_ns: MappingProxyType[str, Int3Tuple]
|
|
303
|
-
|
|
304
|
-
__signature__: inspect.Signature
|
|
305
|
-
|
|
306
|
-
@overload
|
|
307
|
-
def __new__(cls, __f: Callable[P, R], /, *, args: Sequence[str] = ()):
|
|
308
|
-
return __f
|
|
309
|
-
|
|
310
|
-
@property
|
|
311
|
-
def __wrapped__(self) -> Callable[P, R]: ...
|
|
300
|
+
def rgb_dispatch[F: (type, FunctionType)](__f: F, /, *, var_names: Sequence[str] = ()) -> F: ...
|
|
312
301
|
|
|
313
302
|
Back = AnsiBack()
|
|
314
303
|
Fore = AnsiFore()
|
chromatic/data/__init__.py
CHANGED
|
@@ -36,12 +36,8 @@ def _build_config():
|
|
|
36
36
|
f: SupportsWrite
|
|
37
37
|
d: dict[Literal['fonts', 'images'] | Literal['__hash__'], dict[str, str] | str]
|
|
38
38
|
d = {'fonts': {}, 'images': {}, '__hash__': ''}
|
|
39
|
-
printable = ''.join(
|
|
40
|
-
|
|
41
|
-
)
|
|
42
|
-
for font_fp in (
|
|
43
|
-
(Path(fonts) / x).absolute() for x in filter(_is_ttf_ext, os.listdir(fonts))
|
|
44
|
-
):
|
|
39
|
+
printable = ''.join(c for c in printable if any((c.isalnum(), c.isidentifier(), c == '.')))
|
|
40
|
+
for font_fp in ((Path(fonts) / x).absolute() for x in filter(_is_ttf_ext, os.listdir(fonts))):
|
|
45
41
|
font_name = font_fp.stem
|
|
46
42
|
for c in set(font_name):
|
|
47
43
|
if c not in printable:
|
|
@@ -71,9 +67,7 @@ def _validate(**kwargs):
|
|
|
71
67
|
s1, s2 = (
|
|
72
68
|
set(
|
|
73
69
|
str(fp.relative_to(data_root))
|
|
74
|
-
for fp in (
|
|
75
|
-
(Path(subdir) / x).absolute() for x in filter(f, os.listdir(subdir))
|
|
76
|
-
)
|
|
70
|
+
for fp in ((Path(subdir) / x).absolute() for x in filter(f, os.listdir(subdir)))
|
|
77
71
|
)
|
|
78
72
|
for f, subdir in zip((_is_ttf_ext, _is_img_ext), (fonts, images))
|
|
79
73
|
)
|
|
@@ -119,9 +113,7 @@ def _create_font_enum() -> type['UserFont']:
|
|
|
119
113
|
def path(self):
|
|
120
114
|
return data_root / Path(_font_data_[self.name])
|
|
121
115
|
|
|
122
|
-
enum_cls = IntEnum(
|
|
123
|
-
'UserFont', {k: i for (i, k) in enumerate(sorted(_font_data_.keys()))}
|
|
124
|
-
)
|
|
116
|
+
enum_cls = IntEnum('UserFont', {k: i for (i, k) in enumerate(sorted(_font_data_.keys()))})
|
|
125
117
|
enum_cls.path = property(path)
|
|
126
118
|
return enum_cls
|
|
127
119
|
|
|
@@ -156,9 +148,7 @@ def register_user_font[AnyStr: (str, bytes)](__path: AnyStr | os.PathLike[AnyStr
|
|
|
156
148
|
if path_obj.is_symlink():
|
|
157
149
|
path_obj = path_obj.readlink()
|
|
158
150
|
if path_obj.suffix != '.ttf':
|
|
159
|
-
raise ValueError(
|
|
160
|
-
f"Expected '.ttf' file, " f"got filetype {path_obj.suffix!r} instead"
|
|
161
|
-
)
|
|
151
|
+
raise ValueError(f"Expected '.ttf' file, " f"got filetype {path_obj.suffix!r} instead")
|
|
162
152
|
from PIL.ImageFont import FreeTypeFont
|
|
163
153
|
|
|
164
154
|
try:
|
chromatic/demo.py
CHANGED
|
@@ -143,7 +143,7 @@ def named_colors():
|
|
|
143
143
|
)
|
|
144
144
|
buffer.append(xs)
|
|
145
145
|
for ln in buffer:
|
|
146
|
-
print(' | '.join(ln))
|
|
146
|
+
print(' | '.join(map(str, ln)))
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def color_table():
|
|
@@ -174,12 +174,12 @@ def color_table():
|
|
|
174
174
|
for c in colors
|
|
175
175
|
]
|
|
176
176
|
bg_colors = [ColorStr().recolor(bg=None)] + [c.recolor(fg=None, bg=c.fg) for c in fg_colors]
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
print('|'.join(f"{'%dbit' % n: {'>' if n > 9 else '^'}{spacing - 1}}" for n in (4, 8, 24)))
|
|
178
|
+
suffix = '\x1b[0m' if sys.stdout.isatty() else ''
|
|
179
179
|
for row in fg_colors:
|
|
180
180
|
for col in bg_colors:
|
|
181
181
|
for typ in ansi_types:
|
|
182
|
-
print(row.as_ansi_type(typ).recolor(bg=col.bg), end=
|
|
182
|
+
print(row.as_ansi_type(typ).recolor(bg=col.bg), end=suffix)
|
|
183
183
|
print()
|
|
184
184
|
print('\nstyles:')
|
|
185
185
|
print()
|
|
@@ -195,7 +195,7 @@ def color_table():
|
|
|
195
195
|
for style in style_params:
|
|
196
196
|
print(
|
|
197
197
|
ColorStr('.'.join([SgrParameter.__qualname__, style.name])).update_sgr(style),
|
|
198
|
-
end=
|
|
198
|
+
end=suffix + (' ' * 4),
|
|
199
199
|
)
|
|
200
200
|
print()
|
|
201
201
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: ANSI art image processing and colored terminal text
|
|
5
5
|
Author: crypt0lith
|
|
6
6
|
License: MIT License
|
|
@@ -27,20 +27,21 @@ License: MIT License
|
|
|
27
27
|
Project-URL: Homepage, https://github.com/crypt0lith/chromatic
|
|
28
28
|
Keywords: ansi,ascii,art,font,image,terminal,parser
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
31
|
Classifier: Operating System :: OS Independent
|
|
31
32
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
32
33
|
Classifier: Typing :: Typed
|
|
33
34
|
Requires-Python: >=3.12
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
License-File: LICENSE
|
|
36
|
-
Requires-Dist: fonttools~=4.51.0
|
|
37
37
|
Requires-Dist: networkx~=3.4.2
|
|
38
|
-
Requires-Dist: numpy~=2.
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist: scikit-image~=0.25.
|
|
42
|
-
Requires-Dist: scikit-learn~=1.
|
|
43
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: numpy~=2.2.6
|
|
39
|
+
Requires-Dist: pillow~=11.2.1
|
|
40
|
+
Requires-Dist: opencv-python~=4.11.0.86
|
|
41
|
+
Requires-Dist: scikit-image~=0.25.2
|
|
42
|
+
Requires-Dist: scikit-learn~=1.6.1
|
|
43
|
+
Requires-Dist: fonttools~=4.58.0
|
|
44
|
+
Requires-Dist: scipy~=1.15.3
|
|
44
45
|
Dynamic: license-file
|
|
45
46
|
|
|
46
47
|

|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
chromatic/__init__.py,sha256=
|
|
2
|
-
chromatic/_typing.py,sha256=
|
|
3
|
-
chromatic/_version.py,sha256=
|
|
4
|
-
chromatic/demo.py,sha256=
|
|
1
|
+
chromatic/__init__.py,sha256=qDsFcpBycwOTojKMKlpyfkj0pj3wUFDswZtxP2EDSzg,3309
|
|
2
|
+
chromatic/_typing.py,sha256=aPdYI3nFpkGArErzZzv73Rf-Bur3WOsaRyfy8CbWRtE,14714
|
|
3
|
+
chromatic/_version.py,sha256=UC90NvqLUDpj_FE8q0wCmEOBTFqTc2wo52DnLF1e7lU,532
|
|
4
|
+
chromatic/demo.py,sha256=WmtHsJ96MvepLwXBIo8XVcalgAw5P5nlb_TZLRN45qE,12744
|
|
5
5
|
chromatic/ascii/__init__.py,sha256=Ic3bqfqr0uAJd6vB4kToDookvZVr8Nrkyy3speiH5oM,164
|
|
6
|
-
chromatic/ascii/_array.py,sha256=
|
|
6
|
+
chromatic/ascii/_array.py,sha256=ZLNU1x1Jabz9b9Tky6eKKLQyM5gt3tBrks7eegIbtu8,39270
|
|
7
7
|
chromatic/ascii/_curses.py,sha256=Mz-_FgFUvACjMZyFoy1LXpxJzg5ekcyjMV8fYHXHcpA,3632
|
|
8
8
|
chromatic/ascii/_glyph_proc.py,sha256=VVRMFRu4oT7iH-q1MMgmZAnuQ0hbSrmtqtt9q3JnOLQ,2865
|
|
9
9
|
chromatic/color/__init__.py,sha256=8dnpWqbmEciFgJrQpUC0gV9M5D1bKIF7d9Sl4mj0x7g,185
|
|
10
10
|
chromatic/color/colorconv.py,sha256=2FygfpgNX6pfo47DhXNqwA46Nnpw8TbmCD1ThuIdZn4,8400
|
|
11
|
-
chromatic/color/core.py,sha256=
|
|
12
|
-
chromatic/color/core.pyi,sha256=
|
|
11
|
+
chromatic/color/core.py,sha256=vTAHdUY4oZBAgQIz0vaisapasLJab9lVE1emr6FWF8c,59732
|
|
12
|
+
chromatic/color/core.pyi,sha256=nQnfzmA7bPY9rSr7EC9tXrJ_R4NST9VD_6yqZTBxixU,12045
|
|
13
13
|
chromatic/color/iterators.py,sha256=v-JirP7ZvcDzsccD5Cctwsnu3qndCjOolGliAqN0azU,6099
|
|
14
|
-
chromatic/color/palette.py,sha256=
|
|
15
|
-
chromatic/color/palette.pyi,sha256=
|
|
16
|
-
chromatic/data/__init__.py,sha256=
|
|
14
|
+
chromatic/color/palette.py,sha256=Fz_mC7IUeI3-YF23aJPpF5RKVLOsQgWe17Lfq5uGplo,18802
|
|
15
|
+
chromatic/color/palette.pyi,sha256=Umdp_Ichm3-avs-bkwv3Nwb9lyquFOdEtlAhwuqrMGE,10252
|
|
16
|
+
chromatic/data/__init__.py,sha256=6-nmWy2dSlXblkwbtAmsg55HXmVnAGBSOJYb0j7j098,6538
|
|
17
17
|
chromatic/data/__init__.pyi,sha256=AZGa46n1Hck3xHN91Mmm7Qibg--CYweKJMMFPHH6yY8,556
|
|
18
18
|
chromatic/data/fonts/IBM_VGA_437_8x16.ttf,sha256=qMdn-pJWJNKNmHnDoDqGIE94vOTezaCiBv0VK92QbJQ,50124
|
|
19
19
|
chromatic/data/fonts/consolas.ttf,sha256=xubOgRn91H7GpUSaCOLSrX9B6gMUOq4ZMGjtn6WOrrw,459180
|
|
@@ -21,8 +21,8 @@ chromatic/data/images/butterfly.jpg,sha256=rnSfMVotSgpTNRPQ9CzYpDp8S8lkCAJ5ZySRr
|
|
|
21
21
|
chromatic/data/images/escher.png,sha256=FvAC-WOkUVIqHBxasfgCJbH1CN1IwLE-JyMiATOEKFc,132667
|
|
22
22
|
chromatic/data/images/goblin_virus.png,sha256=ygs19t4ZeZYsbiRFXjRyEz8OtgC9Zyl8owX0O-5n40o,8849
|
|
23
23
|
chromatic/data/images/hotdog.jpg,sha256=b0vnoQCarhq-gFu3df512qPTvauvF1Oa0oXVO9msEFc,60823
|
|
24
|
-
chromatic_python-0.2.
|
|
25
|
-
chromatic_python-0.2.
|
|
26
|
-
chromatic_python-0.2.
|
|
27
|
-
chromatic_python-0.2.
|
|
28
|
-
chromatic_python-0.2.
|
|
24
|
+
chromatic_python-0.2.2.dist-info/licenses/LICENSE,sha256=e7GjzmO7L12JDai3XRWHD8PgZvFmzNceEztB6d-9kuA,1086
|
|
25
|
+
chromatic_python-0.2.2.dist-info/METADATA,sha256=2AVOeLAWh5W6zhoSx8d6_KHqPFdc3hypl_8Kjzbcg8M,6042
|
|
26
|
+
chromatic_python-0.2.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
27
|
+
chromatic_python-0.2.2.dist-info/top_level.txt,sha256=wjzxcxfjO8I4u22BS8wL67Bq64c59kbZCqQ--Fc_Mqw,10
|
|
28
|
+
chromatic_python-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|