chromatic-python 0.7.3__tar.gz → 0.7.4__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 (57) hide show
  1. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/PKG-INFO +1 -1
  2. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/_version.py +3 -3
  3. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/core.py +11 -22
  4. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/palette.py +113 -59
  5. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/palette.pyi +4 -2
  6. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/PKG-INFO +1 -1
  7. chromatic_python-0.7.4/chromatic_python.egg-info/scm_version.json +8 -0
  8. chromatic_python-0.7.3/chromatic_python.egg-info/scm_version.json +0 -8
  9. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/.gitattributes +0 -0
  10. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/.github/dependabot.yml +0 -0
  11. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/.github/workflows/docs.yml +0 -0
  12. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/.github/workflows/release.yml +0 -0
  13. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/.gitignore +0 -0
  14. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/README.md +0 -0
  15. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/banner.png +0 -0
  16. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/__init__.py +0 -0
  17. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/__init__.pyi +0 -0
  18. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/__main__.py +0 -0
  19. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/_typing.py +0 -0
  20. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/__init__.py +0 -0
  21. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/__init__.pyi +0 -0
  22. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/colorconv.py +0 -0
  23. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/colorconv.pyi +0 -0
  24. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/color/core.pyi +0 -0
  25. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/__init__.py +0 -0
  26. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/__init__.pyi +0 -0
  27. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/_fetchers.py +0 -0
  28. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/fonts/userfont.schema.json +0 -0
  29. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/fonts/vga437.ttf +0 -0
  30. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/images/butterfly.jpg +0 -0
  31. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/images/escher.png +0 -0
  32. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/images/goblin_virus.png +0 -0
  33. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/images/registry.json +0 -0
  34. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/userfont.py +0 -0
  35. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/data/userfont.pyi +0 -0
  36. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/demo.py +0 -0
  37. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/image/__init__.py +0 -0
  38. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/image/__init__.pyi +0 -0
  39. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/image/_array.py +0 -0
  40. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/image/_curses.py +0 -0
  41. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic/image/_glyph.py +0 -0
  42. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/SOURCES.txt +0 -0
  43. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/dependency_links.txt +0 -0
  44. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/entry_points.txt +0 -0
  45. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/requires.txt +0 -0
  46. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/scm_file_list.json +0 -0
  47. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/chromatic_python.egg-info/top_level.txt +0 -0
  48. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/docs/api/chromatic.md +0 -0
  49. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/docs/api/color.md +0 -0
  50. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/docs/api/data.md +0 -0
  51. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/docs/api/image.md +0 -0
  52. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/docs/index.md +0 -0
  53. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/mkdocs.yml +0 -0
  54. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/pyproject.toml +0 -0
  55. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/setup.cfg +0 -0
  56. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/tests/__init__.py +0 -0
  57. {chromatic_python-0.7.3 → chromatic_python-0.7.4}/tests/test_color_str.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chromatic-python
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: ANSI art image processing and colored terminal text
5
5
  Author: crypt0lith
6
6
  License-Expression: MIT
@@ -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.3'
22
- __version_tuple__ = version_tuple = (0, 7, 3)
21
+ __version__ = version = '0.7.4'
22
+ __version_tuple__ = version_tuple = (0, 7, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g9af897cd5'
24
+ __commit_id__ = commit_id = 'gbbb16e443'
@@ -780,6 +780,7 @@ def _iter_sgr[_T: (abc.Buffer, tp.SupportsInt)](
780
780
 
781
781
  class SgrSequence(abc.MutableSequence[SgrParamBuffer]):
782
782
  """Construct a mutable sequence of SGR code bytes."""
783
+
783
784
  _idx_attrs = ("_fg_idx", "_bg_idx")
784
785
  _key2idx = mappingproxy(dict(zip(("fg", "bg"), _idx_attrs)))
785
786
  _reset2idx = mappingproxy(dict(zip((b"39", b"49"), _idx_attrs)))
@@ -939,37 +940,25 @@ class SgrSequence(abc.MutableSequence[SgrParamBuffer]):
939
940
 
940
941
  """
941
942
 
943
+ K2I = {
944
+ k: i for i, ks in enumerate(zip((b"39", b"49"), ("fg", "bg"))) for k in ks
945
+ }
942
946
  buf = []
943
947
  seen = set()
944
- seen_fg = seen_bg = False
948
+ seen_colors = [False] * 2
945
949
  for x in reversed(self):
946
950
  if x in seen:
947
951
  continue
948
952
  seen.add(x)
949
- if x == b"0":
953
+ v = x._value
954
+ if v == b"0":
950
955
  buf.append(x)
951
956
  break
952
- elif x == b"39":
953
- if not seen_fg:
954
- buf.append(x)
955
- seen_fg = True
956
- continue
957
- elif x == b"49":
958
- if not seen_bg:
959
- buf.append(x)
960
- seen_bg = True
961
- continue
962
- elif not x.is_color():
963
- buf.append(x)
964
- continue
965
- elif x._value.kind() == "fg":
966
- if seen_fg:
957
+ elif v in K2I or x.is_color():
958
+ idx = K2I[getattr(v, "kind", lambda: v)()]
959
+ if seen_colors[idx]:
967
960
  continue
968
- seen_fg = True
969
- elif seen_bg:
970
- continue
971
- else:
972
- seen_bg = True
961
+ seen_colors[idx] = True
973
962
  buf.append(x)
974
963
  self[:] = buf[::-1]
975
964
 
@@ -449,7 +449,83 @@ _rgb_lookup = _DynamicNSMeta(
449
449
  )._getmember
450
450
 
451
451
 
452
- def rgb_dispatch(*names):
452
+ def rgb_dispatch(*names, replace_defaults=True):
453
+ """Returns a decorator which intercepts input arguments that are color
454
+ name strings, and replaces those arguments with their RGB tuple
455
+ counterparts before passing them to the wrapped function.
456
+
457
+ In the bare form, ie. ``@rgb_dispatch``, the decorator treats all
458
+ positional-only and variadic positional parameters as assignable.
459
+
460
+ In the named form, ie. ``@rgb_dispatch("a", "b", "c")``, the decorator
461
+ will only attempt to match and replace those parameter names, regardless
462
+ of their parameter kind.
463
+
464
+ Parameters
465
+ ----------
466
+ *names : str
467
+ Parameter names to target for color name to RGB tuple replacement.
468
+ replace_defaults : bool, default=True
469
+ Whether to replace default argument values of the returned callable.
470
+
471
+ Examples
472
+ --------
473
+ >>> from chromatic.color.palette import rgb_dispatch
474
+ >>> @rgb_dispatch
475
+ ... def func(r="red", b="blue", g="green", x="not a color", /):
476
+ ... return r, g, b, x
477
+ ...
478
+ >>> func()
479
+ ((255, 0, 0), (0, 128, 0), (0, 0, 255), 'not a color')
480
+ >>> func(None, None, None, "Hot Pink")
481
+ (None, None, None, (255, 105, 180))
482
+
483
+ >>> @rgb_dispatch
484
+ ... def func(a, b, c, /, *args):
485
+ ... return a, b, c, *args
486
+ ...
487
+ >>> func("red", "yellow", "pink", "dark magenta", "alice blue")
488
+ ((255, 0, 0), (255, 255, 0), (255, 192, 203), (139, 0, 139), (240, 248, 255))
489
+
490
+ >>> @rgb_dispatch("c")
491
+ ... def func(a, b, c, /, *args):
492
+ ... return a, b, c, *args
493
+ ...
494
+ >>> func("red", "yellow", "pink", "dark magenta", "alice blue")
495
+ ('red', 'yellow', (255, 192, 203), 'dark magenta', 'alice blue')
496
+
497
+ >>> @rgb_dispatch("color")
498
+ ... def func(**kwargs):
499
+ ... return kwargs
500
+ ...
501
+ >>> func(color="red")
502
+ {'color': (255, 0, 0)}
503
+
504
+ >>> @rgb_dispatch("kwargs")
505
+ ... def func(**kwargs):
506
+ ... return kwargs
507
+ ...
508
+ >>> func(color1="red", color2="yellow", color3="orange")
509
+ {'color1': (255, 0, 0), 'color2': (255, 255, 0), 'color3': (255, 165, 0)}
510
+
511
+ >>> @rgb_dispatch("kwargs")
512
+ ... def func(a=None, /, **kwargs):
513
+ ... return a, kwargs
514
+ ...
515
+ >>> func(a="green")
516
+ (None, {'a': (0, 128, 0)})
517
+
518
+ >>> @rgb_dispatch(replace_defaults=False)
519
+ ... def func(fruit_or_color="orange", /):
520
+ ... res = "fruit" if isinstance(fruit_or_color, str) else "color"
521
+ ... return f"{fruit_or_color} is a {res}"
522
+ ...
523
+ >>> func()
524
+ 'orange is a fruit'
525
+ >>> func("orange")
526
+ '(255, 165, 0) is a color'
527
+ """
528
+
453
529
  def decorator(f: types.FunctionType, /):
454
530
  def _prepare():
455
531
  assert isinstance(names, set)
@@ -463,16 +539,6 @@ def rgb_dispatch(*names):
463
539
  has_varkwds = bool(flags & 0x8)
464
540
  total = sum([n_args, n_kwonly, has_varargs, has_varkwds])
465
541
  params = list(code.co_varnames[:total])
466
- if not names:
467
- names.update(
468
- name
469
- for name in params
470
- if (
471
- name in {"bg", "fg"}
472
- or name.startswith(("bg_", "fg_"))
473
- or name.endswith(("_bg", "_fg"))
474
- )
475
- )
476
542
  mask_params = {name: name in names for name in params}
477
543
  positions, keywords = [], {}
478
544
  if names:
@@ -481,40 +547,37 @@ def rgb_dispatch(*names):
481
547
  raise ValueError(f"unexpected parameter names: {unexpected}")
482
548
  elif total == 0 or not (n_pos_or_kw or n_kwonly or has_varkwds):
483
549
  if total > 0:
484
- positions.append(slice(None))
550
+ positions.extend(range(n_posonly))
551
+ positions.append(slice(n_posonly, None))
485
552
  return tuple(positions), mappingproxy(keywords)
486
553
  else:
487
554
  raise ValueError("no parameters specified and none could be inferred")
488
555
  i = 0
489
556
  if n_posonly > 0:
490
- posonly = params[:n_posonly]
491
- for name in posonly:
557
+ for name in params[:n_posonly]:
492
558
  if mask_params[name]:
493
559
  positions.append(i)
494
560
  i += 1
495
561
  del params[:n_posonly]
496
562
  if n_pos_or_kw > 0:
497
- pos_or_kw = params[:n_pos_or_kw]
498
- for name in pos_or_kw:
563
+ for name in params[:n_pos_or_kw]:
499
564
  if mask_params[name]:
500
565
  positions.append(i)
501
566
  keywords[name] = positions[-1]
502
567
  i += 1
503
568
  del params[:n_pos_or_kw]
504
569
  if n_kwonly > 0:
505
- kwonly = params[:n_kwonly]
506
- for name in kwonly:
570
+ for name in params[:n_kwonly]:
507
571
  if mask_params[name]:
508
572
  keywords[name] = None
509
573
  del params[:n_kwonly]
510
574
  if has_varargs:
511
- varargs = params.pop(0)
512
- if mask_params[varargs]:
575
+ if mask_params[params.pop(0)]:
513
576
  positions.append(slice(i, None))
514
577
  if has_varkwds:
515
- varkwds = params.pop(0)
516
- if mask_params[varkwds]:
578
+ if mask_params[params.pop(0)]:
517
579
  keywords[None] = None
580
+ keywords |= dict.fromkeys(names.difference(code.co_varnames[:total]))
518
581
  return tuple(positions), mappingproxy(keywords)
519
582
 
520
583
  POSITIONS, KEYWORDS = _prepare()
@@ -530,25 +593,21 @@ def rgb_dispatch(*names):
530
593
  for i, (j, x) in zip(
531
594
  range(argcount - len(argdefs), argcount), enumerate(argdefs)
532
595
  ):
533
- if not (i in POSITIONS and isinstance(x, str)):
534
- continue
535
- try:
536
- buf[j] = _rgb_lookup(x)
537
- except KeyError:
538
- continue
539
- else:
596
+ if i in POSITIONS and isinstance(x, str):
597
+ try:
598
+ buf[j] = _rgb_lookup(x)
599
+ except KeyError:
600
+ continue
540
601
  changed = True
541
602
  argdefs = tuple(buf)
542
603
  if kwdefaults is not None:
543
604
  kwdefaults = kwdefaults.copy()
544
605
  for k, v in kwdefaults.items():
545
- if not (k in KEYWORDS and isinstance(v, str)):
546
- continue
547
- try:
548
- kwdefaults[k] = _rgb_lookup(v)
549
- except KeyError:
550
- continue
551
- else:
606
+ if k in KEYWORDS and isinstance(v, str):
607
+ try:
608
+ kwdefaults[k] = _rgb_lookup(v)
609
+ except KeyError:
610
+ continue
552
611
  changed = True
553
612
  if not changed:
554
613
  return f
@@ -573,13 +632,14 @@ def rgb_dispatch(*names):
573
632
  setattr(f_new, "__wrapped__", f)
574
633
  return f_new
575
634
 
576
- f = _replace_defaults()
635
+ if replace_defaults:
636
+ f = _replace_defaults()
577
637
 
578
638
  @ft.wraps(f)
579
639
  def wrapper(*args, **kwargs):
580
640
  _kwargs = kwargs.copy()
581
641
  n_args = len(args)
582
- mask = [False for _ in range(n_args)]
642
+ mask = [False] * n_args
583
643
  for idx in POSITIONS:
584
644
  if isinstance(idx, slice):
585
645
  for i in range(*idx.indices(n_args)):
@@ -587,29 +647,23 @@ def rgb_dispatch(*names):
587
647
  elif idx < n_args:
588
648
  mask[idx] = True
589
649
  for k, v in kwargs.items():
590
- if not isinstance(v, str):
591
- continue
592
- if not (k in KEYWORDS or HAS_VARKW):
593
- continue
594
- try:
595
- v = _rgb_lookup(v)
596
- except KeyError:
597
- continue
598
- _kwargs[k] = v
599
- if (i := KEYWORDS.get(k)) is None or i >= n_args:
600
- continue
601
- mask[i] = False
650
+ if (k in KEYWORDS or HAS_VARKW) and isinstance(v, str):
651
+ try:
652
+ v = _rgb_lookup(v)
653
+ except KeyError:
654
+ continue
655
+ _kwargs[k] = v
656
+ if (i := KEYWORDS.get(k)) is None or i >= n_args:
657
+ continue
658
+ mask[i] = False
602
659
  _args = []
603
660
  for g, v in zip(mask, args):
604
- if not (g and isinstance(v, str)):
605
- _args.append(v)
606
- continue
607
- try:
608
- res = _rgb_lookup(v)
609
- except KeyError:
610
- _args.append(v)
611
- continue
612
- _args.append(res)
661
+ if g and isinstance(v, str):
662
+ try:
663
+ v = _rgb_lookup(v)
664
+ except KeyError:
665
+ pass
666
+ _args.append(v)
613
667
  return f(*_args, **_kwargs)
614
668
 
615
669
  return wrapper
@@ -265,10 +265,12 @@ class AnsiFore(ColorNamespace[_frozen_color_chain]):
265
265
  def __call__(self, fg: _ColorLike) -> color_chain: ...
266
266
 
267
267
  @tp.overload
268
- def rgb_dispatch[_F: abc.Callable[..., tp.Any]](f: _F, /, *names: str) -> _F: ...
268
+ def rgb_dispatch[_F: abc.Callable[..., tp.Any]](
269
+ f: _F, /, *names: str, replace_defaults: bool = True
270
+ ) -> _F: ...
269
271
  @tp.overload
270
272
  def rgb_dispatch[_F: abc.Callable[..., tp.Any]](
271
- *names: str,
273
+ *names: str, replace_defaults: bool = True
272
274
  ) -> abc.Callable[[_F], _F]: ...
273
275
 
274
276
  named_color: MappingProxyType[str | tuple[str, tp.Literal["4b", "24b"]], Color]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chromatic-python
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: ANSI art image processing and colored terminal text
5
5
  Author: crypt0lith
6
6
  License-Expression: MIT
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.7.4",
3
+ "distance": 0,
4
+ "node": "gbbb16e4437791f8a31ab74d68f7d176626c3b88e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-08-27"
8
+ }
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.7.3",
3
- "distance": 0,
4
- "node": "g9af897cd5123a20ec3673f13fe8caac8a3f73bb3",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-08-26"
8
- }