#!/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""" {html_content} """ # 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__)} """ # 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 [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()