398 lines
10 KiB
Python
Executable File
398 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Style Master - Style Guide Generator
|
|
|
|
Generates a living style guide based on codebase analysis.
|
|
|
|
Usage:
|
|
python generate_styleguide.py [--path /path/to/project]
|
|
python generate_styleguide.py --output STYLEGUIDE.md
|
|
python generate_styleguide.py --format markdown|json
|
|
|
|
Examples:
|
|
python generate_styleguide.py
|
|
python generate_styleguide.py --output docs/STYLEGUIDE.md
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
import re
|
|
from collections import Counter
|
|
|
|
|
|
def load_template():
|
|
"""Load style guide template."""
|
|
template_path = Path(__file__).parent.parent / 'References' / 'style-guide-template.md'
|
|
|
|
if template_path.exists():
|
|
with open(template_path, 'r') as f:
|
|
return f.read()
|
|
|
|
# Fallback basic template
|
|
return """# {project_name} Style Guide
|
|
|
|
*Generated: {date}*
|
|
|
|
## Design Tokens
|
|
|
|
### Colors
|
|
|
|
{colors}
|
|
|
|
### Typography
|
|
|
|
{typography}
|
|
|
|
### Spacing
|
|
|
|
{spacing}
|
|
|
|
## Components
|
|
|
|
{components}
|
|
|
|
## Guidelines
|
|
|
|
{guidelines}
|
|
|
|
---
|
|
|
|
*This is a living document. Update as the design system evolves.*
|
|
"""
|
|
|
|
|
|
def detect_project_name(root_path):
|
|
"""Detect project name from package.json."""
|
|
package_json = root_path / 'package.json'
|
|
|
|
if package_json.exists():
|
|
try:
|
|
with open(package_json, 'r') as f:
|
|
data = json.load(f)
|
|
return data.get('name', root_path.name)
|
|
except:
|
|
pass
|
|
|
|
return root_path.name
|
|
|
|
|
|
def extract_design_tokens_from_tailwind(root_path):
|
|
"""Extract design tokens from Tailwind config."""
|
|
config_files = ['tailwind.config.js', 'tailwind.config.ts']
|
|
|
|
for config_file in config_files:
|
|
config_path = root_path / config_file
|
|
if config_path.exists():
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
content = f.read()
|
|
|
|
return {
|
|
'source': 'Tailwind Config',
|
|
'colors': 'See tailwind.config.js theme.extend.colors',
|
|
'spacing': 'Using Tailwind default spacing scale',
|
|
'typography': 'See tailwind.config.js theme.extend.fontFamily'
|
|
}
|
|
except:
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def extract_css_variables(root_path):
|
|
"""Extract CSS custom properties from CSS files."""
|
|
css_vars = {}
|
|
|
|
for css_file in root_path.rglob('*.css'):
|
|
if 'node_modules' in str(css_file):
|
|
continue
|
|
|
|
try:
|
|
with open(css_file, 'r') as f:
|
|
content = f.read()
|
|
|
|
# Find CSS custom properties
|
|
pattern = r'--([\w-]+):\s*([^;]+)'
|
|
matches = re.findall(pattern, content)
|
|
|
|
for var_name, var_value in matches:
|
|
css_vars[var_name] = var_value.strip()
|
|
except:
|
|
continue
|
|
|
|
return css_vars
|
|
|
|
|
|
def categorize_css_variables(css_vars):
|
|
"""Categorize CSS variables by type."""
|
|
categorized = {
|
|
'colors': {},
|
|
'spacing': {},
|
|
'typography': {},
|
|
'other': {}
|
|
}
|
|
|
|
color_keywords = ['color', 'bg', 'background', 'border', 'text', 'primary', 'secondary', 'accent']
|
|
spacing_keywords = ['spacing', 'margin', 'padding', 'gap', 'size']
|
|
typo_keywords = ['font', 'text', 'heading', 'body', 'line-height', 'letter-spacing']
|
|
|
|
for var_name, var_value in css_vars.items():
|
|
var_lower = var_name.lower()
|
|
|
|
if any(keyword in var_lower for keyword in color_keywords):
|
|
categorized['colors'][var_name] = var_value
|
|
elif any(keyword in var_lower for keyword in spacing_keywords):
|
|
categorized['spacing'][var_name] = var_value
|
|
elif any(keyword in var_lower for keyword in typo_keywords):
|
|
categorized['typography'][var_name] = var_value
|
|
else:
|
|
categorized['other'][var_name] = var_value
|
|
|
|
return categorized
|
|
|
|
|
|
def format_tokens_section(tokens_dict, title):
|
|
"""Format design tokens into markdown."""
|
|
if not tokens_dict:
|
|
return f"*No {title.lower()} tokens defined yet.*\n"
|
|
|
|
output = ""
|
|
for var_name, var_value in sorted(tokens_dict.items())[:20]: # Limit to 20
|
|
output += f"- `--{var_name}`: {var_value}\n"
|
|
|
|
if len(tokens_dict) > 20:
|
|
output += f"\n*...and {len(tokens_dict) - 20} more*\n"
|
|
|
|
return output
|
|
|
|
|
|
def generate_guidelines(root_path):
|
|
"""Generate guidelines based on detected patterns."""
|
|
guidelines = []
|
|
|
|
# Check for Tailwind
|
|
if (root_path / 'tailwind.config.js').exists() or (root_path / 'tailwind.config.ts').exists():
|
|
guidelines.append("""
|
|
### Tailwind CSS Usage
|
|
|
|
- **Prefer Tailwind utilities** over custom CSS where possible
|
|
- **Use @apply sparingly** - only for frequently repeated patterns
|
|
- **Extend the theme** in tailwind.config.js for custom values
|
|
- **Use arbitrary values** (e.g., `w-[123px]`) only when necessary
|
|
""")
|
|
|
|
# Check for CSS Modules
|
|
css_modules = list(root_path.rglob('*.module.css')) + list(root_path.rglob('*.module.scss'))
|
|
if css_modules:
|
|
guidelines.append("""
|
|
### CSS Modules
|
|
|
|
- **One module per component** - keep styles colocated
|
|
- **Use camelCase** for class names in modules
|
|
- **Avoid global styles** unless absolutely necessary
|
|
- **Compose classes** to avoid duplication
|
|
""")
|
|
|
|
# Default guidelines
|
|
guidelines.append("""
|
|
### General Principles
|
|
|
|
- **Mobile-first**: Design for mobile, enhance for desktop
|
|
- **Accessibility**: Ensure WCAG AA compliance minimum
|
|
- **Performance**: Optimize for fast loading and rendering
|
|
- **Consistency**: Use design tokens and established patterns
|
|
- **Dark mode**: Support both light and dark themes where applicable
|
|
""")
|
|
|
|
return "\n".join(guidelines)
|
|
|
|
|
|
def generate_markdown_styleguide(root_path):
|
|
"""Generate markdown style guide."""
|
|
project_name = detect_project_name(root_path)
|
|
date = datetime.now().strftime('%Y-%m-%d')
|
|
|
|
# Extract design tokens
|
|
tailwind_tokens = extract_design_tokens_from_tailwind(root_path)
|
|
css_vars = extract_css_variables(root_path)
|
|
categorized = categorize_css_variables(css_vars)
|
|
|
|
# Build sections
|
|
colors_section = format_tokens_section(categorized['colors'], 'Colors')
|
|
spacing_section = format_tokens_section(categorized['spacing'], 'Spacing')
|
|
typography_section = format_tokens_section(categorized['typography'], 'Typography')
|
|
|
|
if tailwind_tokens:
|
|
colors_section = f"**Using Tailwind CSS**\n\nSee `tailwind.config.js` for the complete color palette.\n\n{colors_section}"
|
|
|
|
components_section = """
|
|
### Button
|
|
|
|
*Component documentation to be added*
|
|
|
|
### Card
|
|
|
|
*Component documentation to be added*
|
|
|
|
### Form Elements
|
|
|
|
*Component documentation to be added*
|
|
|
|
*Add component documentation as your design system grows.*
|
|
"""
|
|
|
|
guidelines_section = generate_guidelines(root_path)
|
|
|
|
# Build full guide
|
|
styleguide = f"""# {project_name} Style Guide
|
|
|
|
**Last Updated**: {date}
|
|
|
|
*This is a living document that evolves with the project.*
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This style guide documents the design system, patterns, and conventions used in {project_name}.
|
|
|
|
## Design Tokens
|
|
|
|
Design tokens are the visual design atoms of the design system. They define colors, typography, spacing, and other fundamental values.
|
|
|
|
### Colors
|
|
|
|
{colors_section}
|
|
|
|
### Typography
|
|
|
|
{typography_section}
|
|
|
|
### Spacing
|
|
|
|
{spacing_section}
|
|
|
|
## Components
|
|
|
|
Document common component patterns here.
|
|
|
|
{components_section}
|
|
|
|
## Guidelines
|
|
|
|
{guidelines_section}
|
|
|
|
## Accessibility
|
|
|
|
### Color Contrast
|
|
|
|
- Ensure all text has minimum 4.5:1 contrast ratio (WCAG AA)
|
|
- Large text (18pt+) requires minimum 3:1 contrast ratio
|
|
|
|
### Focus States
|
|
|
|
- All interactive elements must have visible focus indicators
|
|
- Focus indicators must have 3:1 contrast ratio with background
|
|
|
|
### Keyboard Navigation
|
|
|
|
- All functionality available via keyboard
|
|
- Logical tab order
|
|
- Skip links for main content
|
|
|
|
## Performance
|
|
|
|
### CSS Best Practices
|
|
|
|
- Remove unused styles in production
|
|
- Use CSS containment for complex layouts
|
|
- Optimize font loading with `font-display: swap`
|
|
- Minimize use of expensive properties in animations
|
|
|
|
## Resources
|
|
|
|
- [Design System Documentation](./docs/design-system.md)
|
|
- [Component Library](./src/components/)
|
|
- [Accessibility Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
|
|
---
|
|
|
|
*Generated by Style Master on {date}*
|
|
"""
|
|
|
|
return styleguide
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Generate project style guide',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--path',
|
|
type=str,
|
|
default='.',
|
|
help='Path to project root (default: current directory)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--output',
|
|
type=str,
|
|
default='STYLEGUIDE.md',
|
|
help='Output file path (default: STYLEGUIDE.md)'
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--format',
|
|
choices=['markdown', 'json'],
|
|
default='markdown',
|
|
help='Output format (default: markdown)'
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
root_path = Path(args.path).resolve()
|
|
|
|
if not root_path.exists():
|
|
print(f"[ERROR] Error: Path does not exist: {root_path}")
|
|
sys.exit(1)
|
|
|
|
print(f"[NOTE] Generating style guide for: {root_path}")
|
|
|
|
# Generate style guide
|
|
if args.format == 'markdown':
|
|
styleguide = generate_markdown_styleguide(root_path)
|
|
|
|
output_path = Path(args.output)
|
|
with open(output_path, 'w') as f:
|
|
f.write(styleguide)
|
|
|
|
print(f"[OK] Style guide created: {output_path}")
|
|
print(f"\n Review and customize the generated style guide")
|
|
print(f" Add component examples, update guidelines, refine tokens\n")
|
|
else:
|
|
# JSON format (for programmatic use)
|
|
css_vars = extract_css_variables(root_path)
|
|
categorized = categorize_css_variables(css_vars)
|
|
|
|
data = {
|
|
'project': detect_project_name(root_path),
|
|
'generated': datetime.now().isoformat(),
|
|
'tokens': categorized
|
|
}
|
|
|
|
output_path = Path(args.output).with_suffix('.json')
|
|
with open(output_path, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
print(f"[OK] Style guide data exported: {output_path}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|