#!/usr/bin/env python3 """ Build-time theme expander — merges layout sections from ui.toml into skin files. Called by CMake during build. Reads source skin files + ui.toml, writes merged output files to the build directory. Source files are never modified. Usage: python3 expand_themes.py For each .toml file in source_themes_dir (except ui.toml), the script: 1. Copies the skin file contents (theme/palette/backdrop/effects) 2. Appends all layout sections from ui.toml (fonts, tabs, components, etc.) 3. Writes the merged result to output_themes_dir/ ui.toml itself is copied unchanged. """ import re import sys import os # Sections to SKIP when extracting from ui.toml (theme-specific, already in skins) SKIP_SECTIONS = {"theme", "theme.palette", "backdrop", "effects"} def extract_layout_sections(ui_toml_path): """Extract non-theme sections from ui.toml as a string.""" sections = [] current_section = None current_lines = [] section_re = re.compile(r'^\[{1,2}([^\]]+)\]{1,2}\s*$') with open(ui_toml_path, 'r') as f: for line in f: m = section_re.match(line.strip()) if m: if current_section is not None or current_lines: sections.append((current_section, current_lines)) current_section = m.group(1).strip() current_lines = [line] else: current_lines.append(line) if current_section is not None or current_lines: sections.append((current_section, current_lines)) layout_parts = [] for section_name, lines in sections: if section_name is None: # Preamble: only include top-level key=value lines kv_lines = [l for l in lines if l.strip() and not l.strip().startswith('#') and '=' in l] if kv_lines: layout_parts.append(''.join(kv_lines)) continue if section_name in SKIP_SECTIONS: continue layout_parts.append(''.join(lines)) return '\n'.join(layout_parts) def main(): if len(sys.argv) != 3: print(f"Usage: {sys.argv[0]} ") sys.exit(1) src_dir = sys.argv[1] out_dir = sys.argv[2] ui_toml = os.path.join(src_dir, "ui.toml") if not os.path.exists(ui_toml): print(f"ERROR: ui.toml not found at {ui_toml}") sys.exit(1) os.makedirs(out_dir, exist_ok=True) layout_content = extract_layout_sections(ui_toml) separator = ( "\n# ===========================================================================\n" "# Layout & Component Properties\n" "# All values below can be customized per-theme. Edit and save to see\n" "# changes reflected in the app in real time via hot-reload.\n" "# ===========================================================================\n\n" ) for fname in sorted(os.listdir(src_dir)): if not fname.endswith('.toml'): continue src_path = os.path.join(src_dir, fname) dst_path = os.path.join(out_dir, fname) if fname == "ui.toml": # Copy ui.toml unchanged with open(src_path, 'r') as f: content = f.read() with open(dst_path, 'w') as f: f.write(content) print(f" COPY {fname}") continue # Skin file — append layout sections with open(src_path, 'r') as f: skin = f.read() if not skin.endswith('\n'): skin += '\n' merged = skin + separator + layout_content if not merged.endswith('\n'): merged += '\n' with open(dst_path, 'w') as f: f.write(merged) lines = merged.count('\n') print(f" MERGE {fname} → {lines} lines") print("Done.") if __name__ == "__main__": main()