Files
orion/scripts/show_structure.py
Samir Boulahtit f20266167d
Some checks failed
CI / ruff (push) Failing after 7s
CI / pytest (push) Failing after 1s
CI / architecture (push) Failing after 9s
CI / dependency-scanning (push) Successful in 27s
CI / audit (push) Successful in 8s
CI / docs (push) Has been skipped
fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.)
- Added ignore rules for patterns intentional in this codebase:
  E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from),
  SIM108/SIM105/SIM117 (readability preferences)
- Added per-file ignores for tests and scripts
- Excluded broken scripts/rename_terminology.py (has curly quotes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:10:42 +01:00

634 lines
24 KiB
Python

#!/usr/bin/env python3
"""
Cross-platform structure generator for Wizamart 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()