Skip to content

Commit

Permalink
Add support for rendering diagrams with mermaid
Browse files Browse the repository at this point in the history
[mermaid][Mermaid] generates diagrams and flowcharts from text in a
similar manner as Markdown.

This adds support for rendering diagrams from any markdown code blocks
with the language tag `mermaid`.

For example the code block:

```mermaid
sequenceDiagram
	Alice->>+John: Hello John, how are you?
	John-->>-Alice: Great!
```

Will now render an inline SVG diagram of the sequence instead of the raw
`<code>` block.

Keeping diagrams as code in this way makes it significantly easier to
keep diagrams up to date with the documentation, and can make reviewing
changes to them easier.

The implementation takes advantage of the existing dependecy on node.js to
install and execute the mermaid cli tool that translates the various
diagram code into SVG. A timeout is added to execution to workaround an
issue where the cli tool [fails to terminate on error][fail-exit].

The deafult mermaid themes for diagrams don't quite fit the GOV.UK
"brand" colors, and creating the CSS to get the colors pretty is out of
scope, so this just sets it to use the "neutral" (grayscale) theme,
which at least doesn't clash with anything.

[mermaid]: https://mermaid-js.github.io/mermaid/#/
[fail-exit]: mermaidjs/mermaid.cli#77
  • Loading branch information
chrisfarms authored and timblair committed Nov 14, 2024
1 parent 9aa4b31 commit d2ed7ea
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 2 deletions.
11 changes: 11 additions & 0 deletions example/source/code.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@ An example of a code block with a short length
RSpec.describe ContentItem do
end
```

An example of a [mermaid](https://mermaid-js.github.io/mermaid) diagram

```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```

34 changes: 33 additions & 1 deletion lib/govuk_tech_docs/tech_docs_html_renderer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require "middleman-core/renderers/redcarpet"
require "tmpdir"
require "timeout"

module GovukTechDocs
class TechDocsHTMLRenderer < Middleman::Renderers::MiddlemanRedcarpetHTML
Expand Down Expand Up @@ -80,7 +82,9 @@ def table_row(body)
end

def block_code(text, lang)
if defined?(super)
if lang == "mermaid"
block_diagram(text)
elsif defined?(super)
# Post-processing the block_code HTML to implement tabbable code blocks.
#
# Middleman monkey patches the Middleman::Renderers::MiddlemanRedcarpetHTML
Expand Down Expand Up @@ -108,5 +112,33 @@ def block_code(text, lang)
pre.to_html
end
end

def block_diagram(code)
mmdc = "#{__dir__}/../../node_modules/.bin/mmdc"
Dir.mktmpdir do |tmp|
input_path = "#{tmp}/input"
output_path = "#{tmp}/output.svg"
File.open(input_path, "w") { |f| f.write(code) }
ok = exec_with_timeout("#{mmdc} -i #{input_path} -o #{output_path} --theme neutral", 10)
if ok && File.exist?(output_path)
File.read(output_path)
else
"<pre>#{code}</pre>"
end
end
end

def exec_with_timeout(cmd, timeout)
pid = Process.spawn(cmd, { %i[err out] => :close, :pgroup => true })
begin
Timeout.timeout(timeout) do
Process.waitpid(pid, 0)
$?.exitstatus.zero?
end
rescue Timeout::Error
Process.kill(15, -Process.getpgid(pid))
false
end
end
end
end
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"lint": "standard"
},
"dependencies": {
"govuk-frontend": "~5.7.1"
"govuk-frontend": "~5.7.1",
"@mermaid-js/mermaid-cli": "^8.4.8"
},
"devDependencies": {
"standard": "^14.3.4"
Expand Down
26 changes: 26 additions & 0 deletions spec/features/diagrams_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require "rack/file"
require "capybara/rspec"

Capybara.app = Rack::File.new("example/build")

RSpec.describe "Diagrams in code blocks" do
include Capybara::DSL

it "generates static SVG from mermaid code blocks" do
when_the_site_is_created
and_i_visit_the_code_page
then_there_is_an_svg_diagram
end

def when_the_site_is_created
rebuild_site!
end

def and_i_visit_the_code_page
visit "/code.html"
end

def then_there_is_an_svg_diagram
expect(page.body).to match '<svg id="mermaid-'
end
end

0 comments on commit d2ed7ea

Please sign in to comment.