from __future__ import annotations import re import shutil from pathlib import Path from editor_app import config DEFAULT_MAIN_PY = 'print("Hello, World!")\n' # Self-contained demos copied from shipped `workspace/code/` (stdlib + machine/neopixel/time only). _CANONICAL_DEMO_FILENAMES = ( "pattern_rainbow_demo.py", "pattern_twinkle_demo.py", "pattern_chase_demo.py", ) def ensure_workspace_lib(workspace_root: Path | None = None) -> None: """Copy shipped MicroPython stubs from the repo into WORKSPACE_ROOT/lib when each file is absent.""" dst_root = (workspace_root or config.WORKSPACE_ROOT).resolve() / "lib" dst_root.mkdir(parents=True, exist_ok=True) src_root = config.PROJECT_ROOT.resolve() / "lib" if not src_root.is_dir(): return for src in sorted(src_root.glob("*.py")): if not src.is_file(): continue dst = dst_root / src.name if dst.exists(): continue shutil.copy2(src, dst) def safe_workspace_leaf(username: str, user_id: int) -> str: base = re.sub(r"[^a-zA-Z0-9._-]+", "-", username.strip()).strip("-").lower() or "user" return f"{base}-{user_id}" def user_workspace_root(user_id: int, username: str, workspace_root: Path | None = None) -> Path: root = (workspace_root or config.WORKSPACE_ROOT).resolve() return root / "users" / safe_workspace_leaf(username, user_id) def _seed_canonical_demos_into_code(code_dir: Path) -> None: src_root = config.PROJECT_ROOT.resolve() / "workspace" / "code" for filename in _CANONICAL_DEMO_FILENAMES: dst = code_dir / filename if dst.exists(): continue src = src_root / filename if src.is_file(): dst.write_text(src.read_text(encoding="utf-8"), encoding="utf-8") def ensure_default_code_main(user_root: Path) -> None: """Ensure code/ has main.py and self-contained NeoPixel demos (copied from repo workspace/code/).""" code_dir = user_root / "code" code_dir.mkdir(parents=True, exist_ok=True) main_py = code_dir / "main.py" if not main_py.exists(): main_py.write_text(DEFAULT_MAIN_PY, encoding="utf-8") _seed_canonical_demos_into_code(code_dir) def rename_user_workspace_leaf( user_id: int, old_username: str, new_username: str, workspace_root: Path | None = None ) -> None: """Rename per-user workspace directory when login name changes.""" root = (workspace_root or config.WORKSPACE_ROOT).resolve() users_dir = root / "users" src = users_dir / safe_workspace_leaf(old_username, user_id) dst = users_dir / safe_workspace_leaf(new_username, user_id) if src.resolve() == dst.resolve(): return dst.parent.mkdir(parents=True, exist_ok=True) if dst.exists(): raise ValueError("Workspace folder for new username already exists; pick another username.") if src.exists(): shutil.move(str(src), str(dst)) else: ensure_default_code_main(dst)