#!/usr/bin/env python3 from __future__ import annotations import argparse, re from pathlib import Path def valid_identifier(value: str) -> str: if not re.fullmatch(r'[A-Za-z][A-Za-z0-9]*', value): raise argparse.ArgumentTypeError('Use PascalCase alphanumeric identifiers only.') return value def render_tree(source: Path, target: Path, tokens: dict[str, str], dry_run: bool) -> list[Path]: generated: list[Path] = [] for item in source.rglob('*'): if item.is_dir(): continue relative = Path(str(item.relative_to(source)).replace('__MODULE__', tokens['MODULE']).replace('__FEATURE__', tokens['FEATURE'])) output = target / relative content = item.read_text(encoding='utf-8') for key, value in tokens.items(): content = content.replace(f'__{key}__', value) if '__' in content: raise RuntimeError(f'Unresolved template token in {item}') generated.append(output) if not dry_run: output.parent.mkdir(parents=True, exist_ok=True) if output.exists(): raise FileExistsError(f'Refusing to overwrite {output}') output.write_text(content, encoding='utf-8') return generated def main() -> int: parser=argparse.ArgumentParser(description='Generate a K-ArtSell Vertical Slice backend/frontend packet.') parser.add_argument('--module', required=True, type=valid_identifier) parser.add_argument('--feature', required=True, type=valid_identifier) parser.add_argument('--slice-id', required=True, help='e.g. VS-14') parser.add_argument('--root', type=Path, default=Path.cwd()) parser.add_argument('--dry-run', action='store_true') args=parser.parse_args() template_root=Path(__file__).resolve().parents[1] / 'templates' / 'vertical-slice' tokens={'MODULE':args.module,'FEATURE':args.feature,'SLICE_ID':args.slice_id} targets=[(template_root/'backend',args.root/'src'/f'KArtSell.Modules.{args.module}'/'Features'/args.feature),(template_root/'frontend',args.root/'frontend'/'src'/'features'/re.sub(r'(?