aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-selector/build-theme.el
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/theme-selector/build-theme.el')
-rw-r--r--scripts/theme-selector/build-theme.el244
1 files changed, 0 insertions, 244 deletions
diff --git a/scripts/theme-selector/build-theme.el b/scripts/theme-selector/build-theme.el
deleted file mode 100644
index fe080c0d1..000000000
--- a/scripts/theme-selector/build-theme.el
+++ /dev/null
@@ -1,244 +0,0 @@
-;;; build-theme.el --- Convert a theme-selector theme.json into a deftheme -*- lexical-binding: t -*-
-
-;; Author: Craig Jennings <c@cjennings.net>
-
-;;; Commentary:
-
-;; The last link in the theme-selector pipeline: turn a theme.json exported by
-;; the tool (see scripts/theme-selector/README.md and
-;; docs/design/theme-selector-package-faces-spec.org) into a single,
-;; self-contained, loadable Emacs deftheme written to themes/<name>-theme.el.
-;;
-;; Four tiers come out of the JSON:
-;; - default -- background from assignments.bg, foreground from .p
-;; - syntax -- assignments.<cat> -> font-lock / tree-sitter faces, with
-;; the bold / italic category sets applied
-;; - ui -- the ui keys are already real face names; fg/bg passthrough
-;; - packages -- per-package face specs with :inherit / :height / weight /
-;; slant
-;;
-;; Usage (from a shell or a running Emacs):
-;;
-;; emacsclient -e '(progn (load ".../build-theme.el")
-;; (build-theme/convert-file ".../dupre-revised.json"))'
-;;
-;; or in batch:
-;;
-;; emacs --batch -l build-theme.el \
-;; --eval '(build-theme/convert-file "dupre-revised.json" "themes")'
-;;
-;; The output is a flat generated deftheme, not the hand-authored
-;; palette/faces/theme trio that the original dupre theme ships -- a theme.json
-;; carries resolved per-face hex, not dupre's semantic-mapping layer, so a flat
-;; deftheme is the faithful output and never clobbers the curated dupre files.
-
-;;; Code:
-
-(require 'json)
-(require 'subr-x)
-
-(defconst build-theme/--syntax-face-map
- '((kw . (font-lock-keyword-face))
- (bi . (font-lock-builtin-face))
- (pp . (font-lock-preprocessor-face))
- (fnd . (font-lock-function-name-face))
- (fnc . (font-lock-function-call-face))
- (ty . (font-lock-type-face))
- (prop . (font-lock-property-name-face font-lock-property-use-face))
- (con . (font-lock-constant-face))
- (num . (font-lock-number-face))
- (str . (font-lock-string-face))
- (esc . (font-lock-escape-face))
- (re . (font-lock-regexp-face))
- (doc . (font-lock-doc-face))
- (cm . (font-lock-comment-face))
- (cmd . (font-lock-comment-delimiter-face))
- (var . (font-lock-variable-name-face font-lock-variable-use-face))
- (op . (font-lock-operator-face))
- (punc . (font-lock-punctuation-face font-lock-bracket-face
- font-lock-delimiter-face font-lock-misc-punctuation-face)))
- "Map each theme.json syntax-category key to the font-lock faces it colors.
-A category may fan out to several faces (e.g. punc covers bracket and
-delimiter too). The dec (decorator) key is deliberately absent: Emacs has
-no dedicated decorator face -- it renders decorators with
-`font-lock-type-face', which the ty key already owns -- so coloring dec
-independently is not possible without clobbering types.")
-
-;;; ---------------------------------------------------------------------------
-;;; Pure helpers
-
-(defun build-theme/--hex-p (s)
- "Non-nil when S is a \"#rrggbb\" hex color string."
- (and (stringp s) (string-match-p "\\`#[0-9a-fA-F]\\{6\\}\\'" s)))
-
-(defun build-theme/--attrs (inherit fg bg bold italic underline strike height)
- "Build a face-attribute plist from the given fields, in canonical order.
-INHERIT is a face symbol or nil. FG and BG are hex strings or nil. BOLD,
-ITALIC, UNDERLINE, and STRIKE are booleans. HEIGHT is a float multiplier; 1.0
-(or nil) is omitted as the default. Only set attributes are written, so a
-fully-nil face yields an empty plist."
- (let (plist)
- (when (and height (numberp height) (/= height 1.0))
- (setq plist (list :height height)))
- (when strike (setq plist (append (list :strike-through t) plist)))
- (when underline (setq plist (append (list :underline t) plist)))
- (when italic (setq plist (append (list :slant 'italic) plist)))
- (when bold (setq plist (append (list :weight 'bold) plist)))
- (when bg (setq plist (append (list :background bg) plist)))
- (when fg (setq plist (append (list :foreground fg) plist)))
- (when inherit (setq plist (append (list :inherit inherit) plist)))
- plist))
-
-(defun build-theme/--face-spec (face attrs)
- "Wrap FACE and its ATTRS plist as a `custom-theme-set-faces' spec.
-Return nil when ATTRS is empty, so cleared faces emit nothing."
- (when attrs
- (list face (list (list t attrs)))))
-
-(defun build-theme/--obj-get (obj key)
- "Value of KEY in alist OBJ, or nil."
- (cdr (assq key obj)))
-
-(defun build-theme/--inherit-symbol (value)
- "Coerce an inherit VALUE (a face-name string, symbol, or nil) to a symbol."
- (cond ((null value) nil)
- ((symbolp value) value)
- ((stringp value) (intern value))
- (t nil)))
-
-;;; ---------------------------------------------------------------------------
-;;; Tiers
-
-(defun build-theme/--default-spec (assignments)
- "Build the `default' face spec from ASSIGNMENTS bg / p."
- (let ((bg (build-theme/--obj-get assignments 'bg))
- (fg (build-theme/--obj-get assignments 'p)))
- (build-theme/--face-spec 'default (build-theme/--attrs nil fg bg nil nil nil nil nil))))
-
-(defun build-theme/--syntax-face-specs (assignments bold italic)
- "Build syntax-tier face specs from ASSIGNMENTS plus the BOLD and ITALIC sets.
-BOLD and ITALIC are lists of category-key symbols. Each category fans out to
-the font-lock faces in `build-theme/--syntax-face-map'."
- (let (specs)
- (dolist (pair build-theme/--syntax-face-map)
- (let* ((cat (car pair))
- (faces (cdr pair))
- (hex (build-theme/--obj-get assignments cat)))
- (when hex
- (let ((attrs (build-theme/--attrs nil hex nil
- (memq cat bold) (memq cat italic) nil nil nil)))
- (dolist (face faces)
- (when-let ((spec (build-theme/--face-spec face attrs)))
- (push spec specs)))))))
- (nreverse specs)))
-
-(defun build-theme/--ui-face-specs (ui)
- "Build UI-tier face specs from the UI alist (face -> {fg,bg,bold,italic})."
- (let (specs)
- (dolist (entry ui)
- (let* ((face (car entry))
- (obj (cdr entry))
- (attrs (build-theme/--attrs nil
- (build-theme/--obj-get obj 'fg)
- (build-theme/--obj-get obj 'bg)
- (build-theme/--obj-get obj 'bold)
- (build-theme/--obj-get obj 'italic)
- (build-theme/--obj-get obj 'underline)
- (build-theme/--obj-get obj 'strike)
- nil)))
- (when-let ((spec (build-theme/--face-spec face attrs)))
- (push spec specs))))
- (nreverse specs)))
-
-(defun build-theme/--package-face-specs (packages)
- "Build package-tier face specs from the PACKAGES alist (app -> face -> spec)."
- (let (specs)
- (dolist (app packages)
- (dolist (entry (cdr app))
- (let* ((face (car entry))
- (obj (cdr entry))
- (attrs (build-theme/--attrs
- (build-theme/--inherit-symbol (build-theme/--obj-get obj 'inherit))
- (build-theme/--obj-get obj 'fg)
- (build-theme/--obj-get obj 'bg)
- (build-theme/--obj-get obj 'bold)
- (build-theme/--obj-get obj 'italic)
- (build-theme/--obj-get obj 'underline)
- (build-theme/--obj-get obj 'strike)
- (build-theme/--obj-get obj 'height))))
- (when-let ((spec (build-theme/--face-spec face attrs)))
- (push spec specs)))))
- (nreverse specs)))
-
-(defun build-theme/--all-specs (data)
- "Build the full ordered face-spec list from parsed theme.json DATA."
- (let ((assignments (build-theme/--obj-get data 'assignments))
- (bold (mapcar #'intern (build-theme/--obj-get data 'bold)))
- (italic (mapcar #'intern (build-theme/--obj-get data 'italic)))
- (ui (build-theme/--obj-get data 'ui))
- (packages (build-theme/--obj-get data 'packages)))
- (delq nil
- (append
- (list (build-theme/--default-spec assignments))
- (build-theme/--syntax-face-specs assignments bold italic)
- (build-theme/--ui-face-specs ui)
- (build-theme/--package-face-specs packages)))))
-
-;;; ---------------------------------------------------------------------------
-;;; Rendering
-
-(defun build-theme/--render (name specs)
- "Render a deftheme file body for theme NAME from face SPECS, as a string."
- (concat
- (format ";;; %s-theme.el --- Generated by theme-selector -*- lexical-binding: t -*-\n" name)
- "\n;;; Commentary:\n"
- (format ";; Generated from %s.json by scripts/theme-selector/build-theme.el.\n" name)
- ";; Do not hand-edit; re-run the converter.\n"
- "\n;;; Code:\n\n"
- (format "(deftheme %s\n \"Generated by theme-selector.\")\n\n" name)
- (format "(custom-theme-set-faces\n '%s\n" name)
- ;; Each spec is quoted: custom-theme-set-faces is a function, so an
- ;; unquoted (face ((t ...))) would be evaluated as a call. Specs hold
- ;; only literal strings, symbols, and numbers, so a plain quote suffices.
- (mapconcat (lambda (spec) (concat " '" (prin1-to-string spec))) specs "\n")
- ")\n\n"
- (format "(provide-theme '%s)\n" name)
- (format ";;; %s-theme.el ends here\n" name)))
-
-(defun build-theme/--parse (json-file)
- "Parse JSON-FILE into an alist, with null/false as nil and arrays as lists.
-Signal a `file-missing' error when JSON-FILE does not exist."
- (unless (file-readable-p json-file)
- (signal 'file-missing (list "Cannot read theme.json" json-file)))
- (with-temp-buffer
- (insert-file-contents json-file)
- (goto-char (point-min))
- (json-parse-buffer :object-type 'alist :array-type 'list
- :null-object nil :false-object nil)))
-
-;;; ---------------------------------------------------------------------------
-;;; Entry point
-
-(defun build-theme/convert-file (json-file &optional out-dir)
- "Convert JSON-FILE (a theme.json export) into a deftheme file.
-Write themes/<name>-theme.el, where <name> is the JSON name field, into
-OUT-DIR (default: the themes/ directory of this repo). Return the written
-path."
- (let* ((data (build-theme/--parse json-file))
- (name (build-theme/--obj-get data 'name))
- (specs (build-theme/--all-specs data))
- (dir (or out-dir
- (expand-file-name
- "../../themes"
- (file-name-directory (or load-file-name buffer-file-name
- default-directory)))))
- (out (expand-file-name (format "%s-theme.el" name) dir)))
- (unless (and (stringp name) (string-match-p "\\`[a-zA-Z][a-zA-Z0-9-]*\\'" name))
- (error "Invalid theme name in %s: %S" json-file name))
- (make-directory dir t)
- (with-temp-file out
- (insert (build-theme/--render name specs)))
- out))
-
-(provide 'build-theme)
-;;; build-theme.el ends here