diff options
Diffstat (limited to 'scripts/theme-studio/capture-default-faces.py')
| -rw-r--r-- | scripts/theme-studio/capture-default-faces.py | 115 |
1 files changed, 69 insertions, 46 deletions
diff --git a/scripts/theme-studio/capture-default-faces.py b/scripts/theme-studio/capture-default-faces.py index 60f8967da..a5214fd5a 100644 --- a/scripts/theme-studio/capture-default-faces.py +++ b/scripts/theme-studio/capture-default-faces.py @@ -17,6 +17,8 @@ import re import subprocess import tempfile +from face_specs import FACE_ATTRS + HERE = pathlib.Path(__file__).resolve().parent ROOT = HERE.parents[1] OUT = HERE / "emacs-default-faces.json" @@ -85,20 +87,11 @@ BUILTIN_FEATURES = [ "shr", ] -ATTRS = { - ":foreground": "foreground", - ":background": "background", - ":weight": "weight", - ":slant": "slant", - ":underline": "underline", - ":strike-through": "strike", - ":box": "box", - ":height": "height", - ":inherit": "inherit", - ":inverse-video": "inverseVideo", - ":extend": "extend", - ":distant-foreground": "distantForeground", -} +# Emacs face :attribute keyword -> snapshot field name, derived from the shared +# face-attribute spec so the capture, the seed extraction, and STYLE_DEFAULTS all +# stay in step. Attributes the snapshot doesn't carry (e.g. family) have no +# capture keyword and are skipped. +ATTRS = {a["capture"]: a["snapshot"] for a in FACE_ATTRS if a["capture"]} def x11_colors() -> dict[str, str]: @@ -170,47 +163,45 @@ def normalize_value(value: object) -> object: return value +def _condition_clauses_pass(clauses: dict) -> bool: + """Apply the four display-condition rules to a {key: values} mapping. + Returns False when any present clause excludes the GUI-light target.""" + if "class" in clauses: + vals = clauses["class"] + if "color" not in vals and "grayscale" not in vals: + return False + if "min-colors" in clauses: + vals = clauses["min-colors"] + if vals and isinstance(vals[0], int) and vals[0] > 16777216: + return False + if "background" in clauses: + vals = clauses["background"] + if vals and "light" not in vals: + return False + if "type" in clauses: + if "tty" in clauses["type"]: + return False + return True + + def condition_matches(condition: object) -> bool: if condition in (True, "t", None): return True if condition == "default": return False + # Normalize the two display-spec shapes -- a {key: values} dict, or a list of + # [key, *values] clauses -- to one {key: values} mapping, then run the four + # rules once (see `_condition_clauses_pass'). if isinstance(condition, dict): - if "class" in condition: - vals = condition["class"] or [] - if "color" not in vals and "grayscale" not in vals: - return False - if "min-colors" in condition: - vals = condition["min-colors"] or [] - if vals and isinstance(vals[0], int) and vals[0] > 16777216: - return False - if "background" in condition: - vals = condition["background"] or [] - if vals and "light" not in vals: - return False - if "type" in condition and "tty" in (condition["type"] or []): - return False - return True + clauses = {k: (condition[k] or []) for k in condition} + return _condition_clauses_pass(clauses) if not isinstance(condition, list): return False + clauses = {} for clause in condition: - if not isinstance(clause, list) or not clause: - continue - key = clause[0] - vals = clause[1:] - if key == "class": - if "color" not in vals and "grayscale" not in vals: - return False - elif key == "min-colors": - if vals and isinstance(vals[0], int) and vals[0] > 16777216: - return False - elif key == "background": - if vals and "light" not in vals: - return False - elif key == "type": - if "tty" in vals: - return False - return True + if isinstance(clause, list) and clause: + clauses[clause[0]] = clause[1:] + return _condition_clauses_pass(clauses) def choose_gui_light(default_spec: object) -> dict[str, object]: @@ -364,9 +355,40 @@ def main() -> None: (add-to-list 'load-path dir)) (dolist (feature (mapcar #'intern ts-probe-builtin-features)) (ignore-errors (require feature))) +(defun ts-probe--eval-deffaces (file) + "Evaluate only the `defface' forms in FILE. + +A defface form is self-contained, so registering a face this way avoids +loading the whole package (and its dependencies / side effects), which in +batch -Q is fragile: a missing dependency or mid-load error would silently +drop every face in the file. Each form is evaluated independently. + +Catch any error from `read', not just `end-of-file': a reader-level error +(unrecognized syntax) otherwise propagates out and aborts the whole pass, +dropping every face in every later file. Stop reading this file instead." + (when (file-readable-p file) + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (condition-case nil + (while t + (let ((form (read (current-buffer)))) + (when (and (consp form) (eq (car form) 'defface)) + (ignore-errors (eval form t))))) + (error nil))))) +;; Pass 1: best-effort full load. Registers faces that are defined by a macro +;; or loop rather than a literal defface (e.g. rainbow-delimiters depth faces, +;; markdown header faces), which pass 2 cannot see. Failures are swallowed. (dolist (file ts-probe-package-files) (with-temp-file {elisp_quote(str(PROGRESS))} (insert file)) (ignore-errors (load file nil t))) +;; Pass 2: evaluate literal defface forms directly. Robustly registers faces +;; whose package failed to fully load in pass 1 (e.g. transient needing +;; cond-let, magit's transient/forge stack) and resets literal faces to their +;; pristine defface default spec. Runs last so the default spec wins over any +;; customization a pass-1 load may have applied. +(dolist (file ts-probe-package-files) + (ts-probe--eval-deffaces file)) (defun ts-probe--proper-list-p (value) (or (null value) (and (consp value) (ts-probe--proper-list-p (cdr value))))) @@ -397,6 +419,7 @@ def main() -> None: (slant . ,(ts-probe--safe (ts-probe--attr face :slant))) (underline . ,(ts-probe--safe (ts-probe--attr face :underline))) (strike . ,(ts-probe--safe (ts-probe--attr face :strike-through))) + (overline . ,(ts-probe--safe (ts-probe--attr face :overline))) (box . ,(ts-probe--safe (ts-probe--attr face :box))) (height . ,(ts-probe--safe (ts-probe--attr face :height))) (inherit . ,(ts-probe--safe (ts-probe--attr face :inherit))) |
