aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/capture-default-faces.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/theme-studio/capture-default-faces.py')
-rw-r--r--scripts/theme-studio/capture-default-faces.py115
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)))