mermaid rendering - chunhualiao/public-docs GitHub Wiki

sometimes I just want to render them myself

#!/usr/bin/env python3
# Instructions to install dependencies and run the program:
#
# 1. Install Node.js:
#    - Download and install Node.js from https://nodejs.org/
#    - Verify installation by running `node --version` in your terminal.
#
# 2. Install the Mermaid CLI (@mermaid-js/mermaid-cli):
#    - Run `npm install -g @mermaid-js/mermaid-cli` in your terminal.
#    - If you encounter permissions issues, you might need to use `sudo` (for macOS/Linux)
#      or run the command prompt as an administrator (for Windows).
#    - Verify installation by running `mmdc --version`.
#
# 3. Run the script:
#    - You can run the script directly with the embedded example by uncommenting the lines in the `if __name__ == "__main__":` block and running:
#      `python3 mermaid-rendering.py`
#    - Alternatively, you can use the command-line interface:
#      - To render Mermaid code from a string:
#        `python3 mermaid-rendering.py -c "your_mermaid_code"`
#      - To render Mermaid code from a file:
#        `python3 mermaid-rendering.py -f /path/to/your/file.mmd`
#      - You can specify the output file with `-o /path/to/output.png`, output type with `-t [png, pdf, svg]`, and theme with `--theme [default, forest, dark, neutral]`.
#      - For example:
#        `python3 mermaid-rendering.py -f input.mmd -o output.png -t png --theme default`

import os
import sys
import subprocess
import argparse
import tempfile
import json
from pathlib import Path
import textwrap

class MermaidRenderer:
    """
    A Python class to render Mermaid diagrams to various formats
    using puppeteer-mermaid behind the scenes
    """
    
    def __init__(self):
        """Initialize the renderer and check if dependencies are installed"""
        self._check_dependencies()
    
    def _check_dependencies(self):
        """Check if Node.js and puppeteer-mermaid are installed"""
        try:
            # Check for Node.js
            subprocess.run(["node", "--version"], capture_output=True, check=True)
        except (subprocess.SubprocessError, FileNotFoundError):
            sys.exit("Error: Node.js is not installed. Please install Node.js from https://nodejs.org/")
        
        # Check if @mermaid-js/mermaid-cli is installed
        result = subprocess.run(["npm", "list", "-g", "@mermaid-js/mermaid-cli"],
                              capture_output=True, text=True)

        if "mermaid-cli" not in result.stdout:
            print("Installing @mermaid-js/mermaid-cli globally...")
            try:
                subprocess.run(["npm", "install", "-g", "@mermaid-js/mermaid-cli"], check=True)
                print("@mermaid-js/mermaid-cli installed successfully.")
            except subprocess.SubprocessError:
                sys.exit("Error: Failed to install @mermaid-js/mermaid-cli. Please install manually using: npm install -g @mermaid-js/mermaid-cli")

    def render(self, mermaid_code, output_file=None, output_format="png", theme="default"):
        """
        Render Mermaid code to the specified format
        
        Args:
            mermaid_code (str): The Mermaid diagram code
            output_file (str, optional): Output file path. If None, generates a filename based on format.
            output_format (str, optional): Output format. Options: png, pdf, svg. Default: png
            theme (str, optional): Mermaid theme. Default: default
            
        Returns:
            str: Path to the generated file
        """
        # Validate output format
        valid_formats = ["png", "pdf", "svg"]
        if output_format not in valid_formats:
            sys.exit(f"Error: Invalid output format. Choose from: {', '.join(valid_formats)}")
        
        # Create a temporary file for the Mermaid code
        with tempfile.NamedTemporaryFile(mode='w', suffix='.mmd', delete=False) as temp_file:
            temp_file.write(mermaid_code)
            input_path = temp_file.name
        
        # Generate output file name if not provided
        if not output_file:
            output_file = f"diagram.{output_format}"
        
        # Ensure output directory exists
        output_dir = os.path.dirname(output_file)
        if output_dir:
            os.makedirs(output_dir, exist_ok=True)
        
        # Run puppeteer-mermaid
        try:
            cmd = [
                "mmdc",
                "-i", input_path,
                "-o", output_file,
                "-t", theme,
                "-f", output_format
            ]
            
            subprocess.run(cmd, check=True)
            print(f"Diagram saved to: {output_file}")
            
            # Clean up the temporary file
            os.unlink(input_path)
            
            return output_file
            
        except subprocess.SubprocessError as e:
            os.unlink(input_path)
            sys.exit(f"Error rendering diagram: {str(e)}")

def main():
    """Command line interface for the Mermaid renderer"""
    parser = argparse.ArgumentParser(description="Render Mermaid diagrams to PNG, PDF, or SVG.")
    
    input_group = parser.add_mutually_exclusive_group(required=True)
    input_group.add_argument("-c", "--code", help="Mermaid code as a string")
    input_group.add_argument("-f", "--file", help="Path to a file containing Mermaid code")
    
    parser.add_argument("-o", "--output", help="Output file path")
    parser.add_argument("-t", "--type", default="png", choices=["png", "pdf", "svg"],
                        help="Output file type (default: png)")
    parser.add_argument("--theme", default="default", 
                        choices=["default", "forest", "dark", "neutral"],
                        help="Mermaid theme (default: default)")
    
    args = parser.parse_args()
    
    # Get Mermaid code from string or file
    if args.code:
        mermaid_code = args.code
    else:
        try:
            with open(args.file, 'r') as f:
                mermaid_code = f.read()
        except (IOError, FileNotFoundError):
            sys.exit(f"Error: Could not read file {args.file}")
    
    # Create renderer and render the diagram
    renderer = MermaidRenderer()
    renderer.render(mermaid_code, args.output, args.type, args.theme)

# Example usage with multiline string for easy copy-paste
if __name__ == "__main__":
    # You can replace this multiline string with your own Mermaid diagram code
    MERMAID_CODE = """
pie title NETFLIX
         "Time spent looking for movie" : 90
         "Time spent watching it" : 10

    """
    
    # Uncomment and modify these lines to run directly
    renderer = MermaidRenderer()
    renderer.render(MERMAID_CODE, "flowchart.svg", "svg", "default")
    renderer.render(MERMAID_CODE, "flowchart.pdf", "pdf", "default")

    # Or use the command-line interface
    #main()