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].

[mermaid]: https://mermaid-js.github.io/mermaid/#/
[fail-exit]: mermaidjs/mermaid.cli#77
  • Loading branch information
chrisfarms committed May 4, 2020
1 parent e140155 commit bf4e2ec
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 0 deletions.
11 changes: 11 additions & 0 deletions example/source/code.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,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;
```

38 changes: 38 additions & 0 deletions 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 @@ -30,5 +32,41 @@ def table(header, body)
</table>
</div>)
end

def block_code(code, lang)
if lang == "mermaid"
block_diagram(code)
else
super
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}", 2)
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lint": "standard"
},
"dependencies": {
"@mermaid-js/mermaid-cli": "^8.4.8",
"govuk-frontend": "^3.6.0"
},
"devDependencies": {
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 bf4e2ec

Please sign in to comment.