jekyll-mermaid-prebuild 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/jekyll-mermaid-prebuild/processor.rb +104 -19
- data/lib/jekyll-mermaid-prebuild/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e76d120b1e9c96438f6f84dea780e132e1bbaf498fd11d8a2f2e37f2b585c2f6
|
|
4
|
+
data.tar.gz: b7e4e4b1249cf4b64e9cd8ee8d7bd19bd0402117e96ad57289d3291e401567f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 463dfec0e22358beeb09c7e4f3500c78bcc35d9c97f46352fe967e100bc5a0d422cfd074c3ade807683230f94a9b1cd8b73e338ba20de6b51e7042cfbebb2129
|
|
7
|
+
data.tar.gz: 89b982187ccb12009c02f7ebf8bb8bab5d97a3766b5bdcf368405ccec5167f533e918bc6c11f3767222ed2cc509178095e54d89c108ce452e3af652f0f454ce3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.2.0...v0.2.1) (2026-01-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Allow literal mermaid code examples to remain un-rendered. ([#4](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/4)) ([6a4e2e1](https://github.com/Texarkanine/jekyll-mermaid-prebuild/commit/6a4e2e1aa43145fde757c64dc20d7189c4eb4489))
|
|
9
|
+
|
|
3
10
|
## [0.2.0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.1.0...v0.2.0) (2026-01-17)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module JekyllMermaidPrebuild
|
|
4
4
|
# Processes document/page content, replacing mermaid blocks with SVG references
|
|
5
5
|
class Processor
|
|
6
|
+
# Pattern to detect any fence opener (captures fence chars and optional language)
|
|
7
|
+
FENCE_OPENER = /^(`{3,}|~{3,})(\w*)/
|
|
8
|
+
|
|
6
9
|
# Initialize processor
|
|
7
10
|
#
|
|
8
11
|
# @param config [Configuration] plugin configuration
|
|
@@ -13,6 +16,7 @@ module JekyllMermaidPrebuild
|
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
# Process content, replacing mermaid code blocks with figure HTML
|
|
19
|
+
# Only processes top-level mermaid blocks (not nested inside other fences)
|
|
16
20
|
#
|
|
17
21
|
# @param content [String] markdown content
|
|
18
22
|
# @param _site [Jekyll::Site] the Jekyll site (unused, kept for API compatibility)
|
|
@@ -20,35 +24,116 @@ module JekyllMermaidPrebuild
|
|
|
20
24
|
def process_content(content, _site = nil)
|
|
21
25
|
return [content, 0, {}] unless content
|
|
22
26
|
|
|
23
|
-
pattern = MmdcWrapper.mermaid_fence_pattern
|
|
24
27
|
converted_count = 0
|
|
25
28
|
svgs_to_copy = {}
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
# Find top-level mermaid blocks respecting fence nesting
|
|
31
|
+
top_level_blocks = find_top_level_mermaid_blocks(content)
|
|
32
|
+
|
|
33
|
+
# Process blocks in reverse order to preserve string positions
|
|
34
|
+
processed = content.dup
|
|
35
|
+
top_level_blocks.reverse_each do |block|
|
|
36
|
+
result = convert_block(block)
|
|
37
|
+
next unless result
|
|
38
|
+
|
|
39
|
+
converted_count += 1
|
|
40
|
+
svgs_to_copy[result[:cache_key]] = result[:cached_path]
|
|
41
|
+
processed[block[:start]...block[:end]] = result[:html]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
[processed, converted_count, svgs_to_copy]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
# Convert a single mermaid block to SVG
|
|
50
|
+
#
|
|
51
|
+
# @param block [Hash] block info with :content key
|
|
52
|
+
# @return [Hash, nil] {cache_key:, cached_path:, html:} or nil if failed
|
|
53
|
+
def convert_block(block)
|
|
54
|
+
mermaid_source = block[:content]
|
|
55
|
+
cache_key = DigestCalculator.content_digest(mermaid_source)
|
|
56
|
+
cached_path = @generator.generate(mermaid_source, cache_key)
|
|
32
57
|
|
|
33
|
-
|
|
34
|
-
cached_path = @generator.generate(mermaid_source, cache_key)
|
|
58
|
+
return nil unless cached_path
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
|
|
60
|
+
svg_url = @generator.build_svg_url(cache_key)
|
|
61
|
+
{ cache_key: cache_key, cached_path: cached_path, html: @generator.build_figure_html(svg_url) }
|
|
62
|
+
end
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
# Find all top-level mermaid code blocks (not nested inside other fences)
|
|
65
|
+
#
|
|
66
|
+
# @param content [String] markdown content
|
|
67
|
+
# @return [Array<Hash>] array of {start:, end:, content:} for each top-level mermaid block
|
|
68
|
+
def find_top_level_mermaid_blocks(content)
|
|
69
|
+
state = { blocks: [], fence_stack: [], current_mermaid: nil, position: 0 }
|
|
41
70
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@generator.build_figure_html(svg_url)
|
|
45
|
-
else
|
|
46
|
-
# Keep original on failure
|
|
47
|
-
original_match
|
|
48
|
-
end
|
|
71
|
+
content.lines.each do |line|
|
|
72
|
+
process_line(line, state)
|
|
49
73
|
end
|
|
50
74
|
|
|
51
|
-
[
|
|
75
|
+
state[:blocks]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Process a single line for fence detection
|
|
79
|
+
def process_line(line, state)
|
|
80
|
+
line_start = state[:position]
|
|
81
|
+
state[:position] += line.length
|
|
82
|
+
|
|
83
|
+
match = line.match(FENCE_OPENER)
|
|
84
|
+
if match
|
|
85
|
+
handle_fence_line(line, line_start, match, state)
|
|
86
|
+
elsif state[:current_mermaid]
|
|
87
|
+
state[:current_mermaid][:content_lines] << line
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Handle a line that matches a fence pattern
|
|
92
|
+
def handle_fence_line(line, line_start, match, state)
|
|
93
|
+
fence_chars = match[1]
|
|
94
|
+
language = match[2]
|
|
95
|
+
fence_type = fence_chars[0]
|
|
96
|
+
fence_length = fence_chars.length
|
|
97
|
+
|
|
98
|
+
if state[:current_mermaid]
|
|
99
|
+
handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state)
|
|
100
|
+
elsif state[:fence_stack].empty?
|
|
101
|
+
handle_line_at_top_level(line_start, language, fence_type, fence_length, state)
|
|
102
|
+
else
|
|
103
|
+
handle_line_in_nested_fence(line, fence_type, fence_length, state)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Handle fence line while inside a mermaid block
|
|
108
|
+
def handle_line_in_mermaid(line, fence_chars, fence_type, fence_length, state)
|
|
109
|
+
cm = state[:current_mermaid]
|
|
110
|
+
if fence_type == cm[:fence_type] && fence_length == cm[:fence_length] && line.strip == fence_chars
|
|
111
|
+
state[:blocks] << { start: cm[:start], end: state[:position], content: cm[:content_lines].join }
|
|
112
|
+
state[:current_mermaid] = nil
|
|
113
|
+
else
|
|
114
|
+
cm[:content_lines] << line
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Handle fence line at top level (not inside any fence)
|
|
119
|
+
def handle_line_at_top_level(line_start, language, fence_type, fence_length, state)
|
|
120
|
+
if language == "mermaid"
|
|
121
|
+
state[:current_mermaid] = { start: line_start, fence_type: fence_type,
|
|
122
|
+
fence_length: fence_length, content_lines: [] }
|
|
123
|
+
else
|
|
124
|
+
state[:fence_stack].push([fence_length, fence_type])
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Handle fence line while inside a non-mermaid fence
|
|
129
|
+
def handle_line_in_nested_fence(line, fence_type, fence_length, state)
|
|
130
|
+
top_fence_length, top_fence_type = state[:fence_stack].last
|
|
131
|
+
|
|
132
|
+
if fence_type == top_fence_type && fence_length >= top_fence_length && line.strip.match?(/^[`~]+$/)
|
|
133
|
+
state[:fence_stack].pop
|
|
134
|
+
else
|
|
135
|
+
state[:fence_stack].push([fence_length, fence_type])
|
|
136
|
+
end
|
|
52
137
|
end
|
|
53
138
|
end
|
|
54
139
|
end
|