jekyll-kroki 0.6.0 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f2efa9a6bac231d6efdac15ccf493e387cdb1a9890f1b774ddf839b8fb0b599
4
- data.tar.gz: f51d26aa98904cd284f8bea24113f6225fdc5511651c0b7a1d432898f47397fa
3
+ metadata.gz: 7b092bf9850f67c8e4bd7bd0d454aeeaa274b372f5ae8976f6fb05cab3f8372a
4
+ data.tar.gz: 1c8b189b173440b3694156281c41915c204d55b0d35d46bc866a51b0287ea73e
5
5
  SHA512:
6
- metadata.gz: 1b4685d93410a2d8ea9ce9a3c97049f28ea2d4fdfcea144357ae388e0c59af9d93fdadb8c872ea80d7b48510358bd9cfedd980ae72cdfef7f586ce6ac49d915b
7
- data.tar.gz: 3158b7e07f81b144d249161984b0874c48a383187a3c82c564a818864cc3401b9a5938d2b5e3632a1ed3993acb3f356d42861f6b8ed1d014ce109405537b2ef7
6
+ metadata.gz: bc181d7f3288979cb09d8aeae9f915de5099ecbe2b0d6b3c56f941935e3d7ea264051b7fa1c0c850f476930613bb45a2723f623f3b97867b65283e752466f16a
7
+ data.tar.gz: 9e46a87b36b24fc80e2a9349044b87c9c0251f59a8b1060ac498b00bf7a012dbf70415be23752988684ba234de27865526db15ea7552a764d6b4953e5fd7f975
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Ruby",
3
- "image": "mcr.microsoft.com/devcontainers/ruby:4.0-bullseye",
3
+ "image": "mcr.microsoft.com/devcontainers/ruby:4.0-bookworm",
4
4
  "customizations": {
5
5
  "vscode": {
6
6
  "extensions": [
@@ -3,5 +3,9 @@
3
3
  "editor.rulers": [
4
4
  120
5
5
  ],
6
- "rubyLsp.formatter": "rubocop"
6
+ "rubyLsp.formatter": "rubocop",
7
+ "cSpell.language": "en-GB",
8
+ "cSpell.words": [
9
+ "Kroki"
10
+ ]
7
11
  }
data/README.md CHANGED
@@ -45,11 +45,11 @@ Instead of using Liquid tags, `jekyll-kroki` leverages the same Markdown fenced
45
45
 
46
46
  #### Seamless GitLab integration
47
47
 
48
- Self-managed GitLab instances can additionally enable the [Kroki integration](https://docs.gitlab.com/ee/administration/integration/kroki.html), which adds support for all the same diagram scripting languages used by `jekyll-kroki`. Furthermore, by pointing both GitLab and `jekyll-kroki` to the same Kroki instance, you can guarantee that diagrams are generated using identical versions of the diagram libraries.
48
+ Self-managed or dedicated GitLab instances can additionally enable the [Kroki integration](https://docs.gitlab.com/ee/administration/integration/kroki.html), which adds support for all the diagram scripting languages used by `jekyll-kroki`. Furthermore, by pointing both GitLab and `jekyll-kroki` to the same Kroki instance, you can guarantee that diagrams are generated using identical versions of the diagram libraries.
49
49
 
50
50
  #### Speed
51
51
 
52
- The server-side nature of Kroki means that you don't have to deal with installing or updating any diagram library dependencies on your machine. Jekyll sites that are generated in CI/CD pipelines can bypass these steps and will thus build faster.
52
+ The server-side nature of Kroki means that you don't have to deal with installing or updating any diagram library dependencies on your machine. Jekyll sites that are generated in CI/CD pipelines can bypass these setup steps and will thus build faster. `jekyll-kroki` is built with performance in mind to minimise the build time of even the largest sites with thousands of diagrams.
53
53
 
54
54
  #### Flexibility
55
55
 
@@ -78,7 +78,7 @@ kroki:
78
78
 
79
79
  ### Security
80
80
 
81
- Embedding diagrams as SVGs directly within HTML files can be dangerous. You should only use a Kroki instance that you trust (or run your own!). For additional security, you can configure a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) using custom Webrick headers in the Jekyll `_config.yml` file:
81
+ Embedding untrusted SVGs directly in HTML files can introduce security risks. Use a Kroki instance that you trust, or consider hosting your own local/private instance. For added security, you can configure a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) by adding custom Webrick headers in your Jekyll `_config.yml` file:
82
82
 
83
83
  ```yaml
84
84
  webrick:
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  class Kroki
5
- VERSION = "0.6.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
data/lib/jekyll/kroki.rb CHANGED
@@ -18,13 +18,17 @@ require "zlib"
18
18
  module Jekyll
19
19
  # Converts diagram descriptions into images using Kroki.
20
20
  class Kroki
21
+ SUPPORTED_LANGUAGES = %w[actdiag blockdiag bpmn bytefield c4plantuml d2 dbml diagramsnet ditaa erd excalidraw
22
+ graphviz mermaid nomnoml nwdiag packetdiag pikchr plantuml rackdiag seqdiag structurizr
23
+ svgbob symbolator tikz umlet vega vegalite wavedrom wireviz].freeze
21
24
  EXPECTED_HTML_TAGS = %w[code div].freeze
25
+ DIAGRAM_SELECTOR = SUPPORTED_LANGUAGES.flat_map do |language|
26
+ EXPECTED_HTML_TAGS.map { |tag| "#{tag}[class~='language-#{language}']" }
27
+ end.join(", ").freeze
28
+
22
29
  HTTP_RETRY_INTERVAL_BACKOFF_FACTOR = 2
23
30
  HTTP_RETRY_INTERVAL_RANDOMNESS = 0.5
24
31
  HTTP_RETRY_INTERVAL = 0.1
25
- SUPPORTED_LANGUAGES = %w[actdiag blockdiag bpmn bytefield c4plantuml d2 dbml diagramsnet ditaa erd excalidraw
26
- graphviz mermaid nomnoml nwdiag packetdiag pikchr plantuml rackdiag seqdiag structurizr
27
- svgbob symbolator tikz umlet vega vegalite wavedrom wireviz].freeze
28
32
 
29
33
  @diagram_cache = Concurrent::Map.new
30
34
 
@@ -37,11 +41,11 @@ module Jekyll
37
41
  connection = setup_connection(config.kroki_url, config.http_retries, config.http_timeout)
38
42
 
39
43
  rendered_diag = embed_docs_in_site(site, connection, config.max_concurrent_docs)
40
- unless rendered_diag.zero?
41
- puts "[jekyll-kroki] Rendered #{rendered_diag} diagrams using Kroki instance at '#{config.kroki_url}'"
42
- end
43
- rescue StandardError => e
44
- fatal_error(e)
44
+ return unless rendered_diag.positive?
45
+
46
+ Jekyll.logger.info(
47
+ "[jekyll-kroki] Rendered #{rendered_diag} diagrams using Kroki instance at '#{config.kroki_url}'"
48
+ )
45
49
  end
46
50
 
47
51
  # Renders the diagram descriptions in all Jekyll pages and documents in the given Jekyll site. Pages / documents
@@ -52,63 +56,42 @@ module Jekyll
52
56
  # @param [Integer] The maximum number of documents to render concurrently.
53
57
  # @return [Integer] The number of successfully rendered diagrams.
54
58
  def embed_docs_in_site(site, connection, max_concurrent_docs)
55
- rendered_diag = 0
56
59
  semaphore = Async::Semaphore.new(max_concurrent_docs)
57
60
 
58
- Async do |task|
59
- tasks = (site.pages + site.documents).filter_map do |doc|
61
+ Async do
62
+ (site.pages + site.documents).filter_map do |doc|
60
63
  next unless embeddable?(doc)
61
64
 
62
- async_embed_single_doc(task, semaphore, connection, doc)
63
- end
64
-
65
- rendered_diag = tasks.sum(&:wait)
66
- end
67
-
68
- rendered_diag
69
- end
70
-
71
- # Renders the supported diagram descriptions in a single document. Multiple documents can be rendered concurrently
72
- # up to the limit imposed by the given semaphore.
73
- #
74
- # @param [Async::Task] The parent async task to spawn a child task from.
75
- # @param [Async::Semaphore] A semaphore to limit concurrency.
76
- # @param [Faraday::Connection] The Faraday connection to use.
77
- # @param [Jekyll::Page, Jekyll::Document] The document to process.
78
- # @return [Integer] The number of successfully rendered diagrams.
79
- def async_embed_single_doc(task, semaphore, connection, doc)
80
- task.async do
81
- semaphore.async { embed_single_doc(connection, doc) }.wait
82
- rescue StandardError => e
83
- warn "[jekyll-kroki] Error rendering diagram: #{e.message}".red
84
- 0
85
- end
65
+ semaphore.async do
66
+ embed_single_doc(connection, doc)
67
+ rescue StandardError => e
68
+ Jekyll.logger.error "[jekyll-kroki] Failed to render diagram in '#{doc.relative_path}': #{e.message}"
69
+ 0
70
+ end
71
+ end.sum(&:wait)
72
+ end.wait
86
73
  end
87
74
 
88
75
  # Renders the supported diagram descriptions in a single document sequentially and embeds them as inline SVGs in
89
- # the HTML source.
76
+ # the HTML source. Returns without modifying the document if no supported diagram descriptions are found.
90
77
  #
91
78
  # @param [Faraday::Connection] The Faraday connection to use.
92
79
  # @param [Jekyll::Page, Jekyll::Document] The document to process.
93
80
  # @return [Integer] The number of successfully rendered diagrams.
94
81
  def embed_single_doc(connection, doc)
95
- # Parse the HTML document.
96
82
  parsed_doc = Nokogiri::HTML(doc.output)
83
+ nodes = parsed_doc.css(DIAGRAM_SELECTOR)
84
+ return 0 if nodes.empty?
97
85
 
98
- rendered_diag = 0
99
- SUPPORTED_LANGUAGES.each do |language|
100
- EXPECTED_HTML_TAGS.each do |tag|
101
- parsed_doc.css("#{tag}[class~='language-#{language}']").each do |diagram_desc|
102
- # Replace the diagram description with the SVG representation rendered by Kroki.
103
- diagram_desc.replace(render_diagram(connection, diagram_desc, language))
104
- rendered_diag += 1
105
- end
106
- end
86
+ nodes.each do |node|
87
+ # Extract the diagram language from the class list.
88
+ language = node["class"].split.grep(/\Alanguage-/).first.delete_prefix("language-")
89
+ node.replace(render_diagram(connection, node.text, language))
107
90
  end
108
91
 
109
92
  # Convert the document back to HTML.
110
93
  doc.output = parsed_doc.to_html
111
- rendered_diag
94
+ nodes.size
112
95
  end
113
96
 
114
97
  # Renders a single diagram description using Kroki. The rendered diagram is cached to avoid redundant HTTP
@@ -118,17 +101,15 @@ module Jekyll
118
101
  # @param [String] The diagram description.
119
102
  # @param [String] The language of the diagram description.
120
103
  # @return [String] The rendered diagram in SVG format.
121
- def render_diagram(connection, diagram_desc, language)
122
- diagram_text = diagram_desc.text
104
+ def render_diagram(connection, diagram_text, language)
123
105
  cache_key = "#{language}:#{Digest::SHA1.hexdigest(diagram_text)}"
124
106
  @diagram_cache.compute_if_absent(cache_key) do
125
- begin
126
- response = connection.get("#{language}/svg/#{encode_diagram(diagram_text)}")
127
- rescue Faraday::Error => e
128
- raise e.message
129
- end
107
+ response = connection.get("#{language}/svg/#{encode_diagram(diagram_text)}")
130
108
  validate_content_type(response)
131
109
  sanitise_diagram(response.body)
110
+ rescue Faraday::BadRequestError => e
111
+ kroki_message = e.response_body.to_s.strip
112
+ raise e, (kroki_message.empty? ? e.message : kroki_message)
132
113
  end
133
114
  end
134
115
 
@@ -140,14 +121,14 @@ module Jekyll
140
121
  returned_content_type = response.headers[:content_type]
141
122
  return if returned_content_type == expected_content_type
142
123
 
143
- raise "Kroki returned an incorrect content type: " \
124
+ raise "[jekyll-kroki] Kroki returned an incorrect content type: " \
144
125
  "expected '#{expected_content_type}', received '#{returned_content_type}'"
145
126
  end
146
127
 
147
128
  # Sanitises a rendered diagram. Only <script> elements are removed, which is the most minimal / naive
148
129
  # implementation possible.
149
130
  #
150
- # @param [String] The diagram to santise in SVG format.
131
+ # @param [String] The diagram to sanitise in SVG format.
151
132
  # @return [String] The sanitised diagram.
152
133
  def sanitise_diagram(diagram_svg)
153
134
  parsed_svg = Nokogiri::XML(diagram_svg)
@@ -158,10 +139,10 @@ module Jekyll
158
139
  # Encodes the diagram into Kroki format using deflate + base64.
159
140
  # See https://docs.kroki.io/kroki/setup/encode-diagram/.
160
141
  #
161
- # @param [String, #read] The diagram description to encode.
142
+ # @param [String] The diagram description to encode.
162
143
  # @return [String] The encoded diagram.
163
144
  def encode_diagram(diagram_desc)
164
- Base64.urlsafe_encode64(Zlib.deflate(diagram_desc))
145
+ Base64.urlsafe_encode64(Zlib.deflate(diagram_desc, Zlib::BEST_COMPRESSION))
165
146
  end
166
147
 
167
148
  # Sets up a new Faraday connection.
@@ -191,27 +172,13 @@ module Jekyll
191
172
  def embeddable?(doc)
192
173
  doc.output_ext == ".html" && (doc.is_a?(Jekyll::Page) || doc.write?)
193
174
  end
194
-
195
- # Exits the Jekyll process without returning a stack trace. This method does not return because the process is
196
- # abruptly terminated.
197
- #
198
- # @param [StandardError] The error to display in the termination message.
199
- # @param [int] The caller index to display in the termination message. The default index is 1, which means the
200
- # calling method. To specify the calling method's caller, pass in 2.
201
- #
202
- # Source: https://www.mslinn.com/ruby/2200-crash-exit.html
203
- def fatal_error(error, caller_index = 1)
204
- raise error
205
- rescue StandardError => e
206
- file, line_number, caller = e.backtrace[caller_index].split(":")
207
- caller = caller.tr("", "'")
208
- warn %([jekyll-kroki] "#{error.message}" #{caller} on line #{line_number} of #{file}).red
209
- exec "exit 1"
210
- end
211
175
  end
212
176
  end
213
177
  end
214
178
 
215
179
  Jekyll::Hooks.register :site, :post_render do |site|
216
180
  Jekyll::Kroki.embed_site(site)
181
+ rescue StandardError => e
182
+ Jekyll.logger.error "[jekyll-kroki] #{e.class}: #{e.message}"
183
+ raise
217
184
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-kroki
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix van Oost
@@ -227,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
227
  - !ruby/object:Gem::Version
228
228
  version: '0'
229
229
  requirements: []
230
- rubygems_version: 3.6.9
230
+ rubygems_version: 4.0.10
231
231
  specification_version: 4
232
232
  summary: A Jekyll plugin to convert diagram descriptions into images using Kroki
233
233
  test_files: []