P0.1: iOS reference artifacts (colors, assets, screens inventory)
This commit is contained in:
139
scripts/extract_ios_screens.py
Normal file
139
scripts/extract_ios_screens.py
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static inventory of SwiftUI screens in iosApp/iosApp/.
|
||||
|
||||
Finds every `struct <Name>View: View { ... }` or `struct <Name>Screen: View { ... }`
|
||||
declaration across the production iOS source tree (excluding generated/build
|
||||
dirs) and categorises them by path.
|
||||
|
||||
Output schema:
|
||||
{
|
||||
"screens": [
|
||||
{"name": "LoginView", "path": "iosApp/iosApp/Login/LoginView.swift", "category": "auth"},
|
||||
...
|
||||
]
|
||||
}
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SOURCE_ROOT = REPO_ROOT / "iosApp" / "iosApp"
|
||||
OUT_PATH = REPO_ROOT / "docs" / "ios-parity" / "screens.json"
|
||||
|
||||
# Also scan widget target (HoneyDue/) for completeness.
|
||||
WIDGET_SOURCE_ROOT = REPO_ROOT / "iosApp" / "HoneyDue"
|
||||
|
||||
EXCLUDED_DIR_PARTS = {"build", "DerivedData", ".build", "Pods"}
|
||||
|
||||
STRUCT_RE = re.compile(
|
||||
r"^\s*struct\s+([A-Za-z_][A-Za-z0-9_]*(?:View|Screen))\s*:\s*View\s*\{",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
CATEGORY_RULES: list[tuple[str, str]] = [
|
||||
# path-part substring (case-insensitive) -> category
|
||||
("Login", "auth"),
|
||||
("Register", "auth"),
|
||||
("PasswordReset", "auth"),
|
||||
("VerifyEmail", "auth"),
|
||||
("Onboarding", "onboarding"),
|
||||
("Task", "task"),
|
||||
("Residence", "residence"),
|
||||
("Document", "document"),
|
||||
("Contractor", "contractor"),
|
||||
("Profile", "profile"),
|
||||
("Subscription", "subscription"),
|
||||
("Widget", "widget"), # matches WidgetIconView etc. (HoneyDue/)
|
||||
("HoneyDue", "widget"), # widget target dir
|
||||
("Shared", "shared"),
|
||||
("Core", "shared"),
|
||||
("Subviews", "shared"),
|
||||
("Dev", "dev"),
|
||||
("AnimationTesting", "dev"),
|
||||
]
|
||||
|
||||
|
||||
def category_for(rel_path: Path) -> str:
|
||||
parts_lower = [p.lower() for p in rel_path.parts]
|
||||
for needle, cat in CATEGORY_RULES:
|
||||
if needle.lower() in parts_lower:
|
||||
return cat
|
||||
# filename fallback
|
||||
stem = rel_path.stem.lower()
|
||||
for needle, cat in CATEGORY_RULES:
|
||||
if needle.lower() in stem:
|
||||
return cat
|
||||
return "shared"
|
||||
|
||||
|
||||
def should_skip(path: Path) -> bool:
|
||||
return any(part in EXCLUDED_DIR_PARTS for part in path.parts)
|
||||
|
||||
|
||||
def find_swift_files(root: Path) -> list[Path]:
|
||||
if not root.is_dir():
|
||||
return []
|
||||
out: list[Path] = []
|
||||
for p in root.rglob("*.swift"):
|
||||
if should_skip(p.relative_to(REPO_ROOT)):
|
||||
continue
|
||||
out.append(p)
|
||||
return sorted(out)
|
||||
|
||||
|
||||
def extract_from(path: Path) -> list[dict[str, Any]]:
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except (UnicodeDecodeError, OSError):
|
||||
return []
|
||||
found: list[dict[str, Any]] = []
|
||||
seen: set[str] = set()
|
||||
for m in STRUCT_RE.finditer(text):
|
||||
name = m.group(1)
|
||||
if name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
rel = path.relative_to(REPO_ROOT)
|
||||
found.append(
|
||||
{
|
||||
"name": name,
|
||||
"path": str(rel),
|
||||
"category": category_for(rel),
|
||||
}
|
||||
)
|
||||
return found
|
||||
|
||||
|
||||
def main() -> int:
|
||||
screens: list[dict[str, Any]] = []
|
||||
for swift_file in find_swift_files(SOURCE_ROOT):
|
||||
screens.extend(extract_from(swift_file))
|
||||
for swift_file in find_swift_files(WIDGET_SOURCE_ROOT):
|
||||
screens.extend(extract_from(swift_file))
|
||||
|
||||
# Sort by category then name for stable output.
|
||||
screens.sort(key=lambda s: (s["category"], s["name"], s["path"]))
|
||||
|
||||
output = {"screens": screens}
|
||||
OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with OUT_PATH.open("w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
print(f"[extract_ios_screens] wrote {OUT_PATH}")
|
||||
print(f" screens={len(screens)}")
|
||||
# category histogram
|
||||
hist: dict[str, int] = {}
|
||||
for s in screens:
|
||||
hist[s["category"]] = hist.get(s["category"], 0) + 1
|
||||
for cat, n in sorted(hist.items(), key=lambda kv: (-kv[1], kv[0])):
|
||||
print(f" {cat}: {n}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user