#!/usr/bin/env python3 """ Cross-platform structure generator for Orion project. Works on Windows, Linux, and macOS. Usage: python show_structure.py frontend python show_structure.py backend python show_structure.py tests python show_structure.py all """ import os import subprocess import sys from datetime import datetime from pathlib import Path def count_files(directory: str, pattern: str) -> int: """Count files matching pattern in directory.""" if not os.path.exists(directory): return 0 count = 0 for _root, dirs, files in os.walk(directory): # Skip __pycache__ and other cache directories dirs[:] = [ d for d in dirs if d not in ["__pycache__", ".pytest_cache", ".git", "node_modules"] ] for file in files: if pattern == "*" or file.endswith(pattern): count += 1 return count def get_tree_structure(directory: str, exclude_patterns: list[str] = None) -> str: """Generate tree structure for directory.""" if not os.path.exists(directory): return f"Directory {directory} not found" # Try to use system tree command first try: if sys.platform == "win32": # Windows tree command result = subprocess.run( ["tree", "/F", "/A", directory], capture_output=True, text=True, encoding="utf-8", errors="replace", ) return result.stdout # Linux/Mac tree command with exclusions exclude_args = [] if exclude_patterns: exclude_args = ["-I", "|".join(exclude_patterns)] result = subprocess.run( ["tree", "-F", "-a"] + exclude_args + [directory], capture_output=True, text=True, ) if result.returncode == 0: return result.stdout except (subprocess.SubprocessError, FileNotFoundError): pass # Fallback: generate tree structure manually return generate_manual_tree(directory, exclude_patterns) def generate_manual_tree( directory: str, exclude_patterns: list[str] = None, prefix: str = "" ) -> str: """Generate tree structure manually when tree command is not available.""" if exclude_patterns is None: exclude_patterns = [ "__pycache__", ".pytest_cache", ".git", "node_modules", "*.pyc", "*.pyo", ] output = [] path = Path(directory) try: items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name)) for i, item in enumerate(items): # Skip excluded patterns skip = False for pattern in exclude_patterns: if pattern.startswith("*"): # File extension pattern if item.name.endswith(pattern[1:]): skip = True break else: # Directory or exact name pattern if item.name == pattern or pattern in str(item): skip = True break if skip: continue is_last = i == len(items) - 1 current_prefix = "└── " if is_last else "├── " if item.is_dir(): output.append(f"{prefix}{current_prefix}{item.name}/") extension = " " if is_last else "│ " subtree = generate_manual_tree( str(item), exclude_patterns, prefix + extension ) if subtree: output.append(subtree) else: output.append(f"{prefix}{current_prefix}{item.name}") except PermissionError: output.append(f"{prefix}[Permission Denied]") return "\n".join(output) def generate_frontend_structure() -> str: """Generate frontend structure report.""" output = [] output.append("Frontend Folder Structure") output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") output.append("=" * 78) output.append("") # Templates section output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ JINJA2 TEMPLATES ║" ) output.append( "║ Location: app/templates ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append(get_tree_structure("app/templates")) # Static assets section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ STATIC ASSETS ║" ) output.append( "║ Location: static ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append(get_tree_structure("static")) # Documentation section (if exists) if os.path.exists("docs"): output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ DOCUMENTATION ║" ) output.append( "║ Location: docs ║" ) output.append( "║ (also listed in tools structure) ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append("Note: Documentation is also included in tools structure") output.append(" for infrastructure/DevOps context.") # Statistics section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ STATISTICS ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append("Templates:") output.append(f" - Total HTML files: {count_files('app/templates', '.html')}") output.append(f" - Total Jinja2 files: {count_files('app/templates', '.j2')}") output.append("") output.append("Static Assets:") output.append(f" - JavaScript files: {count_files('static', '.js')}") output.append(f" - CSS files: {count_files('static', '.css')}") output.append(" - Image files:") for ext in ["png", "jpg", "jpeg", "gif", "svg", "webp", "ico"]: count = count_files("static", f".{ext}") if count > 0: output.append(f" - .{ext}: {count}") if os.path.exists("docs"): output.append("") output.append("Documentation:") output.append(f" - Markdown files: {count_files('docs', '.md')}") output.append(f" - reStructuredText files: {count_files('docs', '.rst')}") output.append("") output.append("=" * 78) output.append("End of structure") return "\n".join(output) def generate_backend_structure() -> str: """Generate backend structure report.""" output = [] output.append("Backend Folder Structure") output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") output.append("=" * 78) output.append("") exclude = [ "__pycache__", "*.pyc", "*.pyo", ".pytest_cache", "*.egg-info", "templates", ] # Backend directories to include backend_dirs = [ ("app", "Application Code"), ("middleware", "Middleware Components"), ("models", "Database Models"), ("storage", "File Storage"), ("tasks", "Background Tasks"), ("logs", "Application Logs"), ] for directory, title in backend_dirs: if os.path.exists(directory): output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append(f"║ {title.upper().center(62)} ║") output.append(f"║ Location: {directory + '/'.ljust(51)} ║") output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append(get_tree_structure(directory, exclude)) # Statistics section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ STATISTICS ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append("Python Files by Directory:") total_py_files = 0 for directory, title in backend_dirs: if os.path.exists(directory): count = count_files(directory, ".py") total_py_files += count output.append(f" - {directory}/: {count} files") output.append(f" - Total Python files: {total_py_files}") output.append("") output.append("Application Components (if in app/):") components = ["routes", "services", "schemas", "exceptions", "utils"] for component in components: component_path = f"app/{component}" if os.path.exists(component_path): count = count_files(component_path, ".py") output.append(f" - app/{component}: {count} files") output.append("") output.append("=" * 78) output.append("End of structure") return "\n".join(output) def generate_tools_structure() -> str: """Generate tools/infrastructure structure report.""" output = [] output.append("Tools & Infrastructure Structure") output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") output.append("=" * 78) output.append("") exclude = ["__pycache__", "*.pyc", "*.pyo", ".pytest_cache", "*.egg-info"] # Tools directories to include tools_dirs = [ ("alembic", "Database Migrations"), ("scripts", "Utility Scripts"), ("docker", "Docker Configuration"), ("docs", "Documentation"), ] for directory, title in tools_dirs: if os.path.exists(directory): output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append(f"║ {title.upper().center(62)} ║") output.append(f"║ Location: {directory + '/'.ljust(51)} ║") output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append(get_tree_structure(directory, exclude)) # Configuration files section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ CONFIGURATION FILES ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append("Root configuration files:") config_files = [ ("Makefile", "Build automation"), ("requirements.txt", "Python dependencies"), ("pyproject.toml", "Python project config"), ("setup.py", "Python setup script"), ("setup.cfg", "Setup configuration"), ("alembic.ini", "Alembic migrations config"), ("mkdocs.yml", "MkDocs documentation config"), ("Dockerfile", "Docker image definition"), ("docker-compose.yml", "Docker services"), (".dockerignore", "Docker ignore patterns"), (".gitignore", "Git ignore patterns"), (".env.example", "Environment variables template"), ] for file, description in config_files: if os.path.exists(file): output.append(f" ✓ {file.ljust(25)} - {description}") # Statistics section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ STATISTICS ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append("Database Migrations:") if os.path.exists("alembic/versions"): migration_count = count_files("alembic/versions", ".py") output.append(f" - Total migrations: {migration_count}") if migration_count > 0: # Get first and last migration try: migrations = sorted( [f for f in os.listdir("alembic/versions") if f.endswith(".py")] ) if migrations: output.append(f" - First: {migrations[0][:40]}...") if len(migrations) > 1: output.append(f" - Latest: {migrations[-1][:40]}...") except Exception: pass else: output.append(" - No alembic/versions directory found") output.append("") output.append("Scripts:") if os.path.exists("scripts"): script_types = { ".py": "Python scripts", ".sh": "Shell scripts", ".bat": "Batch scripts", } for ext, desc in script_types.items(): count = count_files("scripts", ext) if count > 0: output.append(f" - {desc}: {count}") else: output.append(" - No scripts directory found") output.append("") output.append("Documentation:") if os.path.exists("docs"): doc_types = { ".md": "Markdown files", ".rst": "reStructuredText files", } for ext, desc in doc_types.items(): count = count_files("docs", ext) if count > 0: output.append(f" - {desc}: {count}") else: output.append(" - No docs directory found") output.append("") output.append("Docker:") docker_files = ["Dockerfile", "docker-compose.yml", ".dockerignore"] docker_exists = any(os.path.exists(f) for f in docker_files) if docker_exists: output.append(" ✓ Docker configuration present") if os.path.exists("docker"): output.append(f" - Docker directory files: {count_files('docker', '*')}") else: output.append(" - No Docker configuration found") output.append("") output.append("=" * 78) output.append("End of structure") return "\n".join(output) def generate_test_structure() -> str: """Generate test structure report.""" output = [] output.append("Test Folder Structure") output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") output.append("=" * 78) output.append("") # Test files section output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ TEST FILES ║" ) output.append( "║ Location: tests/ ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") exclude = ["__pycache__", "*.pyc", "*.pyo", ".pytest_cache", "*.egg-info"] output.append(get_tree_structure("tests", exclude)) # Configuration section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ TEST CONFIGURATION ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") output.append("Test configuration files:") test_config_files = [ "pytest.ini", "conftest.py", "tests/conftest.py", ".coveragerc", ] for file in test_config_files: if os.path.exists(file): output.append(f" ✓ {file}") # Statistics section output.append("") output.append("") output.append( "╔══════════════════════════════════════════════════════════════════╗" ) output.append( "║ STATISTICS ║" ) output.append( "╚══════════════════════════════════════════════════════════════════╝" ) output.append("") # Count test files test_file_count = 0 if os.path.exists("tests"): for root, dirs, files in os.walk("tests"): dirs[:] = [d for d in dirs if d != "__pycache__"] for file in files: if file.startswith("test_") and file.endswith(".py"): test_file_count += 1 output.append("Test Files:") output.append(f" - Total test files: {test_file_count}") output.append("") output.append("By Category:") categories = ["unit", "integration", "system", "e2e", "performance"] for category in categories: category_path = f"tests/{category}" if os.path.exists(category_path): count = 0 for root, dirs, files in os.walk(category_path): dirs[:] = [d for d in dirs if d != "__pycache__"] for file in files: if file.startswith("test_") and file.endswith(".py"): count += 1 output.append(f" - tests/{category}: {count} files") # Count test functions test_function_count = 0 if os.path.exists("tests"): for root, dirs, files in os.walk("tests"): dirs[:] = [d for d in dirs if d != "__pycache__"] for file in files: if file.startswith("test_") and file.endswith(".py"): filepath = os.path.join(root, file) try: with open(filepath, encoding="utf-8") as f: for line in f: if line.strip().startswith("def test_"): test_function_count += 1 except Exception: pass output.append("") output.append("Test Functions:") output.append(f" - Total test functions: {test_function_count}") output.append("") output.append("Coverage Files:") if os.path.exists(".coverage"): output.append(" ✓ .coverage (coverage data file exists)") if os.path.exists("htmlcov"): output.append(" ✓ htmlcov/ (HTML coverage report exists)") output.append("") output.append("=" * 78) output.append("End of structure") return "\n".join(output) def main(): """Main entry point.""" if len(sys.argv) < 2: print("Usage: python show_structure.py [frontend|backend|tests|tools|all]") sys.exit(1) structure_type = sys.argv[1].lower() generators = { "frontend": ("frontend-structure.txt", generate_frontend_structure), "backend": ("backend-structure.txt", generate_backend_structure), "tests": ("test-structure.txt", generate_test_structure), "tools": ("tools-structure.txt", generate_tools_structure), } if structure_type == "all": for name, (filename, generator) in generators.items(): print(f"\n{'=' * 60}") print(f"Generating {name} structure...") print("=" * 60) content = generator() with open(filename, "w", encoding="utf-8") as f: f.write(content) print(f"✅ {name.capitalize()} structure saved to {filename}") print(f"\n{content}\n") elif structure_type in generators: filename, generator = generators[structure_type] print(f"Generating {structure_type} structure...") content = generator() with open(filename, "w", encoding="utf-8") as f: f.write(content) print(f"\n✅ Structure saved to {filename}\n") print(content) else: print(f"Error: Unknown structure type '{structure_type}'") print("Valid options: frontend, backend, tests, tools, all") sys.exit(1) if __name__ == "__main__": main()