Make-ing Mermaid August 13, 2019

Sequence diagrams are a very useful way to communicate about how processes work, especially when there are multiple services (or other actors) at play. A typical component-oriented diagram starts to suffer when there are several calls back and forth between the same systems, for example.

In the past I’ve used PlantUML, websequencediagrams.com, and Sequence Diagram from the Mac App Store.

Recently, though, I’ve been really enjoying Mermaid.

  • It has a nice, simple, syntax
  • It generates png and svg, and both can be styled using CSS
  • You can render mermaid diagrams right in a browser (for example, in hugo, which I haven’t done yet)
  • There’s an online live editor
  • It can also run from the command line

Running mermaid from the command line

Yes, it’s inevitable. If I can run it from the command line, I will.

At this time, there’s a stated issue in the CLI’s README which suggests doing a local install. This works fine; the automation steps listed here assume they’re based off of that. If you’re not doing the local install, just update the path to mmdc to be wherever it is on your system.

Makefile magic

make is a classic tool for a reason. For pretty much any application where some source files (and maybe other files, like configurations) end up being used to generate an output file – make can help.

Here’s the file I’ve used to mostly automate working with mermaid.

all: $(addsuffix .png, $(basename $(wildcard *.mmd)))

%.png: %.mmd style.css config.json
	./node_modules/.bin/mmdc -C style.css -w 2048 -H 1536 -c config.json -t neutral -i $< -o $@
	exiftool -Source"<=$<" $@
	open $@

Which results in this process, helpfully illustrated by mermaid:

mermaid

This is some make superpowers.

all: $(addsuffix .png, $(basename $(wildcard *.mmd)))

Because the target all shows up first, it’s the default, so it runs if you just run make.

all uses a few make builtins to say “for all the mmd files” (aka mermaid sources), remove the mmd (basename), and add .png instead”.

So, if in the directory you have first.mmd and second.mmd, the all target will have first.png and second.png.

%.png: %.mmd style.css config.json

This section, with the %, is a pattern rule.

It creates automatic rules for targets. So any (nonexistent, at least on the first run) .png file names that got added to all in the first stepwill get matched. They’ll automatically depend on both the matching .mmd file, as well as getting rebuilt if the config.json or style.css files get updated.

Inside the pattern rule block, there are 2 special variable names: $< (the input file) and $@ (the output file.)

	./node_modules/.bin/mmdc -C style.css -w 2048 -H 1536 -c config.json -t neutral -i $< -o $@

To make a given png, it’ll run the mermaid CLI tool with a bunch of configuration, including $< (the .mmd file) for input and $@ (the .png file) for output.

The exiftool command – we’ll get to in a minute.

And the final line (open $@) uses the OS’s open tool to show you the resulting image. (YMMV outside of MacOS, but I trust that if you’re generating images from text based markup, you can figure out how to view an image on the platform of your choice.)

And that’s it! A nice png file, delivered by mermaid.

A similar process can be done to generate svg files, which are generally preferable … except when inserting them inside Google Docs, which is what I tend to do with these sequences.

Dark trickery with exiftool

Mermaid files are plain text, which can be used to generate images as we’ve seen here.

But what if it’s time to update a diagram – the sequence diagram is there in the document, it’s almost right, but it needs some updating.

Hopefully, the original file is in git somewhere, helpfully version controlled … but a lot of times documentation flows don’t get the same kind of engineering toolchain love as something like source code would. So, maybe, it’s just an image. And yes, looking at the above, you could probably type this back in:

sequenceDiagram
	user ->> make: run
	make ->> files: any files changed?
	alt No Files Changed
		make -x user: nothing to make
	end
	make ->> mermaid: build png
	mermaid ->> make: return status
	make ->> exiftool: add mermaid source to image
	exiftool ->> make: return status
	make ->> open: Open file
	open ->> user: Check out this file
	make ->> user: Build complete

but there’s got to be a better way.

I am lucky enough to work with Sean Kilgore, aka @log1kal, and he found this problem particularly troublesome.

Which explains the exiftool line of the Makefile, his truly excellent addition:

  exiftool -Source"<=$<" $@ 

Using some creative syntax, it includes the entire source file being used to generate the image into the Source tag of the png file.

This is cool stuff, because it can be recovered. Using a simple shell script like recover_source.sh:

#!/usr/bin/env bash

exiftool -s -j -Source $1 | jq -r .[0].Source

-s reads the tag value, -j prints the structure as json, jq then picks the source attribute from the resulting document, and it’s -r argument means it actually prints whitespace and newlines for real, instead of as escape codes.

So, does it work?

Here is a Google Doc, with a mermaid image inserted via Insert > Image.

google doc screenshot

Sadly, there’s not clear way to do a right-click “Save Image”, but File > Download > Webpage will give a zip file with HTML and all the other assets.

$ unzip -l Test\ Mermaid.zip 
Archive:  Test Mermaid.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
     2676  08-13-2019 22:48   TestMermaid.html
    36923  08-13-2019 22:48   images/image1.png
---------                     -------
    39599                     2 files

and … sure enough, it works!

$ ~/work/mermaid/recover_source.sh image1.png 
sequenceDiagram
	user ->> make: run
	make ->> files: any files changed?
	alt No Files Changed
		make -x user: nothing to make
	end
	# ... rest cut because you've seen it already

It’s not perfect.

  • it’s kind of a pain to download a zip file and extract the contents
  • If someone copy-pasted the contents of the image (e.g. from Preview) it doesn’t preserve the metadata.

But, it’s still better than nothing, and folks that care about it working would know to avoid the copy/paste issue.

And yes, the images in this blog post come with source included, if you want to try it out.

Making it even more automatic

One final target can be added to the Makefile for extra fun:

watch:
	fswatch -o . | xargs -n1 sh -c 'make all'

This uses the open source fswatch, a cross-platform file change watching tool, to automatically re-run Make when things in the directory change.

So, just run make watch, leave that running in the background, and work with your editor of choice in the other. Creating new .mmd files magically leads to new images popping up, as does editing existing ones, or changing styles.

And just for fun, here’s that process via mermaid:

fsevents

and the source:

graph LR
	makewatch[$ make watch]	
	make[$ make]	
	filesystem((Filesystem))

	makewatch --> fsevents
	fsevents-- watches --> filesystem
	fsevents-- runs --> make
	vim -- writes to --> filesystem

Happy Mermaiding!