project-standalo-sonic-cloud/skills/documentation-generator/scripts/generate_html.py

492 lines
17 KiB
Python

#!/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('<', '&lt;')
.replace('>', '&gt;')
.replace('"', '&quot;')
.replace("'", '&#39;'))
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'''
<div class="card">
<div class="card-header">
<div class="card-icon">{icon}</div>
<div class="card-title">{escape_html(cap.get('capability', cap.get('name', 'Feature')))}</div>
</div>
<p>{escape_html(cap.get('description', ''))}</p>
</div>''')
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'''
<tr>
<td><code>{escape_html(tool)}</code></td>
<td>{escape_html(purpose)}</td>
</tr>''')
return '\n'.join(html_parts) if html_parts else '''
<tr>
<td><code>Node.js</code></td>
<td>JavaScript runtime environment</td>
</tr>'''
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'''
<tr>
<td>{escape_html(layer)}</td>
<td><span class="badge badge-primary">{escape_html(tech)}</span></td>
<td>{escape_html(purpose)}</td>
</tr>''')
# Add key dependencies
for dep in tech_stack.get('key_dependencies', [])[:5]:
html_parts.append(f'''
<tr>
<td>Dependency</td>
<td><span class="badge badge-info">{escape_html(dep.get('name', ''))}</span></td>
<td>{escape_html(dep.get('purpose', ''))}</td>
</tr>''')
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'<li><code>{escape_html(f)}</code></li>' for f in files[:3]])
html_parts.append(f'''
<div class="feature-item">
<div class="feature-icon">{icon}</div>
<div class="feature-content">
<h4>{escape_html(name)}</h4>
<p>{escape_html(description)}</p>
<details>
<summary>
🔧 Technical Details
<span class="tech-badge">For Engineers</span>
</summary>
<div>
<p>{escape_html(technical_notes)}</p>
<p><strong>Key Files:</strong></p>
<ul>
{files_html}
</ul>
</div>
</details>
</div>
</div>''')
return '\n'.join(html_parts) if html_parts else '''
<div class="feature-item">
<div class="feature-icon">✨</div>
<div class="feature-content">
<h4>Core Functionality</h4>
<p>Main features of the application.</p>
</div>
</div>'''
def generate_api_endpoints_html(endpoints: List[Dict]) -> str:
"""Generate HTML for API endpoints."""
if not endpoints:
return '''
<p>No API endpoints detected. This project may use a different API pattern or may not have an API layer.</p>'''
html_parts = ['<table>', '<thead>', '<tr>', '<th>Method</th>', '<th>Endpoint</th>', '<th>Description</th>', '</tr>', '</thead>', '<tbody>']
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'''
<tr>
<td><span class="method {method_class}">{escape_html(method)}</span></td>
<td><code>{escape_html(path)}</code></td>
<td>{escape_html(description)}</td>
</tr>''')
html_parts.extend(['</tbody>', '</table>'])
return '\n'.join(html_parts)
def generate_components_html(components: List[Dict]) -> str:
"""Generate HTML for component catalog."""
if not components:
return '''
<p>No UI components detected. This project may not have a frontend layer or uses a different component pattern.</p>'''
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'''
<div class="card">
<div class="card-header">
<div class="card-icon">🧩</div>
<div class="card-title">{escape_html(name)}</div>
</div>
<p>{escape_html(description)}</p>
<p><code>{escape_html(path)}</code></p>
<details>
<summary>
🔧 Props & Usage
<span class="tech-badge">Technical</span>
</summary>
<div>
<p><strong>Props:</strong> {escape_html(props)}</p>
<h4>Usage Example</h4>
<pre><code>&lt;{escape_html(name)} /&gt;</code></pre>
</div>
</details>
</div>''')
return '\n'.join(html_parts)
def generate_data_models_html(models: List[Dict]) -> str:
"""Generate HTML for data models."""
if not models:
return '''
<p>No data models detected. This project may not use a database or uses a different data pattern.</p>'''
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 = '<table><thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody>'
for field in fields[:10]:
field_name = field.get('name', '')
field_type = field.get('type', 'unknown')
field_desc = field.get('description', '')
fields_html += f'''
<tr>
<td><code>{escape_html(field_name)}</code></td>
<td><code>{escape_html(field_type)}</code></td>
<td>{escape_html(field_desc)}</td>
</tr>'''
fields_html += '</tbody></table>'
html_parts.append(f'''
<h3>{escape_html(name)}</h3>
<p><strong>What it represents:</strong> {escape_html(description)}</p>
{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'''
<div class="glossary-term">
<span class="glossary-word">{escape_html(word)}</span>
<span class="glossary-definition">{escape_html(definition)}</span>
</div>''')
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'<!-- CAPABILITIES_PLACEHOLDER -->.*?</div>\s*</div>',
f'<!-- Generated Capabilities -->\n{cap_html}',
html,
flags=re.DOTALL
)
# Replace technology stack
html = re.sub(
r'<!-- TECH_STACK_PLACEHOLDER -->.*?</tr>',
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'<!-- FEATURES_PLACEHOLDER -->.*?</div>\s*</div>\s*</div>',
f'<!-- Generated Features -->\n{generate_features_html(features)}',
html,
flags=re.DOTALL
)
# Replace API endpoints
html = re.sub(
r'<!-- API_ENDPOINTS_PLACEHOLDER -->.*?</details>',
f'<h3>API Endpoints</h3>\n{generate_api_endpoints_html(endpoints)}',
html,
flags=re.DOTALL
)
# Replace components
html = re.sub(
r'<!-- COMPONENTS_PLACEHOLDER -->.*?</div>\s*</div>',
f'<!-- Generated Components -->\n{generate_components_html(components)}',
html,
flags=re.DOTALL
)
# Replace data models
html = re.sub(
r'<!-- DATA_MODELS_PLACEHOLDER -->.*?</table>',
f'<!-- Generated Data Models -->\n{generate_data_models_html(models)}',
html,
flags=re.DOTALL
)
# Replace glossary
html = re.sub(
r'<!-- GLOSSARY_PLACEHOLDER -->.*?</div>',
f'<!-- Generated Glossary -->\n{generate_glossary_html(glossary)}\n</div>',
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 <analysis.yml> <template.html> [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()