#!/usr/bin/env python3 """Convert ObservableObject/@Published Swift files to imperative onChange pattern.""" import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] / "suixinkan_ios" def convert_file(path: Path) -> bool: text = path.read_text(encoding="utf-8") original = text if "ObservableObject" not in text and "@Published" not in text and "import Combine" not in text: return False text = re.sub(r"^import Combine\n", "", text, flags=re.MULTILINE) if ": ObservableObject" in text: text = text.replace(": ObservableObject", "") # Insert onChange after class opening brace def add_onchange(match): decl = match.group(0) if "onChange:" in text[match.end():match.end()+200]: return decl return decl + "\n var onChange: (() -> Void)?\n" text = re.sub( r"(final class \w+[^{]*\{)\n", add_onchange, text, count=1, ) def repl_published(m): access = m.group(1) or "" name = m.group(2) rest = m.group(3) return f"{access}var {name}{rest} {{ didSet {{ onChange?() }} }}\n" text = re.sub( r"@Published\s+(private\(set\)\s+)?var\s+(\w+)([^=\n]*=\s*[^\n]+)\n", repl_published, text, ) text = re.sub( r"@Published\s+(private\(set\)\s+)?var\s+(\w+)([^=\n]*)\n", lambda m: f"{m.group(1) or ''}var {m.group(2)}{m.group(3)} {{ didSet {{ onChange?() }} }}\n", text, ) if text != original: path.write_text(text, encoding="utf-8") return True return False def main(): changed = 0 for path in ROOT.rglob("*.swift"): if convert_file(path): changed += 1 print(f"converted: {path.relative_to(ROOT.parent)}") print(f"Done. {changed} files converted.") if __name__ == "__main__": main()