Class: JekyllMermaidPrebuild::Processor
- Inherits:
-
Object
- Object
- JekyllMermaidPrebuild::Processor
- Defined in:
- lib/jekyll-mermaid-prebuild/processor.rb
Overview
Processes document/page content, replacing mermaid blocks with SVG references
Constant Summary collapse
- FENCE_OPENER =
Pattern to detect any fence opener (captures fence chars and optional language)
/^(`{3,}|~{3,})(\w*)/
Instance Method Summary collapse
-
#convert_block(block) ⇒ Hash?
Convert a single mermaid block to SVG.
-
#digest_string_for_cache(source) ⇒ String
Input to MD5 for cache key.
-
#find_top_level_mermaid_blocks(content) ⇒ Array<Hash>
Find all top-level mermaid code blocks (not nested inside other fences).
-
#handle_fence_line(line, line_start, match, state) ⇒ Object
Handle a line that matches a fence pattern.
-
#handle_line_at_top_level(line_start, language, fence_type, fence_length, state) ⇒ Object
Handle fence line at top level (not inside any fence).
-
#handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state) ⇒ Object
Handle fence line while inside a mermaid block.
-
#handle_line_in_nested_fence(line, fence_type, fence_length, state) ⇒ Object
Handle fence line while inside a non-mermaid fence.
-
#initialize(config, generator) ⇒ Processor
constructor
Initialize processor.
-
#process_content(content, _site = nil) ⇒ Array<String, Integer, Hash>
Process content, replacing mermaid code blocks with figure HTML Only processes top-level mermaid blocks (not nested inside other fences).
-
#process_line(line, state) ⇒ Object
Process a single line for fence detection.
Constructor Details
#initialize(config, generator) ⇒ Processor
Initialize processor
13 14 15 16 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 13 def initialize(config, generator) @config = config @generator = generator end |
Instance Method Details
#convert_block(block) ⇒ Hash?
Convert a single mermaid block to SVG
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 67 def convert_block(block) mermaid_source = block[:content] diagram_type = EmojiCompensator.detect_diagram_type(mermaid_source) source_for_render = if @config.emoji_width_compensation[diagram_type] EmojiCompensator.compensate(mermaid_source, diagram_type) else mermaid_source end digest_input = digest_string_for_cache(source_for_render) cache_key = DigestCalculator.content_digest(digest_input) paths = @generator.generate(source_for_render, cache_key) return nil if paths.nil? || paths.empty? light_url = @generator.build_svg_url(cache_key) html = if @config.prefers_color_scheme == :auto dark_url = @generator.build_svg_url("#{cache_key}-dark") @generator.build_figure_html(light_url, dark_url: dark_url) else @generator.build_figure_html(light_url) end { svgs: paths, html: html } end |
#digest_string_for_cache(source) ⇒ String
Returns input to MD5 for cache key.
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 51 def digest_string_for_cache(source) parts = [source] parts << "pcs=#{@config.prefers_color_scheme}" parts << "bgL=#{@config.chart_background_light}" parts << "bgD=#{@config.chart_background_dark}" parts << "tc=#{@config.text_centering}" unless @config.text_centering parts << "op=#{@config.overflow_protection}" unless @config.overflow_protection pad = @config.edge_label_padding parts << "edge_pad=#{pad}" if pad.is_a?(Numeric) && pad.positive? parts.join("\0") end |
#find_top_level_mermaid_blocks(content) ⇒ Array<Hash>
Find all top-level mermaid code blocks (not nested inside other fences)
96 97 98 99 100 101 102 103 104 105 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 96 def find_top_level_mermaid_blocks(content) blocks = [] state = { blocks: blocks, fence_stack: [], position: 0 } content.lines do |line| process_line(line, state) end blocks end |
#handle_fence_line(line, line_start, match, state) ⇒ Object
Handle a line that matches a fence pattern
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 122 def handle_fence_line(line, line_start, match, state) fence_chars = match[1] language = match[2] fence_type = fence_chars.include?("~") ? "~" : "`" fence_length = fence_chars.length if state[:current_mermaid] handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state) elsif state[:fence_stack].empty? handle_line_at_top_level(line_start, language, fence_type, fence_length, state) else handle_line_in_nested_fence(line, fence_type, fence_length, state) end end |
#handle_line_at_top_level(line_start, language, fence_type, fence_length, state) ⇒ Object
Handle fence line at top level (not inside any fence)
152 153 154 155 156 157 158 159 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 152 def handle_line_at_top_level(line_start, language, fence_type, fence_length, state) if language == "mermaid" state[:current_mermaid] = { start: line_start, fence_type: fence_type, fence_length: fence_length, content_lines: [] } else state[:fence_stack].push([fence_length, fence_type]) end end |
#handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state) ⇒ Object
Handle fence line while inside a mermaid block
138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 138 def handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state) cm = state[:current_mermaid] blocks = state[:blocks] position = state[:position] content_lines = cm[:content_lines] if fence_type == cm[:fence_type] && fence_length == cm[:fence_length] && line.strip == fence_chars blocks << { start: cm[:start], end: position, content: content_lines.join } state[:current_mermaid] = nil else content_lines << line end end |
#handle_line_in_nested_fence(line, fence_type, fence_length, state) ⇒ Object
Handle fence line while inside a non-mermaid fence
162 163 164 165 166 167 168 169 170 171 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 162 def handle_line_in_nested_fence(line, fence_type, fence_length, state) fence_stack = state[:fence_stack] top_fence_length, top_fence_type = fence_stack.last if fence_type == top_fence_type && fence_length >= top_fence_length && line.strip.match?(/\A[`~]+\z/) fence_stack.pop else fence_stack.push([fence_length, fence_type]) end end |
#process_content(content, _site = nil) ⇒ Array<String, Integer, Hash>
Process content, replacing mermaid code blocks with figure HTML Only processes top-level mermaid blocks (not nested inside other fences)
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 24 def process_content(content, _site = nil) return [nil, 0, {}] unless content converted_count = 0 svgs_to_copy = {} # Find top-level mermaid blocks respecting fence nesting top_level_blocks = find_top_level_mermaid_blocks(content) # Process blocks in reverse order to preserve string positions processed = content.dup top_level_blocks.reverse_each do |block| result = convert_block(block) next unless result result => { svgs:, html: } block => { start:, end: block_end } converted_count += 1 svgs_to_copy.merge!(svgs) processed[start...block_end] = html end [processed, converted_count, svgs_to_copy] end |
#process_line(line, state) ⇒ Object
Process a single line for fence detection
108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/jekyll-mermaid-prebuild/processor.rb', line 108 def process_line(line, state) line_start = state[:position] state[:position] += line.length match = line.match(FENCE_OPENER) if match handle_fence_line(line, line_start, match, state) elsif (current_mermaid = state[:current_mermaid]) content_lines = current_mermaid[:content_lines] content_lines << line end end |