|
import argparse |
|
import os |
|
import subprocess |
|
import tempfile |
|
import datetime |
|
import random |
|
import string |
|
import shlex |
|
import uuid |
|
|
|
|
|
def generate_unique_filename(format): |
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") |
|
random_string = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)) |
|
return f"mermaid_{timestamp}_{random_string}.{format}" |
|
|
|
|
|
def find_chrome_path(): |
|
home_dir = os.path.expanduser("~") |
|
cache_dir = os.path.join(home_dir, ".cache", "puppeteer") |
|
|
|
try: |
|
cmd = f"find {shlex.quote(cache_dir)} -name chrome-headless-shell -type f | sort -V | tail -n 1" |
|
result = subprocess.run(cmd, shell=True, check=True, capture_output=True, text=True) |
|
chrome_path = result.stdout.strip() |
|
|
|
if not chrome_path: |
|
print("Chrome headless shell not found in the expected location.") |
|
return None |
|
|
|
return chrome_path |
|
except subprocess.CalledProcessError as e: |
|
print(f"An error occurred while trying to find Chrome: {e}") |
|
return None |
|
|
|
|
|
def render_mermaid(mermaid_code, output_file, format='svg'): |
|
|
|
use_headless = False |
|
if use_headless: |
|
chrome_path = find_chrome_path() |
|
if not chrome_path: |
|
raise Exception("Chrome headless shell not found. Unable to render Mermaid diagram.") |
|
|
|
os.environ["PUPPETEER_EXECUTABLE_PATH"] = chrome_path |
|
|
|
elif os.path.isfile('/usr/bin/chromium-browser'): |
|
os.environ["PUPPETEER_EXECUTABLE_PATH"] = '/usr/bin/chromium-browser' |
|
|
|
|
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.mmd', delete=False) as temp: |
|
temp.write(mermaid_code) |
|
temp_path = temp.name |
|
|
|
config_file = f'puppeteer-config{str(uuid.uuid4())}.json' |
|
try: |
|
|
|
with open(config_file, 'wt') as f: |
|
f.write('{"args": ["--no-sandbox"]}') |
|
cmd = ['mmdc', '-i', temp_path, '-o', output_file, '-f', format, '-p', config_file] |
|
|
|
|
|
result = subprocess.run(cmd, check=True, capture_output=True, text=True) |
|
|
|
|
|
if result.stdout: |
|
print("mmdc output:", result.stdout) |
|
if result.stderr: |
|
print("mmdc warnings/errors:", result.stderr) |
|
|
|
print(f"Created output file in {format} format: {output_file}") |
|
|
|
|
|
if format != 'png': |
|
|
|
base_name = '.'.join(output_file.split('.')[:-1]) |
|
output_file_png = base_name + '.png' |
|
|
|
cmd = ['mmdc', '-i', temp_path, '-o', output_file_png, '-f', 'png', '-w', '2048', '-p', config_file] |
|
|
|
|
|
result = subprocess.run(cmd, check=True, capture_output=True, text=True) |
|
|
|
|
|
if result.stdout: |
|
print("mmdc output:", result.stdout) |
|
if result.stderr: |
|
print("mmdc warnings/errors:", result.stderr) |
|
|
|
print( |
|
f"Created mermaid output file in PNG format: {output_file_png} that is a conversion of {output_file}. " |
|
"Use this for image_query to analyze what SVG looks like, " |
|
"because other tools do not retain fonts when making PNG." |
|
) |
|
|
|
|
|
return os.path.abspath(output_file) |
|
finally: |
|
|
|
os.unlink(temp_path) |
|
if os.path.isfile(config_file): |
|
try: |
|
os.remove(config_file) |
|
except FileNotFoundError: |
|
pass |
|
|
|
|
|
def main(): |
|
parser = argparse.ArgumentParser(description='Render Mermaid diagrams from a file or direct input using mmdc.') |
|
input_group = parser.add_mutually_exclusive_group(required=True) |
|
input_group.add_argument('-f', '--file', '--input', help='Input file containing Mermaid code') |
|
input_group.add_argument('-c', '--code', help='Direct Mermaid code input', nargs='+') |
|
parser.add_argument('-o', '--output', help='Output file name (default: auto-generated unique name)') |
|
|
|
args = parser.parse_args() |
|
|
|
|
|
if args.output is None: |
|
format = 'svg' |
|
args.output = generate_unique_filename(format) |
|
else: |
|
format = args.output.split('.')[-1] |
|
assert format in ['svg', 'png', 'pdf'], f"Invalid output filename {args.output} with format: {format}" |
|
|
|
try: |
|
|
|
if args.file: |
|
with open(args.file, 'r') as f: |
|
mermaid_code = f.read() |
|
else: |
|
mermaid_code = ' '.join(args.code) |
|
|
|
|
|
output_path = render_mermaid(mermaid_code, args.output, format=format) |
|
print(f"Mermaid diagram rendered successfully.") |
|
print(f"Output file: {output_path}") |
|
except subprocess.CalledProcessError as e: |
|
print(f"Error rendering Mermaid diagram: {e}") |
|
print(f"mmdc output: {e.output}") |
|
print(f"mmdc error: {e.stderr}") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|