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..d137b0e7 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) + $CHILD_STATUS.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 '