From d2ed7ea1b7551a0f57f5b73963bb4d4894862c00 Mon Sep 17 00:00:00 2001 From: chrisfarms Date: Sat, 2 May 2020 17:54:31 +0100 Subject: [PATCH 1/3] Add support for rendering diagrams with mermaid [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 `` 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]: https://github.com/mermaidjs/mermaid.cli/issues/77 --- example/source/code.html.md | 11 ++++++ .../tech_docs_html_renderer.rb | 34 ++++++++++++++++++- package.json | 3 +- spec/features/diagrams_spec.rb | 26 ++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 spec/features/diagrams_spec.rb diff --git a/example/source/code.html.md b/example/source/code.html.md index 5b2a4aaa..9cbd2327 100644 --- a/example/source/code.html.md +++ b/example/source/code.html.md @@ -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; +``` + diff --git a/lib/govuk_tech_docs/tech_docs_html_renderer.rb b/lib/govuk_tech_docs/tech_docs_html_renderer.rb index 31b47c4d..37160aa9 100644 --- a/lib/govuk_tech_docs/tech_docs_html_renderer.rb +++ b/lib/govuk_tech_docs/tech_docs_html_renderer.rb @@ -1,4 +1,6 @@ require "middleman-core/renderers/redcarpet" +require "tmpdir" +require "timeout" module GovukTechDocs class TechDocsHTMLRenderer < Middleman::Renderers::MiddlemanRedcarpetHTML @@ -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 @@ -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 + "
#{code}
" + 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 diff --git a/package.json b/package.json index dcdc429e..f9b39bdb 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/spec/features/diagrams_spec.rb b/spec/features/diagrams_spec.rb new file mode 100644 index 00000000..b1ceeb08 --- /dev/null +++ b/spec/features/diagrams_spec.rb @@ -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 '#{code}" + %Q{
#{code}
} end end end From c851210008629df23e8383a1574ba6d2e06077d4 Mon Sep 17 00:00:00 2001 From: Tim Blair Date: Thu, 14 Nov 2024 16:42:41 +0000 Subject: [PATCH 3/3] Fix Rubocop failures --- lib/govuk_tech_docs/tech_docs_html_renderer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/govuk_tech_docs/tech_docs_html_renderer.rb b/lib/govuk_tech_docs/tech_docs_html_renderer.rb index f25ca1ed..d137b0e7 100644 --- a/lib/govuk_tech_docs/tech_docs_html_renderer.rb +++ b/lib/govuk_tech_docs/tech_docs_html_renderer.rb @@ -123,7 +123,7 @@ def block_diagram(code) if ok && File.exist?(output_path) File.read(output_path) else - %Q{
#{code}
} + %({
#{code}
) end end end @@ -133,7 +133,7 @@ def exec_with_timeout(cmd, timeout) begin Timeout.timeout(timeout) do Process.waitpid(pid, 0) - $?.exitstatus.zero? + $CHILD_STATUS.exitstatus.zero? end rescue Timeout::Error Process.kill(15, -Process.getpgid(pid))