From c05364951bf59edde7d0d0eb35013dfc077d40cf Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 8 Jun 2026 02:32:45 -0500 Subject: feat(theme-selector): generated all-package inventory (tier-3 phase 6) I added the hybrid inventory. build-inventory.el, loaded into a running Emacs, queries every installed package's faces grouped by the package that defines them and writes package-inventory.json. generate.py embeds that file and merges each package into the app dropdown as an editable generic app, leaving the bespoke org, magit, and elfeed untouched. The dropdown now reaches 40 apps: the three bespoke plus 37 inventory packages (643 faces), so any installed package can be themed against the palette with the generic preview. The inventory is a committed data artifact refreshed by reloading the .el, never browser-side discovery, matching the spec's hybrid-and-split decision. --- scripts/theme-selector/build-inventory.el | 31 ++ scripts/theme-selector/generate.py | 11 + scripts/theme-selector/package-inventory.json | 723 ++++++++++++++++++++++++++ scripts/theme-selector/theme-selector.html | 2 +- todo.org | 4 +- 5 files changed, 768 insertions(+), 3 deletions(-) create mode 100644 scripts/theme-selector/build-inventory.el create mode 100644 scripts/theme-selector/package-inventory.json diff --git a/scripts/theme-selector/build-inventory.el b/scripts/theme-selector/build-inventory.el new file mode 100644 index 00000000..52e14baa --- /dev/null +++ b/scripts/theme-selector/build-inventory.el @@ -0,0 +1,31 @@ +;;; build-inventory.el --- emit package->faces inventory for theme-selector -*- lexical-binding: t -*- +;;; Commentary: +;; Loaded into a running Emacs (emacsclient -e '(load ".../build-inventory.el")') +;; to write package-inventory.json next to itself: a JSON object mapping each +;; installed (elpa/straight) package to the faces it defines, grouped by the +;; package that owns the face's definition file. Built-in faces are skipped. +;; generate.py embeds the JSON so the theme-selector dropdown can reach every +;; installed package (tier-3 phase 6, the "theme every package" path). +;;; Code: + +(require 'json) + +(let ((h (make-hash-table :test 'equal))) + (dolist (f (face-list)) + (let* ((file (ignore-errors (symbol-file f 'defface))) + (pkg (and (stringp file) + (string-match "/\\(?:elpa\\|straight/build\\|site-lisp\\)/\\([a-zA-Z0-9._-]+?\\)-[0-9][^/]*/" file) + (match-string 1 file)))) + (when pkg (push (symbol-name f) (gethash pkg h))))) + (let (al) + (maphash (lambda (k v) (push (cons (intern k) (sort v #'string<)) al)) h) + (setq al (sort al (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))) + (with-temp-file (expand-file-name + "package-inventory.json" + (file-name-directory (or load-file-name buffer-file-name + "~/.emacs.d/scripts/theme-selector/"))) + (let ((json-encoding-pretty-print t)) + (insert (json-encode al) "\n"))))) + +(provide 'build-inventory) +;;; build-inventory.el ends here diff --git a/scripts/theme-selector/generate.py b/scripts/theme-selector/generate.py index 80e9ad54..a7bc1b73 100644 --- a/scripts/theme-selector/generate.py +++ b/scripts/theme-selector/generate.py @@ -174,6 +174,17 @@ def _faces(names,prefix,seed): APPS={"org-mode":{"label":"org-mode","preview":"org","faces":_faces(ORG_FACES,"org-",ORG_SEED)}, "magit":{"label":"magit","preview":"magit","faces":_faces(MAGIT_FACES,"magit-",MAGIT_SEED)}, "elfeed":{"label":"elfeed","preview":"elfeed","faces":_faces(ELFEED_FACES,"elfeed-",ELFEED_SEED)}} +# Phase 6: merge the generated all-package inventory (refresh with build-inventory.el). +# Bespoke apps stay; every other installed package becomes an editable generic app. +_inv_path=os.path.join(HERE,"package-inventory.json") +if os.path.exists(_inv_path): + _INV=json.load(open(_inv_path)) + _BESPOKE={"magit","elfeed","org","org-mode"} + for _pkg in sorted(_INV): + if _pkg in _BESPOKE or _pkg in APPS: continue + APPS[_pkg]={"label":_pkg,"preview":"generic","faces":[ + [f,(f[len(_pkg)+1:] if f.startswith(_pkg+"-") else f).replace("-face","").replace("-"," "),{}] + for f in _INV[_pkg]]} HTML = """theme-selector