aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-13 15:27:54 -0500
committerCraig Jennings <c@cjennings.net>2026-06-13 15:27:54 -0500
commit19780fa994a697966984366a54dcdfbdb7e7838c (patch)
treeeffda6c339ea9dab6fde730028496dc749d01ad2 /scripts/theme-studio
parent94363181928da1d8693f71967949b795a315318a (diff)
downloaddotemacs-19780fa994a697966984366a54dcdfbdb7e7838c.tar.gz
dotemacs-19780fa994a697966984366a54dcdfbdb7e7838c.zip
Add theme studio default face drift summary
Diffstat (limited to 'scripts/theme-studio')
-rw-r--r--scripts/theme-studio/default-face-summary.py30
-rw-r--r--scripts/theme-studio/default_faces.py32
-rw-r--r--scripts/theme-studio/test_generate.py41
3 files changed, 102 insertions, 1 deletions
diff --git a/scripts/theme-studio/default-face-summary.py b/scripts/theme-studio/default-face-summary.py
new file mode 100644
index 00000000..4a163eb4
--- /dev/null
+++ b/scripts/theme-studio/default-face-summary.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+"""Print a concise summary of theme-studio's captured Emacs default faces."""
+
+from __future__ import annotations
+
+import json
+import pathlib
+import sys
+
+from default_faces import DefaultFaces, changed_summary
+
+
+HERE = pathlib.Path(__file__).resolve().parent
+
+
+def main() -> None:
+ paths = [pathlib.Path(p) for p in sys.argv[1:]]
+ if not paths:
+ paths = [HERE / "emacs-default-faces.json"]
+ summaries = [DefaultFaces.from_path(path).summary() for path in paths]
+ if len(summaries) == 1:
+ print(json.dumps(summaries[0], indent=2, sort_keys=True))
+ elif len(summaries) == 2:
+ print(json.dumps(changed_summary(summaries[0], summaries[1]), indent=2, sort_keys=True))
+ else:
+ raise SystemExit("usage: default-face-summary.py [snapshot.json [snapshot-after.json]]")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/theme-studio/default_faces.py b/scripts/theme-studio/default_faces.py
index a2fd2720..ce2bf319 100644
--- a/scripts/theme-studio/default_faces.py
+++ b/scripts/theme-studio/default_faces.py
@@ -99,6 +99,30 @@ class DefaultFaces:
return fallback
return self.color_names.get(str(value).lower(), fallback)
+ def summary(self) -> dict[str, Any]:
+ if not self.data:
+ return {}
+ inventory = self.data.get("package-inventory", {})
+ package_faces = sorted({face for faces in inventory.values() for face in faces})
+ package_inherits = {
+ face: self.seed(face).get("inherit")
+ for face in package_faces
+ if self.seed(face).get("inherit")
+ }
+ ui_faces = self.data.get("ui-faces", [])
+ return {
+ "emacsVersion": self.data.get("meta", {}).get("emacs-version"),
+ "default": {
+ "foreground": self.color("default", "foreground"),
+ "background": self.color("default", "background"),
+ },
+ "faceCount": len(self.data.get("faces", {})),
+ "packageFaceCount": len(package_faces),
+ "packageUnresolvedFaceCount": self.data.get("meta", {}).get("package-unresolved-face-count", 0),
+ "uiOwnSeeds": {face: self.seed(face) for face in ui_faces if self.seed(face)},
+ "packageInherits": package_inherits,
+ }
+
def _build_color_hex(self) -> dict[str, str]:
out: dict[str, str] = {}
if not self.data:
@@ -126,3 +150,11 @@ class DefaultFaces:
if hex_value and name and not str(name).startswith("#"):
out.setdefault(hex_value.lower(), str(name).lower().replace(" ", "-"))
return out
+
+
+def changed_summary(before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any]:
+ changed = {}
+ for key in sorted(set(before) | set(after)):
+ if before.get(key) != after.get(key):
+ changed[key] = {"before": before.get(key), "after": after.get(key)}
+ return changed
diff --git a/scripts/theme-studio/test_generate.py b/scripts/theme-studio/test_generate.py
index 034df72b..16ed07f1 100644
--- a/scripts/theme-studio/test_generate.py
+++ b/scripts/theme-studio/test_generate.py
@@ -14,7 +14,7 @@ from collections import Counter, defaultdict
import generate # importable without side effects: the file write is __main__-guarded
from app_inventory import face_rows
-from default_faces import DefaultFaces
+from default_faces import DefaultFaces, changed_summary
from face_specs import package_face_spec, ui_face_spec
@@ -224,6 +224,45 @@ class DefaultFaceAdapter(unittest.TestCase):
self.assertEqual(defaults.seed("missing"), {})
self.assertEqual(defaults.label("#000000", "fallback"), "fallback")
+ def test_summary_reports_default_drift_fields(self):
+ defaults = DefaultFaces({
+ "meta": {"emacs-version": "30.2", "package-unresolved-face-count": 2},
+ "ui-faces": ["sample"],
+ "package-inventory": {"pkg": ["pkg-face"]},
+ "faces": {
+ "default": {
+ "effectiveGuiLight": {
+ "foregroundHex": "#000000",
+ "backgroundHex": "#ffffff",
+ },
+ "chosenGuiLight": {},
+ },
+ "sample": {
+ "chosenGuiLight": {"backgroundHex": "#ffffff"},
+ "effectiveGuiLight": {},
+ },
+ "pkg-face": {
+ "chosenGuiLight": {"inherit": "base-face"},
+ "effectiveGuiLight": {},
+ },
+ },
+ })
+ self.assertEqual(defaults.summary(), {
+ "emacsVersion": "30.2",
+ "default": {"foreground": "#000000", "background": "#ffffff"},
+ "faceCount": 3,
+ "packageFaceCount": 1,
+ "packageUnresolvedFaceCount": 2,
+ "uiOwnSeeds": {"sample": {"bg": "#ffffff"}},
+ "packageInherits": {"pkg-face": "base-face"},
+ })
+
+ def test_changed_summary_reports_only_changed_top_level_keys(self):
+ self.assertEqual(changed_summary({"a": 1, "b": 2}, {"a": 1, "b": 3, "c": 4}), {
+ "b": {"before": 2, "after": 3},
+ "c": {"before": None, "after": 4},
+ })
+
class PackageFaceCoverage(unittest.TestCase):
ALLOWED_DUPLICATES = {