mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-04 12:02:33 +02:00
tools/generate-iconify-subset.py scans the PHP/JS/CSS source for literal tabler-* tokens and regenerates the used-only iconify-icons.min.css from the full iconify-icons.css. Run it after adding a new icon. Also regenerates the subset header banner to point at the script.
150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate the used-only iconify subset stylesheet.
|
|
|
|
The full `src/Public/assets/admin/vendor/fonts/iconify-icons.css` ships ~5900
|
|
Tabler icons (2.6 MB) but the panel references only ~150 of them. This scans
|
|
the PHP/JS/CSS source for literal `tabler-*` class tokens and writes a minified
|
|
`iconify-icons.min.css` next to the full file, containing just the base `.ti`
|
|
rule plus the icon rules that are actually used. Both admin and reseller shells
|
|
link the `.min.css`.
|
|
|
|
Run it after adding a NEW tabler-* icon anywhere in the source:
|
|
|
|
python3 tools/generate-iconify-subset.py
|
|
|
|
Paths are resolved by locating the repo's `src/` root above this file, so it
|
|
works from any CWD. Only literal `tabler-foo` tokens are detected — never build
|
|
icon class names dynamically (e.g. "tabler-" + name) or the subset will miss
|
|
them.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Locate the application `src/` root (repo-root/src). tools/ sits beside src/.
|
|
SRC_ROOT = os.path.join(os.path.dirname(HERE), "src")
|
|
if not os.path.isdir(SRC_ROOT):
|
|
# Fallback: ascend until a `src/` directory is found.
|
|
node = HERE
|
|
while node != os.path.dirname(node):
|
|
cand = os.path.join(node, "src")
|
|
if os.path.isdir(cand):
|
|
SRC_ROOT = cand
|
|
break
|
|
node = os.path.dirname(node)
|
|
|
|
FONTS_DIR = os.path.join(SRC_ROOT, "Public", "assets", "admin", "vendor", "fonts")
|
|
SRC = os.path.join(FONTS_DIR, "iconify-icons.css")
|
|
DST = os.path.join(FONTS_DIR, "iconify-icons.min.css")
|
|
|
|
# Source trees to scan for icon usage, relative to the `src/` root.
|
|
SCAN_DIRS = ["Public/Views", "Public/assets/admin/js", "Public/assets/admin/xcvm", "Core", "Modules"]
|
|
SCAN_EXTS = (".php", ".js", ".css")
|
|
|
|
TOKEN_RE = re.compile(r"tabler-[a-z0-9\-]+")
|
|
RULE_RE = re.compile(r"\.([a-zA-Z0-9_\-]+)\s*\{(.*?)\}", re.S)
|
|
|
|
|
|
def collect_used_tokens():
|
|
"""Return the set of literal tabler-* tokens referenced in the source."""
|
|
used = set()
|
|
for rel in SCAN_DIRS:
|
|
base = os.path.join(SRC_ROOT, rel)
|
|
for root, _dirs, files in os.walk(base):
|
|
for name in files:
|
|
if not name.endswith(SCAN_EXTS) or name.startswith("iconify-icons"):
|
|
continue
|
|
try:
|
|
text = open(os.path.join(root, name), encoding="utf-8", errors="replace").read()
|
|
except OSError:
|
|
continue
|
|
used.update(TOKEN_RE.findall(text))
|
|
return used
|
|
|
|
|
|
def minify_block(body):
|
|
"""Collapse whitespace in a rule body, preserving quoted url("...") data."""
|
|
out = []
|
|
in_str = False
|
|
quote = ""
|
|
pending = False
|
|
prev = ""
|
|
for c in body:
|
|
if in_str:
|
|
out.append(c)
|
|
if c == quote:
|
|
in_str = False
|
|
continue
|
|
if c in "\"'":
|
|
in_str = True
|
|
quote = c
|
|
out.append(c)
|
|
prev = c
|
|
pending = False
|
|
continue
|
|
if c in " \t\r\n\f":
|
|
pending = True
|
|
continue
|
|
if c in ":;,":
|
|
pending = False
|
|
out.append(c)
|
|
prev = c
|
|
continue
|
|
if pending:
|
|
if prev and prev not in ":;,(":
|
|
out.append(" ")
|
|
pending = False
|
|
out.append(c)
|
|
prev = c
|
|
return "".join(out).strip()
|
|
|
|
|
|
def main():
|
|
if not os.path.isfile(SRC):
|
|
sys.exit("source not found: %s" % SRC)
|
|
|
|
used = collect_used_tokens()
|
|
css = open(SRC, encoding="utf-8").read()
|
|
|
|
base = None
|
|
kept = []
|
|
kept_names = set()
|
|
for m in RULE_RE.finditer(css):
|
|
selector, body = m.group(1), minify_block(m.group(2))
|
|
if selector == "ti":
|
|
base = ".ti{" + body + "}"
|
|
elif selector in used:
|
|
kept.append("." + selector + "{" + body + "}")
|
|
kept_names.add(selector)
|
|
|
|
missing = sorted(used - kept_names - {"ti"})
|
|
if missing:
|
|
# Not fatal: tokens matched in source but absent from the full css
|
|
# (typos, or icons from another set). Report so they can be fixed.
|
|
sys.stderr.write("warning: %d referenced icon(s) not in %s:\n %s\n" % (
|
|
len(missing), os.path.basename(SRC), ", ".join(missing)))
|
|
|
|
if base is None:
|
|
sys.exit("base `.ti` rule not found in source css")
|
|
|
|
header = (
|
|
"/* Generated by tools/generate-iconify-subset.py — do not edit by hand.\n"
|
|
" Used-only subset of iconify-icons.css. Icons: %d.\n"
|
|
" Re-run the script after adding a new tabler-* icon. */\n" % len(kept)
|
|
)
|
|
open(DST, "w", encoding="utf-8").write(header + base + "\n" + "".join(kept) + "\n")
|
|
|
|
orig = os.path.getsize(SRC)
|
|
sub = os.path.getsize(DST)
|
|
print("icons kept : %d" % len(kept))
|
|
print("subset : %s" % DST)
|
|
print("size : %.1f KB (-%.1f%% vs full %.1f MB)" % (
|
|
sub / 1024, 100 * (1 - sub / orig), orig / 1024 / 1024))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|