Files

100 lines
3.8 KiB
Python

"""One-off converter: the CNX/Mooban view CSS -> isokit theme TOML.
python tools/css2theme.py C:/dev/mooban/tools/estate-view/iso-mooban.css themes/default.toml
Palette blocks (`.iso-wrap { --iso-x: ... }` and the light block) become
[palette.dark] / [palette.light]; one-class rules that only set `--b` become
[materials]; every other plain rule becomes [rules]. Structural rules (stage,
toolbar, tooltip, faces, tags, selection, animation, full screen) live in
viewer/isokit-view.css and are skipped here.
"""
import re
import sys
STRUCTURAL = re.compile(
r"iso-(wrap|stage|svg$|bar|seg|compass|tip|note|fs|fullbtn|tag\b|tag |signtag|warntag|hide|ghost|hov|sel|dim|"
r"dragging|off|still|fan\b|puff|smoke|glint|disc\b|walk|crane\b|tin-|tout-|led[rgo]?\b|park\b|drive|a\b)"
)
FACE = re.compile(r"\.iso-(t|l|c|r)\b(?![-\w])")
def blocks(css):
css = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
i, n = 0, len(css)
while i < n:
j = css.find("{", i)
if j < 0:
break
sel = css[i:j].strip()
if sel.startswith("@"):
depth, k = 1, j + 1
while depth and k < n:
depth += {"{": 1, "}": -1}.get(css[k], 0)
k += 1
i = k
continue
k = css.find("}", j)
yield sel, css[j + 1:k].strip()
i = k + 1
def decls(body):
out = []
for d in body.split(";"):
d = d.strip()
if d:
k, v = d.split(":", 1)
out.append((k.strip(), v.strip()))
return out
def q(s):
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
def main(src, dst):
css = open(src, encoding="utf-8").read()
dark, light, mats, rules = {}, {}, {}, {}
for sel, body in blocks(css):
ds = decls(body)
if sel in (".iso-wrap", "html[data-theme=light] .iso-wrap"):
pal = dark if sel == ".iso-wrap" else light
for k, v in ds:
if k.startswith("--iso-"):
pal[k[6:]] = v
continue
m = re.fullmatch(r"\.iso-([\w-]+)", sel)
if m and len(ds) == 1 and ds[0][0] == "--b":
v = ds[0][1]
vm = re.fullmatch(r"var\(--iso-([\w-]+)\)", v)
mats[m.group(1)] = vm.group(1) if vm else v
continue
parts = [p.strip() for p in sel.split(",")]
if any(STRUCTURAL.search(p) for p in parts) or all(FACE.search(p) and "iso-svg .iso-" in p and len(p.split()) == 2 for p in parts):
continue
rules[sel] = "; ".join(f"{k}: {v}" for k, v in ds)
with open(dst, "w", encoding="utf-8", newline="\n") as f:
f.write("# isokit default theme, converted from the CNX Vinyl / CNX Scuba / Mooban views.\n")
f.write("# palette.*: CSS variables --iso-<name> (dark is the default, light applies under\n")
f.write("# html[data-theme=light]); materials: class iso-<name> sets the face colour --b to a\n")
f.write("# palette entry or a literal colour; rules: any other CSS, selector = declarations.\n")
f.write("# A project theme file uses the same tables and overrides entries by key.\n\n")
f.write('name = "default"\n\n')
for name, pal in (("dark", dark), ("light", light)):
f.write(f"[palette.{name}]\n")
for k, v in pal.items():
f.write(f"{q(k) if not re.fullmatch(r'[A-Za-z0-9_-]+', k) else k} = {q(v)}\n")
f.write("\n")
f.write("[materials]\n")
for k, v in mats.items():
f.write(f"{k} = {q(v)}\n")
f.write("\n[rules]\n")
for k, v in rules.items():
f.write(f"{q(k)} = {q(v)}\n")
print(f"palette dark {len(dark)}, light {len(light)}, materials {len(mats)}, rules {len(rules)}")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2])