chromatic-python 0.7.2__tar.gz → 0.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. chromatic_python-0.7.3/.github/workflows/docs.yml +36 -0
  2. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/PKG-INFO +5 -4
  3. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/__init__.pyi +2 -0
  4. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/__main__.py +162 -110
  5. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/_version.py +3 -3
  6. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/__init__.pyi +12 -0
  7. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/colorconv.py +66 -25
  8. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/colorconv.pyi +61 -2
  9. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/core.py +233 -192
  10. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/core.pyi +8 -3
  11. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/palette.py +56 -0
  12. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/__init__.pyi +0 -12
  13. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/_array.py +2 -2
  14. chromatic_python-0.7.3/chromatic/image/_curses.py +41 -0
  15. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/PKG-INFO +5 -4
  16. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/SOURCES.txt +7 -0
  17. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/requires.txt +2 -2
  18. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/scm_file_list.json +31 -24
  19. chromatic_python-0.7.3/chromatic_python.egg-info/scm_version.json +8 -0
  20. chromatic_python-0.7.3/docs/api/chromatic.md +3 -0
  21. chromatic_python-0.7.3/docs/api/color.md +1 -0
  22. chromatic_python-0.7.3/docs/api/data.md +1 -0
  23. chromatic_python-0.7.3/docs/api/image.md +1 -0
  24. chromatic_python-0.7.3/docs/index.md +134 -0
  25. chromatic_python-0.7.3/mkdocs.yml +34 -0
  26. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/pyproject.toml +11 -3
  27. chromatic_python-0.7.2/chromatic/image/_curses.py +0 -136
  28. chromatic_python-0.7.2/chromatic_python.egg-info/scm_version.json +0 -8
  29. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.gitattributes +0 -0
  30. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.github/dependabot.yml +0 -0
  31. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.github/workflows/release.yml +0 -0
  32. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/.gitignore +0 -0
  33. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/README.md +0 -0
  34. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/banner.png +0 -0
  35. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/__init__.py +0 -0
  36. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/_typing.py +0 -0
  37. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/__init__.py +0 -0
  38. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/color/palette.pyi +0 -0
  39. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/__init__.py +0 -0
  40. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/__init__.pyi +0 -0
  41. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/_fetchers.py +0 -0
  42. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/fonts/userfont.schema.json +0 -0
  43. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/fonts/vga437.ttf +0 -0
  44. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/butterfly.jpg +0 -0
  45. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/escher.png +0 -0
  46. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/goblin_virus.png +0 -0
  47. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/images/registry.json +0 -0
  48. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/userfont.py +0 -0
  49. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/data/userfont.pyi +0 -0
  50. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/demo.py +0 -0
  51. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/__init__.py +0 -0
  52. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic/image/_glyph.py +0 -0
  53. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/dependency_links.txt +0 -0
  54. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/entry_points.txt +0 -0
  55. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/chromatic_python.egg-info/top_level.txt +0 -0
  56. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/setup.cfg +0 -0
  57. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/tests/__init__.py +0 -0
  58. {chromatic_python-0.7.2 → chromatic_python-0.7.3}/tests/test_color_str.py +0 -0
@@ -0,0 +1,36 @@
1
+ name: docs
2
+
3
+ on:
4
+ release:
5
+ types: [released]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ pages: write
10
+ id-token: write
11
+
12
+ concurrency:
13
+ group: pages
14
+ cancel-in-progress: false
15
+
16
+ jobs:
17
+ build:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v5
22
+ - run: uv sync --group docs
23
+ - run: uv run mkdocs build
24
+ - uses: actions/upload-pages-artifact@v3
25
+ with:
26
+ path: site
27
+
28
+ deploy:
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ environment:
32
+ name: github-pages
33
+ url: ${{ steps.deployment.outputs.page_url }}
34
+ steps:
35
+ - id: deployment
36
+ uses: actions/deploy-pages@v4
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chromatic-python
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: ANSI art image processing and colored terminal text
5
5
  Author: crypt0lith
6
6
  License-Expression: MIT
7
- Project-URL: repository, https://github.com/crypt0lith/chromatic.git
7
+ Project-URL: Homepage, https://github.com/crypt0lith/chromatic
8
+ Project-URL: Documentation, https://crypt0lith.github.io/chromatic
8
9
  Keywords: ansi,ascii,art,font,image,terminal,parser
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: Programming Language :: Python :: 3.12
@@ -15,9 +16,9 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
16
  Classifier: Typing :: Typed
16
17
  Requires-Python: <4.0,>=3.12
17
18
  Description-Content-Type: text/markdown
18
- Requires-Dist: numpy<2.6,>=1.25.2
19
+ Requires-Dist: numpy<2.8,>=2.1
19
20
  Requires-Dist: pillow>=12.2.0
20
- Requires-Dist: scipy<2.0,>=1.11.4
21
+ Requires-Dist: scipy>=1.11.4
21
22
  Requires-Dist: opencv-python
22
23
  Requires-Dist: fonttools>=4.60.2
23
24
  Requires-Dist: lazy_loader
@@ -4,6 +4,7 @@ __all__ = [
4
4
  "ColorNamespace",
5
5
  "ColorStr",
6
6
  "Fore",
7
+ "SgrFlag",
7
8
  "SgrParameter",
8
9
  "Style",
9
10
  "UserFont",
@@ -50,6 +51,7 @@ from .color import (
50
51
  ColorNamespace,
51
52
  ColorStr,
52
53
  Fore,
54
+ SgrFlag,
53
55
  SgrParameter,
54
56
  Style,
55
57
  ansicolor4Bit,
@@ -1,3 +1,4 @@
1
+ # vim: foldmethod=marker foldenable
1
2
  import collections.abc as abc
2
3
  import os
3
4
  import sys
@@ -9,6 +10,8 @@ from inspect import signature
9
10
  def parse_args():
10
11
  import argparse as ap
11
12
 
13
+ from . import __version__
14
+
12
15
  class SetEnvAction(ap.Action):
13
16
  def __init__(self, *args, **kwargs):
14
17
  if kwargs.get("nargs") not in ("?", None):
@@ -37,7 +40,7 @@ def parse_args():
37
40
  out = super()._get_help_string(action)
38
41
  if out is not None and isinstance(action, SetEnvAction):
39
42
  out += " (env: {}={})".format(
40
- *(lambda s: (s, os.environ.get(s, "")))(action.env)
43
+ action.env, os.environ.get(action.env, "")
41
44
  )
42
45
  return out
43
46
 
@@ -63,21 +66,57 @@ def parse_args():
63
66
 
64
67
  font_dir_env_base = init_font_dir_env_parser()
65
68
 
66
- if os.path.isfile(sys.argv[0]) and os.path.samefile(sys.argv[0], __file__):
67
- top_parser = ap.ArgumentParser(prog=__package__)
68
- else:
69
- top_parser = ap.ArgumentParser()
69
+ from .color.palette import ColorNamespace
70
70
 
71
- cmd_subparsers = top_parser.add_subparsers(dest="cmd", required=True)
71
+ color_ns = ColorNamespace.asdict()
72
+
73
+ def ColorArgType(allow_rgba=False):
74
+ def color_arg(s: str, /) -> tuple[int, int, int, int] | tuple[int, int, int]:
75
+ try:
76
+ return color_ns[s.strip().replace(" ", "_").upper()]
77
+ except KeyError:
78
+ if any(c not in "0123456789abcdef" for c in s.casefold()):
79
+ raise
80
+ n = len(s)
81
+ if allow_rgba and n % 4 == 0:
82
+ if n == 4:
83
+ s = "".join(c * 2 for c in s)
84
+ x = int(s, 16)
85
+ if not 0 <= x < (1 << 32):
86
+ raise ValueError(f"{x:#x} is not u32")
87
+ return (
88
+ (x >> 24) & 0xFF,
89
+ (x >> 16) & 0xFF,
90
+ (x >> 8) & 0xFF,
91
+ x & 0xFF,
92
+ ) # fmt: skip
93
+ elif n % 3 == 0:
94
+ if n == 3:
95
+ s = "".join(c * 2 for c in s)
96
+ x = int(s, 16)
97
+ if not 0 <= x < (1 << 24):
98
+ raise ValueError(f"{x:#x} is not u24")
99
+ return (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF
100
+ raise ValueError(
101
+ "expected hex digit with nbytes in {}, got {}".format(
102
+ (3, 4, 6, 8), int(s, 16).bit_length() // 8
103
+ )
104
+ )
105
+
106
+ return color_arg
72
107
 
73
108
  def init_font_subcmds(parser: ap.ArgumentParser):
74
109
  subcmds = parser.add_subparsers(dest="subcmd", required=True)
110
+
75
111
  mut_by_name_base = new_base_parser()
76
112
  mut_by_name_base.add_argument(
77
113
  dest="name", metavar="NAME", help="name of the registered font"
78
114
  )
115
+
79
116
  uf_attr_base = new_base_parser()
117
+
80
118
  uf_attr_opts = uf_attr_base.add_argument_group("font attribute options")
119
+ # font attribute options {{{
81
120
  uf_attr_opts.add_argument(
82
121
  "--size", dest="size", type=int, help="size in pixels"
83
122
  )
@@ -96,6 +135,9 @@ def parse_args():
96
135
  action=ap.BooleanOptionalAction,
97
136
  help="whether to set this font as the new default font",
98
137
  )
138
+ # }}}
139
+
140
+ # subcommands: font {{{
99
141
  subcmds.add_parser(
100
142
  "delete",
101
143
  parents=[font_dir_env_base, mut_by_name_base],
@@ -134,12 +176,18 @@ def parse_args():
134
176
  set a user font as the default font.
135
177
  it will be accessible as `{__package__}.DEFAULT_FONT`""",
136
178
  )
179
+ # }}}
180
+
181
+ # font edit {{{
137
182
  p_edit_font_opts = subcmd_p_edit.add_argument_group(
138
183
  "font options", argument_default=ap.SUPPRESS
139
184
  )
140
185
  p_edit_font_opts.add_argument(
141
186
  "--font", dest="font", metavar="FILE", help="path to truetype font file"
142
187
  )
188
+ # }}}
189
+
190
+ # font list {{{
143
191
  p_list_fmt_opts = subcmd_p_list.add_argument_group("formatting options")
144
192
  p_list_fmt_opts.add_argument(
145
193
  "--json",
@@ -147,6 +195,9 @@ def parse_args():
147
195
  action="store_true",
148
196
  help="emit entries in JSON format",
149
197
  )
198
+ # }}}
199
+
200
+ # font register {{{
150
201
  subcmd_p_register.add_argument(
151
202
  dest="font", metavar="FONT", help="path to truetype font file"
152
203
  )
@@ -167,16 +218,77 @@ def parse_args():
167
218
  create a symlink instead of copying the file.
168
219
  like `ln -s TARGET LINK_NAME`, this will fail if the link name already exists""",
169
220
  )
221
+ # }}}
222
+
223
+ # font rename {{{
170
224
  subcmd_p_rename.add_argument(
171
225
  dest="newname",
172
226
  metavar="NEWNAME",
173
227
  help="new name to give the registered font",
174
228
  )
229
+ # }}}
175
230
 
176
231
  def init_image_subcmds(parser: ap.ArgumentParser):
232
+ def save_img_callback(path: str | os.PathLike[str] | None = None, /):
233
+
234
+ # deferred exception handler
235
+ # on write error, fall-through to Image.show()
236
+ # then re-raise after in-memory image is opened
237
+
238
+ from functools import wraps
239
+
240
+ def defer_exc[**P, R](
241
+ f: abc.Callable[P, abc.Generator[Exception, tp.Any, R]], /
242
+ ):
243
+ @wraps(f)
244
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
245
+ err = None
246
+ it = f(*args, **kwargs)
247
+ try:
248
+ while True:
249
+ err = next(it)
250
+ except StopIteration as stop:
251
+ return stop.value
252
+ finally:
253
+ if err is not None:
254
+ raise err
255
+
256
+ return wrapper
257
+
258
+ import PIL.Image
259
+
260
+ @defer_exc
261
+ def callback(ns: ap.Namespace, img: PIL.Image.Image):
262
+ if path is not None:
263
+ try:
264
+ img.save(path)
265
+ except Exception as e:
266
+ yield e
267
+ else:
268
+ if getattr(ns, "show", False):
269
+ with PIL.Image.open(path) as f:
270
+ f.show()
271
+ return path
272
+ dump_stdout = hasattr(ns, "dumpfile") and ns.dumpfile.fileno() == 1
273
+ if getattr(ns, "show", not dump_stdout):
274
+ img.show()
275
+
276
+ return callback
277
+
278
+ def save_img_to_dir(dirname: str, /):
279
+ from datetime import datetime
280
+ from pathlib import Path
281
+
282
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
283
+ outfile = Path(dirname, f"{__package__}_{timestamp}.png")
284
+ return save_img_callback(outfile)
285
+
177
286
  subcmds = parser.add_subparsers(dest="subcmd", required=True)
287
+
178
288
  ansify_base = new_base_parser()
289
+
179
290
  font_opts = ansify_base.add_argument_group("font options")
291
+ # font options {{{
180
292
  font_opts.add_argument(
181
293
  "--font",
182
294
  dest="font",
@@ -189,61 +301,22 @@ def parse_args():
189
301
  metavar="SIZE",
190
302
  help="font size, in pixels",
191
303
  )
192
-
193
- from .color.palette import ColorNamespace
194
-
195
- color_ns = ColorNamespace.asdict()
196
-
197
- def color_parser(allow_rgba=False):
198
- def parsed_color(
199
- s: str, /
200
- ) -> tuple[int, int, int, int] | tuple[int, int, int]:
201
- try:
202
- return color_ns[s.strip().replace(" ", "_").upper()]
203
- except KeyError:
204
- if any(c not in "0123456789abcdef" for c in s.casefold()):
205
- raise
206
- n = len(s)
207
- if allow_rgba and n % 4 == 0:
208
- if n == 4:
209
- s = "".join(c * 2 for c in s)
210
- x = int(s, 16)
211
- if not 0 <= x < (1 << 32):
212
- raise ValueError(f"{x:#x} is not u32")
213
- return (
214
- (x >> 24) & 0xFF,
215
- (x >> 16) & 0xFF,
216
- (x >> 8) & 0xFF,
217
- x & 0xFF,
218
- ) # fmt: skip
219
- elif n % 3 == 0:
220
- if n == 3:
221
- s = "".join(c * 2 for c in s)
222
- x = int(s, 16)
223
- if not 0 <= x < (1 << 24):
224
- raise ValueError(f"{x:#x} is not u24")
225
- return (x >> 16) & 0xFF, (x >> 8) & 0xFF, x & 0xFF
226
- raise ValueError(
227
- "expected hex digit with nbytes in {}, got {}".format(
228
- (3, 4, 6, 8), int(s, 16).bit_length() // 8
229
- )
230
- )
231
-
232
- return parsed_color
304
+ # }}}
233
305
 
234
306
  color_opts = ansify_base.add_argument_group("color defaults")
307
+ # color defaults {{{
235
308
  color_opts.add_argument(
236
309
  "--bg",
237
310
  dest="bg_default",
238
311
  metavar="COLOR",
239
- type=color_parser(allow_rgba=True),
312
+ type=ColorArgType(allow_rgba=True),
240
313
  help="default background color. can be hex rgb(a) value or color name",
241
314
  )
242
315
  color_opts.add_argument(
243
316
  "--fg",
244
317
  dest="fg_default",
245
318
  metavar="COLOR",
246
- type=color_parser(allow_rgba=True),
319
+ type=ColorArgType(allow_rgba=True),
247
320
  help="default foreground color. can be hex rgb(a) value or color name",
248
321
  )
249
322
  color_opts.add_argument(
@@ -252,10 +325,12 @@ def parse_args():
252
325
  action="store_true",
253
326
  help="use alpha channel with defaults: bg=0, fg=0xFF",
254
327
  )
328
+ # }}}
255
329
 
256
330
  from_img_base = new_base_parser()
257
331
 
258
332
  colorize_opts = from_img_base.add_argument_group("colorization options")
333
+ # colorization options {{{
259
334
  colorize_opts.add_argument(
260
335
  "-a",
261
336
  "--ansi-type",
@@ -266,6 +341,7 @@ def parse_args():
266
341
  since truecolor is rgb, '24b' effectively means 'original colors'
267
342
  (choices: %(choices)s)""",
268
343
  )
344
+
269
345
  equalize_opts = colorize_opts.add_mutually_exclusive_group()
270
346
  equalize_opts.add_argument(
271
347
  "--equalize",
@@ -281,18 +357,22 @@ def parse_args():
281
357
  const="white_point",
282
358
  help="apply white-point equalization before image-to-ansi conversion",
283
359
  )
360
+
284
361
  colorize_opts.add_argument(
285
362
  "--input-bg",
286
363
  dest="bg",
287
364
  metavar="COLOR",
288
- type=color_parser(),
365
+ type=ColorArgType(),
289
366
  default=None,
290
367
  help="""\
291
368
  initial background (canvas) color,
292
369
  injected as an attribute on the intermediate text object itself.
293
370
  can be hex rgb value or color name""",
294
371
  )
372
+ # }}}
373
+
295
374
  glyph_opts = from_img_base.add_argument_group("glyph options")
375
+ # glyph options {{{
296
376
  glyph_opts.add_argument(
297
377
  "--factor",
298
378
  dest="factor",
@@ -302,7 +382,15 @@ def parse_args():
302
382
  scaling factor for image width to %(metavar)s columns per output line.
303
383
  analogous to level-of-detail""",
304
384
  )
385
+
305
386
  char_set_opts = glyph_opts.add_mutually_exclusive_group()
387
+
388
+ from .image._curses import ascii_printable, cp437_printable
389
+
390
+ # char_set {{{
391
+ char_set_preset_help = (
392
+ "use printable characters from {} character encoding as charset"
393
+ ).format
306
394
  char_set_opts.add_argument(
307
395
  "-c",
308
396
  "--chars",
@@ -312,12 +400,6 @@ def parse_args():
312
400
  charset where %(metavar)s is a string containing chars to use for glyph selection.
313
401
  a char can appear more than once, which affects frequency distribution""",
314
402
  )
315
-
316
- from .image._curses import ascii_printable, cp437_printable
317
-
318
- char_set_preset_help = (
319
- "use printable characters from {} character encoding as charset"
320
- ).format
321
403
  char_set_opts.add_argument(
322
404
  "--ascii",
323
405
  dest="char_set",
@@ -350,8 +432,10 @@ def parse_args():
350
432
  )
351
433
  ),
352
434
  )
435
+ # }}}
353
436
 
354
437
  glyph_sort_opts = glyph_opts.add_mutually_exclusive_group()
438
+ # sort_glyphs {{{
355
439
  glyph_sort_opts.add_argument(
356
440
  "--sort",
357
441
  dest="sort_glyphs",
@@ -370,63 +454,13 @@ def parse_args():
370
454
  sort glyphs in reverse order.
371
455
  flips the luminance mapping to (light -> dark)""",
372
456
  )
373
-
374
- def save_img_callback(path: str | os.PathLike[str] | None = None, /):
375
-
376
- # deferred exception handler
377
- # on write error, fall-through to Image.show()
378
- # then re-raise after in-memory image is opened
379
-
380
- from functools import wraps
381
-
382
- def defer_exc[**P, R](
383
- f: abc.Callable[P, abc.Generator[Exception, tp.Any, R]], /
384
- ):
385
- @wraps(f)
386
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
387
- err = None
388
- it = f(*args, **kwargs)
389
- try:
390
- while True:
391
- err = next(it)
392
- except StopIteration as stop:
393
- return stop.value
394
- finally:
395
- if err is not None:
396
- raise err
397
-
398
- return wrapper
399
-
400
- import PIL.Image
401
-
402
- @defer_exc
403
- def callback(ns: ap.Namespace, img: PIL.Image.Image):
404
- if path is not None:
405
- try:
406
- img.save(path)
407
- except Exception as e:
408
- yield e
409
- else:
410
- if getattr(ns, "show", False):
411
- with PIL.Image.open(path) as f:
412
- f.show()
413
- return path
414
- dump_stdout = hasattr(ns, "dumpfile") and ns.dumpfile.fileno() == 1
415
- if getattr(ns, "show", not dump_stdout):
416
- img.show()
417
-
418
- return callback
419
-
420
- def save_img_to_dir(dirname: str, /):
421
- from datetime import datetime
422
- from pathlib import Path
423
-
424
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
425
- outfile = Path(dirname, f"{__package__}_{timestamp}.png")
426
- return save_img_callback(outfile)
457
+ # }}}
458
+ # }}}
427
459
 
428
460
  output_opts_base = new_base_parser()
461
+
429
462
  output_opts = output_opts_base.add_argument_group("output options")
463
+ # output options {{{
430
464
  outfile_opts = output_opts.add_mutually_exclusive_group()
431
465
  outfile_opts.add_argument(
432
466
  "-O",
@@ -466,14 +500,32 @@ def parse_args():
466
500
  help="write the ansified image text to stdout",
467
501
  )
468
502
  output_opts_base.set_defaults(_outfile_callback=save_img_callback())
503
+ # }}}
504
+
505
+ # subcommands: image {{{
469
506
  subcmd_p_ansify = subcmds.add_parser(
470
507
  "ansify",
471
508
  parents=[font_dir_env_base, ansify_base, from_img_base, output_opts_base],
472
509
  formatter_class=EnvHelpFormatter,
473
510
  )
511
+ # }}}
512
+
513
+ # image ansify {{{
474
514
  subcmd_p_ansify.add_argument(
475
515
  dest="img", metavar="IMAGEFILE", help="input image"
476
516
  )
517
+ # }}}
518
+
519
+ if os.path.isfile(sys.argv[0]) and os.path.samefile(sys.argv[0], __file__):
520
+ top_parser = ap.ArgumentParser(prog=__package__)
521
+ else:
522
+ top_parser = ap.ArgumentParser()
523
+
524
+ top_parser.add_argument(
525
+ "-V", "--version", action="version", version=f"%(prog)s {__version__}"
526
+ )
527
+
528
+ cmd_subparsers = top_parser.add_subparsers(dest="cmd", required=True)
477
529
 
478
530
  font_cmd_subparser = cmd_subparsers.add_parser("font")
479
531
  image_cmd_subparser = cmd_subparsers.add_parser("image")
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.7.2'
22
- __version_tuple__ = version_tuple = (0, 7, 2)
21
+ __version__ = version = '0.7.3'
22
+ __version_tuple__ = version_tuple = (0, 7, 3)
23
23
 
24
- __commit_id__ = commit_id = 'g7703f67b1'
24
+ __commit_id__ = commit_id = 'g9af897cd5'
@@ -8,6 +8,7 @@ __all__ = [
8
8
  "DEFAULT_ANSI",
9
9
  "Fore",
10
10
  "SGR_RESET",
11
+ "SgrFlag",
11
12
  "SgrParameter",
12
13
  "SgrSequence",
13
14
  "Style",
@@ -24,8 +25,12 @@ __all__ = [
24
25
  "hsv2rgb",
25
26
  "int2rgb",
26
27
  "is_u24",
28
+ "lab2lch",
27
29
  "lab2rgb",
28
30
  "lab2xyz",
31
+ "lch2lab",
32
+ "lch2rgb",
33
+ "lerp_lch",
29
34
  "named_color",
30
35
  "nearest_ansi_4bit_rgb",
31
36
  "nearest_ansi_8bit_rgb",
@@ -36,6 +41,7 @@ __all__ = [
36
41
  "rgb2hsv",
37
42
  "rgb2int",
38
43
  "rgb2lab",
44
+ "rgb2lch",
39
45
  "rgb2xyz",
40
46
  "rgb_diff",
41
47
  "rgb_dispatch",
@@ -54,8 +60,12 @@ from .colorconv import (
54
60
  hsv2rgb,
55
61
  int2rgb,
56
62
  is_u24,
63
+ lab2lch,
57
64
  lab2rgb,
58
65
  lab2xyz,
66
+ lch2lab,
67
+ lch2rgb,
68
+ lerp_lch,
59
69
  nearest_ansi_4bit_rgb,
60
70
  nearest_ansi_8bit_rgb,
61
71
  rgb2hexstr,
@@ -63,6 +73,7 @@ from .colorconv import (
63
73
  rgb2hsv,
64
74
  rgb2int,
65
75
  rgb2lab,
76
+ rgb2lch,
66
77
  rgb2xyz,
67
78
  rgb_diff,
68
79
  rgb_to_ansi_8bit,
@@ -75,6 +86,7 @@ from .core import (
75
86
  SGR_RESET,
76
87
  Color,
77
88
  ColorStr,
89
+ SgrFlag,
78
90
  SgrParameter,
79
91
  SgrSequence,
80
92
  ansicolor4Bit,