Skip to content

Commit 239a122

Browse files
authored
feat: add colorcet aliases (#11)
* feat: add colorcet aliases * refactor: change alias structure * fix: fix mock in test * fix: add changelog to check-manifest * test: add test
1 parent d8b84c7 commit 239a122

File tree

7 files changed

+394
-12
lines changed

7 files changed

+394
-12
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ The `cmap.Colormap` object has convenience methods that allow it to be used with
9898
See [documentation](https://cmap-docs.readthedocs.io/en/latest/colormaps/#usage-with-external-visualization-libraries)
9999
for details.
100100

101-
If you would like to see support added for a particular library, please open an issue or PR.
101+
If you would like to see support added for a particular library, please open an issue or PR.
102102

103103
## Alternatives
104104

docs/_gen_cmaps.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
from cmap import Colormap, _catalog
88
from cmap._util import report
99

10+
# TODO: convert to jinja
1011
TEMPLATE = """# {name}
1112
13+
{aliases}
14+
1215
{info}
1316
1417
| category | license | authors | source |
@@ -87,6 +90,11 @@ def build_catalog(catalog: _catalog.Catalog) -> None:
8790
license_: str = info.license
8891
except KeyError as e:
8992
raise KeyError(f"Missing info for {name}: {e}") from e
93+
94+
if info.qualified_name.lower() != name.lower():
95+
# skip aliases
96+
continue
97+
9098
source = info.source
9199
source = f"[{source}]({source})" if source.startswith("http") else f"`{source}`"
92100
authors = ", ".join(info.authors)
@@ -101,6 +109,9 @@ def build_catalog(catalog: _catalog.Catalog) -> None:
101109
if k in INCLUDE_DATA
102110
}
103111

112+
_aliases = [x for x in info.aliases if x != info.name]
113+
aliases = _make_aliases_md(_aliases) if _aliases else ""
114+
104115
# write the actual markdown file
105116
with mkdocs_gen_files.open(f"catalog/{category}/{name.lower()}.md", "w") as f:
106117
f.write(
@@ -110,10 +121,15 @@ def build_catalog(catalog: _catalog.Catalog) -> None:
110121
license=license_,
111122
authors=authors,
112123
source=source,
124+
aliases=aliases,
113125
info=info.info,
114126
data=json.dumps({name: cmap_data}, separators=(",", ":")),
115127
)
116128
)
117129

118130

131+
def _make_aliases_md(aliases: list[str]) -> str:
132+
return "**Aliases**: " + ", ".join(f"`{a}`" for a in aliases)
133+
134+
119135
build_catalog(_catalog.catalog)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ source = "vcs"
8383

8484
# https://hatch.pypa.io/latest/config/build/#file-selection
8585
[tool.hatch.build.targets.sdist]
86-
include = ["/src", "/tests"]
86+
include = ["/src", "/tests", "CHANGELOG.md"]
8787

8888

8989
# https://github.com/charliermarsh/ruff

src/cmap/_catalog.py

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
"""Catalog of available colormaps.
2+
3+
This module contains the logic that indexes all of the "record.json" files found
4+
in the data directory.
5+
6+
TODO: this needs to be cleaned up, and documented better.
7+
"""
18
from __future__ import annotations
29

310
import json
4-
import warnings
11+
import logging
512
from dataclasses import dataclass, field
613
from pathlib import Path
714
from typing import TYPE_CHECKING, Iterator, Literal, Mapping, cast
@@ -23,13 +30,16 @@ class CatalogItem(TypedDict):
2330
tags: NotRequired[list[str]]
2431
interpolation: NotRequired[bool]
2532
info: NotRequired[str]
33+
aliases: NotRequired[list[str]]
2634

2735
class CatalogAlias(TypedDict):
2836
alias: str
2937
conflicts: NotRequired[list[str]]
3038

3139
CatalogDict: TypeAlias = dict[str, CatalogItem]
3240

41+
logger = logging.getLogger("cmap")
42+
3343

3444
def _norm_name(name: str) -> str:
3545
return name.lower().replace(" ", "_").replace("-", "_")
@@ -47,6 +57,11 @@ class LoadedCatalogItem:
4757
authors: list[str] = field(default_factory=list)
4858
interpolation: bool | Interpolation = "linear"
4959
tags: list[str] = field(default_factory=list)
60+
aliases: list[str] = field(default_factory=list)
61+
62+
@property
63+
def qualified_name(self) -> str:
64+
return f"{self.namespace}:{self.name}"
5065

5166

5267
CATALOG: dict[str, CatalogItem | CatalogAlias] = {}
@@ -62,30 +77,62 @@ def _populate_catalog() -> None:
6277
for r in sorted(Path(cmap.data.__file__).parent.rglob("record.json")):
6378
with open(r) as f:
6479
data = json.load(f)
80+
namespace = data["namespace"]
6581
for name, v in data["colormaps"].items():
66-
v = cast("CatalogItem | CatalogAlias", v)
67-
namespaced = f"{data['namespace']}:{name}"
82+
namespaced = f"{namespace}:{name}"
83+
84+
# if the key "alias" exists, this is a CatalogAlias.
85+
# We just add it to the catalog under both the namespaced name
86+
# and the short name. The Catalog._load method will handle the resolution
87+
# of the alias.
6888
if "alias" in v:
89+
v = cast("CatalogAlias", v)
6990
if ":" not in v["alias"]: # pragma: no cover
7091
raise ValueError(f"{namespaced!r} alias is not namespaced")
7192
CATALOG[namespaced] = v
7293
CATALOG[name] = v # FIXME
7394
continue
7495

96+
# otherwise we have a CatalogItem
97+
v = cast("CatalogItem", v)
98+
99+
# here we add any global keys to the colormap that are not already there.
75100
for k in ("license", "namespace", "source", "authors", "category"):
76101
if k in data:
77102
v.setdefault(k, data[k])
78103

104+
# add the fully namespaced colormap to the catalog
79105
CATALOG[namespaced] = v
80106

107+
# if the short name is not already in the catalog, add it as a pointer
108+
# to the fully namespaced colormap.
81109
if name not in CATALOG:
82110
CATALOG[name] = {"alias": namespaced, "conflicts": []}
83111
else:
84-
cast("CatalogAlias", CATALOG[name])["conflicts"].append(namespaced)
112+
# if the short name is already in the catalog, we have a conflict.
113+
# add the fully namespaced name to the conflicts list.
114+
entry = cast("CatalogAlias", CATALOG[name])
115+
entry.setdefault("conflicts", []).append(namespaced)
116+
117+
# lastly, the `aliases` key of a colormap refers to aliases within the
118+
# namespace. These are keys that *must* be accessed using the fullly
119+
# namespaced name (with a colon). We add these to the catalog as well
120+
# so that they can be
121+
for alias in v.get("aliases", []):
122+
if ":" in alias: # pragma: no cover
123+
raise ValueError(
124+
f"internal alias {alias!r} in namespace {namespace} "
125+
"should not have colon."
126+
)
127+
CATALOG[f"{namespace}:{alias}"] = {"alias": namespaced}
85128

86129

87130
_populate_catalog()
88131
_CATALOG_LOWER = {_norm_name(k): v for k, v in CATALOG.items()}
132+
_ALIASES: dict[str, list[str]] = {}
133+
for k, v in _CATALOG_LOWER.items():
134+
if alias := v.get("alias"):
135+
_ALIASES.setdefault(_norm_name(alias), []).append(k) # type: ignore
89136

90137

91138
class Catalog(Mapping[str, "LoadedCatalogItem"]):
@@ -119,11 +166,10 @@ def _load(self, key: str) -> LoadedCatalogItem:
119166
item = cast("CatalogAlias", item)
120167
namespaced = item["alias"]
121168
if conflicts := item.get("conflicts"):
122-
warnings.warn(
123-
f"The name {key!r} is an alias for {namespaced!r}, but is also "
124-
f"available as: {', '.join(conflicts)!r}. To silence this "
125-
"warning, use a fully namespaced name.",
126-
stacklevel=2,
169+
logger.warning(
170+
f"WARNING: The name {key!r} is an alias for {namespaced!r}, "
171+
f"but is also available as: {', '.join(conflicts)!r}.\nTo "
172+
"silence this warning, use a fully namespaced name.",
127173
)
128174
return self[namespaced]
129175

@@ -136,6 +182,7 @@ def _load(self, key: str) -> LoadedCatalogItem:
136182
# well tested on internal data though
137183
mod = __import__(module, fromlist=[attr])
138184
_item["data"] = getattr(mod, attr)
185+
_item["aliases"] = _ALIASES.get(key, [])
139186
return LoadedCatalogItem(name=key.split(":", 1)[-1], **_item)
140187

141188

src/cmap/_external.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def rich_print_colormap(cm: Colormap, width: int | None = None) -> None:
165165
if cm.interpolation == "nearest":
166166
width = len(cm.color_stops)
167167
else:
168-
width or (console.width - 12)
168+
width = width or (console.width - 12)
169169
for color in cm.iter_colors(width):
170170
color_cell += Text(" ", style=Style(bgcolor=color.hex[:7]))
171171
console.print(color_cell)

0 commit comments

Comments
 (0)