Module: JekyllMermaidPrebuild::MmdcWrapper

Defined in:
lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb

Overview

Wrapper for mmdc (mermaid CLI) commands

Constant Summary collapse

MMDC_COMMAND =

Check if mmdc is available in PATH

Returns:

  • (Boolean)

    true if mmdc is available

"mmdc"
ALLOWED_RENDER_THEMES =

Render mermaid source to SVG file

%i[default dark].freeze

Class Method Summary collapse

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


14
15
16
17
18
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 14

def self.available?
  return @available if instance_variable_defined?(:@available)

  @available = command_exists?(MMDC_COMMAND)
end

.check_statusSymbol

Check mmdc status (availability and Puppeteer)

Returns:

  • (Symbol)

    :ok, :not_found, :puppeteer_error, or :unknown_error



51
52
53
54
55
56
57
58
59
60
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 51

def self.check_status
  return @check_status if instance_variable_defined?(:@check_status)

  unless available?
    @check_status = :not_found
    return @check_status
  end

  @check_status = test_render
end

.command_exists?(cmd) ⇒ Boolean

Check if a command exists in PATH (cross-platform)

Parameters:

  • cmd (String)

    command name

Returns:

  • (Boolean)

    true if command found



24
25
26
27
28
29
30
31
32
33
34
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 24

def self.command_exists?(cmd)
  return false if cmd.nil? || cmd.empty?

  cmd_name = Gem.win_platform? ? "#{cmd}.exe" : cmd
  path_dirs = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR)

  path_dirs.any? do |dir|
    executable = File.join(dir, cmd_name)
    File.file?(executable) && File.executable?(executable)
  end
end

.mermaid_fence_patternRegexp

Build regex pattern for mermaid fenced code blocks Supports both backtick (```) and tilde (~~~) fences with 3+ characters

Returns:

  • (Regexp)

    pattern matching mermaid code blocks



129
130
131
132
133
134
135
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 129

def self.mermaid_fence_pattern
  /
    ^(`{3,}|~{3,})mermaid\s*\n  # Opening fence: 3+ backticks or tildes, then 'mermaid'
    (.*?)                        # Content (captured, non-greedy)
    ^\1\s*$                      # Closing fence: must match opening fence type and length
  /mx
end

.render(mermaid_source, output_path, theme: :default) ⇒ Boolean

Returns true if successful.

Parameters:

  • theme (Symbol) (defaults to: :default)

    :default (mermaid default theme) or :dark (mmdc -t dark)

Returns:

  • (Boolean)

    true if successful

Raises:

  • (ArgumentError)

    if theme is not supported



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 106

def self.render(mermaid_source, output_path, theme: :default)
  raise ArgumentError, "unsupported mmdc theme #{theme.inspect}" unless ALLOWED_RENDER_THEMES.include?(theme)

  input_file = Tempfile.new(["mermaid", ".mmd"])

  begin
    input_file.write(mermaid_source)
    input_file.close

    cmd = [MMDC_COMMAND, "-i", input_file.path, "-o", output_path, "-e", "svg"]
    cmd += ["-t", "dark"] if theme == :dark
    _stdout, _stderr, status = Open3.capture3(*cmd)

    status.success?
  ensure
    input_file.unlink
  end
end

.reset_cache!Object

Reset cached status (useful for testing)



63
64
65
66
67
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 63

def self.reset_cache!
  remove_instance_variable(:@available) if instance_variable_defined?(:@available)
  remove_instance_variable(:@version) if instance_variable_defined?(:@version)
  remove_instance_variable(:@check_status) if instance_variable_defined?(:@check_status)
end

.test_renderSymbol

Test mmdc with a minimal diagram to verify Puppeteer works

Returns:

  • (Symbol)

    :ok, :puppeteer_error, or :unknown_error



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 72

def self.test_render
  input = Tempfile.new(["", ".mmd"])
  output = Tempfile.new(["", ".svg"])
  begin
    input.write("graph TD\nA-->B")
    input.close
    output.close # Close before mmdc writes (Windows file locking)

    _stdout, stderr, status = Open3.capture3(
      MMDC_COMMAND, "-i", input.path, "-o", output.path, "-e", "svg"
    )

    if status.success?
      :ok
    elsif stderr.include?("libgbm") || stderr.include?("browser process")
      :puppeteer_error
    else
      :unknown_error
    end
  ensure
    input.unlink
    output.unlink
  end
end

.versionString?

Get mmdc version

Returns:

  • (String, nil)

    version string or nil



39
40
41
42
43
44
45
46
# File 'lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb', line 39

def self.version
  return @version if instance_variable_defined?(:@version)

  output, status = Open3.capture2e(MMDC_COMMAND, "--version")
  @version = output.strip if status.success?
rescue StandardError
  @version = nil
end