#!/usr/bin/env python3 # ============================================================================= # generate_structure.py # Generates PROJECT_STRUCTURE.md with a visual repo tree. # Place in: .codey/scripts/generate_structure.py # ============================================================================= # PYTHON VERSION NOTE: # Embedding \n inside f-string expressions like f"{'\n'.join(x)}" is only # supported from Python 3.12+. On 3.10/3.11 (GitHub Actions default) this # causes a SyntaxError: "unterminated string literal". # FIX: always build the joined string BEFORE the f-string block. # Example: # BAD → f"```\n{'\n'.join(tree)}\n```" # crashes on 3.10/3.11 # GOOD → tree_str = "\n".join(tree) # then use tree_str inside f-string or concatenation # ============================================================================= from pathlib import Path from datetime import datetime # ───────────────────────────────────────────── # IGNORE LIST — folders/files to skip # ───────────────────────────────────────────── IGNORE = { '.git', '__pycache__', '.pytest_cache', '*.pyc', '.DS_Store', 'node_modules', '.venv', 'venv', } def is_ignored(path: Path) -> bool: name = path.name for pattern in IGNORE: if '*' in pattern: if path.match(pattern): return True elif name == pattern: return True return False def generate_tree(root: Path, prefix: str = "", is_last: bool = True) -> list: lines = [] connector = "└── " if is_last else "├── " if root == Path("."): lines.append(root.resolve().name + "/") children = sorted(root.iterdir(), key=lambda p: (p.is_file(), p.name.lower())) visible = [c for c in children if not is_ignored(c)] for i, child in enumerate(visible): lines.extend(generate_tree(child, prefix="", is_last=(i == len(visible) - 1))) return lines if root.is_dir(): lines.append(f"{prefix}{connector}{root.name}/") extension = " " if is_last else "│ " children = sorted(root.iterdir(), key=lambda p: (p.is_file(), p.name.lower())) visible = [c for c in children if not is_ignored(c)] for i, child in enumerate(visible): lines.extend(generate_tree(child, prefix + extension, is_last=(i == len(visible) - 1))) else: lines.append(f"{prefix}{connector}{root.name}") return lines def main(): root = Path(".") tree = generate_tree(root) now = datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC") # Build tree string BEFORE f-string — required for Python < 3.12 tree_str = "\n".join(tree) output = "# 📁 Project Structure\n\n" output += f"> Auto-generated by `generate_structure.py` — {now}\n" output += "> Do not edit manually.\n\n" output += "```\n" output += tree_str + "\n" output += "```\n" out_path = Path("PROJECT_STRUCTURE.md") out_path.write_text(output, encoding="utf-8") print(f"✓ PROJECT_STRUCTURE.md written ({len(tree)} lines)") if __name__ == "__main__": main()