Initial commit
This commit is contained in:
308
skills/scripts/create_package.py
Normal file
308
skills/scripts/create_package.py
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Package architecture documentation into a comprehensive ZIP file.
|
||||
Includes: ARCHITECTURE.md, OpenAPI spec, PDF, and diagram images.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
|
||||
def convert_markdown_to_pdf(md_file: str, output_pdf: str, work_dir: str) -> bool:
|
||||
"""Convert markdown to PDF using pandoc if available, else create placeholder."""
|
||||
try:
|
||||
# Try using pandoc
|
||||
result = subprocess.run(
|
||||
['pandoc', md_file, '-o', output_pdf, '--pdf-engine=pdflatex'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
# Fallback: Try weasyprint
|
||||
try:
|
||||
import markdown
|
||||
from weasyprint import HTML, CSS
|
||||
|
||||
# Read markdown
|
||||
with open(md_file, 'r', encoding='utf-8') as f:
|
||||
md_content = f.read()
|
||||
|
||||
# Convert to HTML
|
||||
html_content = markdown.markdown(md_content, extensions=['tables', 'fenced_code'])
|
||||
|
||||
# Add styling
|
||||
styled_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 0 20px;
|
||||
}}
|
||||
h1, h2, h3 {{ color: #333; }}
|
||||
h1 {{ border-bottom: 2px solid #333; padding-bottom: 10px; }}
|
||||
h2 {{ border-bottom: 1px solid #666; padding-bottom: 5px; margin-top: 30px; }}
|
||||
code {{
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}}
|
||||
pre {{
|
||||
background: #f4f4f4;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}}
|
||||
table {{
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}}
|
||||
th, td {{
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}}
|
||||
th {{
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{html_content}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Convert HTML to PDF
|
||||
HTML(string=styled_html).write_pdf(output_pdf)
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Last resort: Create a simple text-based PDF notice
|
||||
notice = f"""PDF Generation Not Available
|
||||
|
||||
The required tools (pandoc or weasyprint) are not installed.
|
||||
Please refer to the ARCHITECTURE.md file for the full documentation.
|
||||
|
||||
To generate a PDF manually:
|
||||
1. Install pandoc: apt-get install pandoc texlive-latex-base
|
||||
OR
|
||||
2. Install weasyprint: pip install markdown weasyprint --break-system-packages
|
||||
|
||||
Then run:
|
||||
pandoc {md_file} -o {output_pdf}
|
||||
OR
|
||||
python {os.path.abspath(__file__)} <work_dir>
|
||||
"""
|
||||
|
||||
# Create a simple text file as placeholder
|
||||
with open(output_pdf + '.txt', 'w') as f:
|
||||
f.write(notice)
|
||||
|
||||
print(f"⚠️ PDF generation tools not available. Created notice file instead.")
|
||||
return False
|
||||
|
||||
|
||||
def render_mermaid_diagrams(work_dir: str, diagrams_dir: str) -> list:
|
||||
"""Render Mermaid diagrams to PNG images using mmdc if available."""
|
||||
|
||||
# Find all .mmd files in work directory
|
||||
mmd_files = list(Path(work_dir).glob('*.mmd'))
|
||||
|
||||
if not mmd_files:
|
||||
print("No .mmd files found to render")
|
||||
return []
|
||||
|
||||
os.makedirs(diagrams_dir, exist_ok=True)
|
||||
rendered_files = []
|
||||
|
||||
for mmd_file in mmd_files:
|
||||
output_file = os.path.join(diagrams_dir, mmd_file.stem + '.png')
|
||||
|
||||
try:
|
||||
# Try using mermaid-cli (mmdc)
|
||||
result = subprocess.run(
|
||||
['mmdc', '-i', str(mmd_file), '-o', output_file, '-b', 'transparent'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0 and os.path.exists(output_file):
|
||||
rendered_files.append(output_file)
|
||||
print(f"✅ Rendered: {mmd_file.name} → {os.path.basename(output_file)}")
|
||||
else:
|
||||
print(f"⚠️ Could not render: {mmd_file.name}")
|
||||
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired) as e:
|
||||
print(f"⚠️ mermaid-cli not available. Skipping diagram rendering.")
|
||||
print(f" Install with: npm install -g @mermaid-js/mermaid-cli")
|
||||
break
|
||||
|
||||
return rendered_files
|
||||
|
||||
|
||||
def create_package_zip(work_dir: str, output_zip: str) -> bool:
|
||||
"""Create a ZIP package with all documentation files in proper structure."""
|
||||
|
||||
work_path = Path(work_dir)
|
||||
|
||||
# Collect files to include
|
||||
files_to_include = []
|
||||
|
||||
# ARCHITECTURE.md (required)
|
||||
arch_file = work_path / 'ARCHITECTURE.md'
|
||||
if arch_file.exists():
|
||||
files_to_include.append(('ARCHITECTURE.md', arch_file))
|
||||
else:
|
||||
print("❌ ARCHITECTURE.md not found!")
|
||||
return False
|
||||
|
||||
# OpenAPI spec (if exists)
|
||||
openapi_file = work_path / 'openapi.json'
|
||||
if openapi_file.exists():
|
||||
files_to_include.append(('openapi.json', openapi_file))
|
||||
|
||||
# PDF (if exists)
|
||||
pdf_file = work_path / 'ARCHITECTURE.pdf'
|
||||
pdf_exists = False
|
||||
if pdf_file.exists():
|
||||
files_to_include.append(('ARCHITECTURE.pdf', pdf_file))
|
||||
pdf_exists = True
|
||||
else:
|
||||
# Check for the txt notice
|
||||
pdf_txt = work_path / 'ARCHITECTURE.pdf.txt'
|
||||
if pdf_txt.exists():
|
||||
files_to_include.append(('PDF_GENERATION_NOTICE.txt', pdf_txt))
|
||||
|
||||
# Find .mmd source files (should be in work_dir root)
|
||||
mmd_files = sorted(list(work_path.glob('*.mmd')))
|
||||
|
||||
# Find diagram images (PNG/SVG)
|
||||
diagrams_dir = work_path / 'diagrams'
|
||||
diagram_files = []
|
||||
if diagrams_dir.exists():
|
||||
diagram_files = sorted(list(diagrams_dir.glob('*.png')) + list(diagrams_dir.glob('*.svg')))
|
||||
|
||||
# Create ZIP with proper structure
|
||||
try:
|
||||
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Add main files at root
|
||||
for arc_name, file_path in files_to_include:
|
||||
zipf.write(file_path, arc_name)
|
||||
print(f"📦 Added: {arc_name}")
|
||||
|
||||
# Add .mmd source files to diagrams/source/
|
||||
if mmd_files:
|
||||
for mmd_file in mmd_files:
|
||||
arc_name = f"diagrams/source/{mmd_file.name}"
|
||||
zipf.write(mmd_file, arc_name)
|
||||
print(f"📦 Added: {arc_name}")
|
||||
|
||||
# Add rendered diagram images to diagrams/
|
||||
if diagram_files:
|
||||
for diagram_file in diagram_files:
|
||||
arc_name = f"diagrams/{diagram_file.name}"
|
||||
zipf.write(diagram_file, arc_name)
|
||||
print(f"📦 Added: {arc_name}")
|
||||
|
||||
# Print summary
|
||||
print(f"\n✅ Package created: {output_zip}")
|
||||
print(f"\n📦 Package contents:")
|
||||
print(f" ├── ARCHITECTURE.md")
|
||||
if pdf_exists:
|
||||
print(f" ├── ARCHITECTURE.pdf")
|
||||
if openapi_file.exists():
|
||||
print(f" ├── openapi.json")
|
||||
if mmd_files or diagram_files:
|
||||
print(f" └── diagrams/")
|
||||
if diagram_files:
|
||||
for df in diagram_files:
|
||||
print(f" ├── {df.name}")
|
||||
if mmd_files:
|
||||
print(f" └── source/")
|
||||
for mf in mmd_files:
|
||||
print(f" ├── {mf.name}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error creating ZIP: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python create_package.py <work_directory> [output_zip]")
|
||||
print("\nThe work directory should contain:")
|
||||
print(" - ARCHITECTURE.md (required)")
|
||||
print(" - openapi.json (optional)")
|
||||
print(" - *.mmd files (optional, for diagram rendering)")
|
||||
sys.exit(1)
|
||||
|
||||
work_dir = sys.argv[1]
|
||||
output_zip = sys.argv[2] if len(sys.argv) > 2 else os.path.join(work_dir, 'architecture-package.zip')
|
||||
|
||||
if not os.path.exists(work_dir):
|
||||
print(f"❌ Work directory not found: {work_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"📦 Creating architecture documentation package...\n")
|
||||
print(f"Work directory: {work_dir}")
|
||||
print(f"Output ZIP: {output_zip}\n")
|
||||
|
||||
# Step 1: Convert Markdown to PDF
|
||||
arch_md = os.path.join(work_dir, 'ARCHITECTURE.md')
|
||||
if os.path.exists(arch_md):
|
||||
print("📄 Converting ARCHITECTURE.md to PDF...")
|
||||
pdf_file = os.path.join(work_dir, 'ARCHITECTURE.pdf')
|
||||
convert_markdown_to_pdf(arch_md, pdf_file, work_dir)
|
||||
print()
|
||||
|
||||
# Step 2: Render Mermaid diagrams
|
||||
diagrams_dir = os.path.join(work_dir, 'diagrams')
|
||||
print("🎨 Rendering Mermaid diagrams...")
|
||||
rendered = render_mermaid_diagrams(work_dir, diagrams_dir)
|
||||
if rendered:
|
||||
print(f"✅ Rendered {len(rendered)} diagram(s)")
|
||||
print()
|
||||
|
||||
# Step 3: Create ZIP package
|
||||
print("📦 Creating ZIP package...")
|
||||
success = create_package_zip(work_dir, output_zip)
|
||||
|
||||
if success:
|
||||
# Get file size
|
||||
size_mb = os.path.getsize(output_zip) / (1024 * 1024)
|
||||
print(f"\n✅ Package complete! Size: {size_mb:.2f} MB")
|
||||
print(f"📦 {output_zip}")
|
||||
else:
|
||||
print("\n❌ Package creation failed")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user