#!/usr/bin/env python3
"""
HTML Documentation Generator
Generates beautiful HTML documentation from project analysis.
"""
import os
import sys
import json
import re
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional
# Try to import yaml
try:
import yaml
except ImportError:
yaml = None
def load_template(template_path: Path) -> str:
"""Load the HTML template."""
with open(template_path, 'r', encoding='utf-8') as f:
return f.read()
def load_analysis(analysis_path: Path) -> Dict[str, Any]:
"""Load project analysis from YAML or JSON."""
with open(analysis_path, 'r', encoding='utf-8') as f:
content = f.read()
if yaml and (analysis_path.suffix in ['.yml', '.yaml']):
return yaml.safe_load(content)
return json.loads(content)
def escape_html(text: str) -> str:
"""Escape HTML special characters."""
if not text:
return ''
return (str(text)
.replace('&', '&')
.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace("'", '''))
def generate_capabilities_html(capabilities: List[Dict]) -> str:
"""Generate HTML for capabilities cards."""
icons = ['β¨', 'β‘', 'π', 'π', 'π', 'π‘', 'π―', 'π§']
html_parts = []
for i, cap in enumerate(capabilities[:8]):
icon = icons[i % len(icons)]
html_parts.append(f'''
{escape_html(cap.get('description', ''))}
''')
return '\n'.join(html_parts)
def generate_prerequisites_html(prerequisites: List[Dict]) -> str:
"""Generate HTML for prerequisites table rows."""
html_parts = []
for prereq in prerequisites:
tool = prereq.get('tool', prereq.get('name', ''))
purpose = prereq.get('purpose', prereq.get('description', ''))
html_parts.append(f'''
{escape_html(tool)} |
{escape_html(purpose)} |
''')
return '\n'.join(html_parts) if html_parts else '''
Node.js |
JavaScript runtime environment |
'''
def generate_tech_stack_html(tech_stack: Dict) -> str:
"""Generate HTML for technology stack table rows."""
html_parts = []
stack_items = [
('Language', tech_stack.get('language')),
('Framework', tech_stack.get('framework')),
('Database', tech_stack.get('database')),
('UI Framework', tech_stack.get('ui_framework')),
]
purposes = {
'TypeScript': 'Type-safe JavaScript for better code quality',
'JavaScript': 'Programming language for web applications',
'Python': 'General-purpose programming language',
'Next.js': 'Full-stack React framework with SSR',
'React': 'Component-based UI library',
'Vue.js': 'Progressive JavaScript framework',
'Express': 'Minimal web server framework',
'Prisma': 'Type-safe database ORM',
'MongoDB': 'NoSQL document database',
'PostgreSQL': 'Relational database',
'Tailwind CSS': 'Utility-first CSS framework',
'Material UI': 'React component library',
}
for layer, tech in stack_items:
if tech:
purpose = purposes.get(tech, f'{tech} for {layer.lower()}')
html_parts.append(f'''
| {escape_html(layer)} |
{escape_html(tech)} |
{escape_html(purpose)} |
''')
# Add key dependencies
for dep in tech_stack.get('key_dependencies', [])[:5]:
html_parts.append(f'''
| Dependency |
{escape_html(dep.get('name', ''))} |
{escape_html(dep.get('purpose', ''))} |
''')
return '\n'.join(html_parts)
def generate_directory_structure(structure: Dict) -> str:
"""Generate directory structure text."""
lines = ['project/']
for i, dir_info in enumerate(structure.get('directories', [])[:10]):
prefix = 'βββ ' if i == len(structure.get('directories', [])) - 1 else 'βββ '
path = dir_info.get('path', '')
purpose = dir_info.get('purpose', '')
lines.append(f"{prefix}{path}/ # {purpose}")
return '\n'.join(lines)
def generate_features_html(features: List[Dict]) -> str:
"""Generate HTML for features section."""
icons = ['π', 'π€', 'π', 'πΎ', 'π', 'π', 'π§', 'βοΈ']
html_parts = []
for i, feature in enumerate(features[:8]):
icon = icons[i % len(icons)]
name = feature.get('name', 'Feature')
description = feature.get('description', '')
technical_notes = feature.get('technical_notes', '')
files = feature.get('files', [])
files_html = '\n'.join([f'{escape_html(f)}' for f in files[:3]])
html_parts.append(f'''
{icon}
{escape_html(name)}
{escape_html(description)}
π§ Technical Details
For Engineers
{escape_html(technical_notes)}
Key Files:
''')
return '\n'.join(html_parts) if html_parts else '''
β¨
Core Functionality
Main features of the application.
'''
def generate_api_endpoints_html(endpoints: List[Dict]) -> str:
"""Generate HTML for API endpoints."""
if not endpoints:
return '''
No API endpoints detected. This project may use a different API pattern or may not have an API layer.
'''
html_parts = ['', '', '', '| Method | ', 'Endpoint | ', 'Description | ', '
', '', '']
for endpoint in endpoints[:15]:
method = endpoint.get('method', 'GET')
method_class = f'method-{method.lower()}'
path = endpoint.get('path', '')
description = endpoint.get('description', '')
html_parts.append(f'''
| {escape_html(method)} |
{escape_html(path)} |
{escape_html(description)} |
''')
html_parts.extend(['', '
'])
return '\n'.join(html_parts)
def generate_components_html(components: List[Dict]) -> str:
"""Generate HTML for component catalog."""
if not components:
return '''
No UI components detected. This project may not have a frontend layer or uses a different component pattern.
'''
html_parts = []
for comp in components[:10]:
name = comp.get('name', 'Component')
description = comp.get('description', f'{name} component')
path = comp.get('path', '')
props = comp.get('props', 'See source file')
html_parts.append(f'''
{escape_html(description)}
{escape_html(path)}
π§ Props & Usage
Technical
Props: {escape_html(props)}
Usage Example
<{escape_html(name)} />
''')
return '\n'.join(html_parts)
def generate_data_models_html(models: List[Dict]) -> str:
"""Generate HTML for data models."""
if not models:
return '''
No data models detected. This project may not use a database or uses a different data pattern.
'''
html_parts = []
for model in models[:10]:
name = model.get('name', 'Model')
description = model.get('description', f'{name} data model')
fields = model.get('fields', [])
fields_html = ''
if fields:
fields_html = '| Field | Type | Description |
'
for field in fields[:10]:
field_name = field.get('name', '')
field_type = field.get('type', 'unknown')
field_desc = field.get('description', '')
fields_html += f'''
{escape_html(field_name)} |
{escape_html(field_type)} |
{escape_html(field_desc)} |
'''
fields_html += '
'
html_parts.append(f'''
{escape_html(name)}
What it represents: {escape_html(description)}
{fields_html}''')
return '\n'.join(html_parts)
def generate_er_diagram(models: List[Dict]) -> str:
"""Generate ASCII ER diagram."""
if not models:
return '''βββββββββββββββββββββββββββββββββββββββ
β No data models detected β
βββββββββββββββββββββββββββββββββββββββ'''
lines = []
for model in models[:4]:
name = model.get('name', 'Model')
fields = model.get('fields', [])[:4]
width = max(len(name) + 4, max([len(f.get('name', '')) + len(f.get('type', '')) + 5 for f in fields] or [20]))
lines.append('β' + 'β' * width + 'β')
lines.append('β' + f' {name} '.center(width) + 'β')
lines.append('β' + 'β' * width + 'β€')
for field in fields:
field_str = f" {field.get('name', '')} : {field.get('type', '')}"
lines.append('β' + field_str.ljust(width) + 'β')
lines.append('β' + 'β' * width + 'β')
lines.append('')
return '\n'.join(lines)
def generate_glossary_html(terms: List[Dict]) -> str:
"""Generate HTML for glossary."""
html_parts = []
for term in terms:
word = term.get('term', '')
definition = term.get('definition', '')
html_parts.append(f'''
{escape_html(word)}
{escape_html(definition)}
''')
return '\n'.join(html_parts)
def generate_system_diagram(tech_stack: Dict, structure: Dict) -> str:
"""Generate ASCII system architecture diagram."""
framework = tech_stack.get('framework', 'Application')
database = tech_stack.get('database', '')
ui = tech_stack.get('ui_framework', 'UI')
diagram = f'''βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Application Architecture β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Client βββββΆβ API βββββΆβ Database β β
β β ({ui or 'UI'}) β β ({framework or 'Server'}) β β ({database or 'Storage'}) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ'''
return diagram
def generate_html(analysis: Dict, template: str) -> str:
"""Generate final HTML from analysis and template."""
project = analysis.get('project', {})
tech_stack = analysis.get('tech_stack', {})
structure = analysis.get('structure', {})
features = analysis.get('features', [])
components = analysis.get('components', [])
endpoints = analysis.get('api_endpoints', [])
models = analysis.get('data_models', [])
glossary = analysis.get('glossary_terms', [])
# Basic replacements
replacements = {
'{{PROJECT_NAME}}': escape_html(project.get('name', 'Project')),
'{{VERSION}}': escape_html(project.get('version', '1.0.0')),
'{{TAGLINE}}': escape_html(project.get('description', 'Project documentation')),
'{{DESCRIPTION}}': escape_html(project.get('description', 'This project provides various features and capabilities.')),
'{{AUDIENCE}}': 'Developers, stakeholders, and anyone interested in understanding this project.',
'{{GENERATED_DATE}}': datetime.now().strftime('%Y-%m-%d'),
}
# Generate complex sections
html = template
# Replace simple placeholders
for key, value in replacements.items():
html = html.replace(key, value)
# Replace capabilities section
capabilities = [{'capability': cap.get('name'), 'description': cap.get('description')}
for cap in features[:4]] if features else [
{'capability': 'Core Features', 'description': 'Main application functionality'},
{'capability': 'Easy Integration', 'description': 'Simple setup and configuration'}
]
# Find and replace the capabilities placeholder section
cap_html = generate_capabilities_html(capabilities)
html = re.sub(
r'.*?\s*',
f'\n{cap_html}',
html,
flags=re.DOTALL
)
# Replace technology stack
html = re.sub(
r'.*?',
generate_tech_stack_html(tech_stack),
html,
flags=re.DOTALL
)
# Generate and replace diagrams
html = html.replace('{{SYSTEM_DIAGRAM}}', generate_system_diagram(tech_stack, structure))
html = html.replace('{{DIRECTORY_STRUCTURE}}', generate_directory_structure(structure))
html = html.replace('{{ER_DIAGRAM}}', generate_er_diagram(models))
# Replace features section
html = re.sub(
r'.*?\s*\s*',
f'\n{generate_features_html(features)}',
html,
flags=re.DOTALL
)
# Replace API endpoints
html = re.sub(
r'.*?',
f'API Endpoints
\n{generate_api_endpoints_html(endpoints)}',
html,
flags=re.DOTALL
)
# Replace components
html = re.sub(
r'.*?\s*',
f'\n{generate_components_html(components)}',
html,
flags=re.DOTALL
)
# Replace data models
html = re.sub(
r'.*?',
f'\n{generate_data_models_html(models)}',
html,
flags=re.DOTALL
)
# Replace glossary
html = re.sub(
r'.*?',
f'\n{generate_glossary_html(glossary)}\n',
html,
flags=re.DOTALL
)
# Clean up remaining placeholders
html = re.sub(r'\{\{[A-Z_]+\}\}', '', html)
return html
def main():
"""Main entry point."""
if len(sys.argv) < 3:
print("Usage: generate_html.py [output.html]")
sys.exit(1)
analysis_path = Path(sys.argv[1])
template_path = Path(sys.argv[2])
output_path = Path(sys.argv[3]) if len(sys.argv) > 3 else Path('documentation.html')
if not analysis_path.exists():
print(f"Error: Analysis file not found: {analysis_path}", file=sys.stderr)
sys.exit(1)
if not template_path.exists():
print(f"Error: Template file not found: {template_path}", file=sys.stderr)
sys.exit(1)
analysis = load_analysis(analysis_path)
template = load_template(template_path)
html = generate_html(analysis, template)
output_path.write_text(html, encoding='utf-8')
print(f"HTML documentation generated: {output_path}")
if __name__ == '__main__':
main()