- 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>
634 lines
24 KiB
Python
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()
|