jekyll-kroki 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20faf867e0d7257dc109beb7013daffedbed2d90642e3b483d1be42a62220008
4
- data.tar.gz: b248eeb60615d9343256b79b1524abe5253c5b02c2f1fa01741f3f2d914cef62
3
+ metadata.gz: d4c40ec016cd09c83ced71e8ead7559ab2fee1224432efc5bdefa366e363275a
4
+ data.tar.gz: 4341c9a5fa9891a0397ed167b7c56cc453e77ff8d00aef210819f982205431a5
5
5
  SHA512:
6
- metadata.gz: 1aa3042410e6a16d5bc1e5519a3f7e1043611d22d404ed7856c943eba4a29dd36d5bab5f0a2a732c9cce77231927855bf464211d7a1ddfe58c54b4e3ed02a448
7
- data.tar.gz: e2b86eed2875b0d846955222023102cbd215bbeebe68247debff8f56af2cbe328c28d60c974e9b321927178e09361758172553dcc863404068eaabb574a822a3
6
+ metadata.gz: 19733f768a5f025ece0eb647a1f3e5f9ef553c1b27aa0b5b0d716d4b329c303af2f77971c5d64d63db2d5be344b834e7f9abc7e6993202bb900206ed49894c6f
7
+ data.tar.gz: 2df9e238f430dee8787a9fbb96d8ba7b48bf18918c144b7630f9e80baebdff7b07d263956a123a6b9e8d268c05793532634e62b997f8067574badefbc5d0e227
data/.rubocop.yml CHANGED
@@ -11,3 +11,5 @@ Style/StringLiteralsInInterpolation:
11
11
 
12
12
  Layout/LineLength:
13
13
  Max: 120
14
+ Metrics/MethodLength:
15
+ Max: 12
data/README.md CHANGED
@@ -29,12 +29,33 @@ Kroki --> Jekyll: Rendered diagram in SVG format
29
29
 
30
30
  When Jekyll builds your site, the `jekyll-kroki` plugin will encode the diagram, send it to the Kroki server for rendering, then replace the diagram description in the generated HTML with the rendered diagram in SVG format:
31
31
 
32
- ![sample-diagram](https://github.com/felixvanoost/jekyll-kroki/assets/10233016/e526864f-c364-49a0-9cca-929a59343af0)
32
+ ![sample-diagram](https://github.com/felixvanoost/jekyll-kroki/assets/10233016/244d2ec4-b09b-4a5f-8164-3851574c3dd2)
33
33
 
34
34
  The site remains truly static as the SVG is directly embedded in the HTML files that Jekyll serves. Jekyll only depends on the Kroki server (which can also be run locally) during the build stage, and all of the client-side processing that is normally used to render diagrams into images is eliminated.
35
35
 
36
36
  `jekyll-kroki` uses the same Markdown fenced code syntax as the [GitLab Kroki integration](https://docs.gitlab.com/ee/administration/integration/kroki.html), allowing diagram descriptions in Markdown files to be displayed seamlessly in both the GitLab UI and on GitLab Pages sites generated using Jekyll.
37
37
 
38
+ ### Configuration
39
+
40
+ You can specify the URL of the Kroki instance to use in the Jekyll `_config.yml` file:
41
+
42
+ ```yaml
43
+ jekyll-kroki:
44
+ kroki_url: "https://my-kroki.server"
45
+ ```
46
+
47
+ This is useful if you want to run a Kroki instance locally or your organisation maintains its own private Kroki server. `jekyll-kroki` will use the public Kroki instance https://kroki.io by default if a URL is not specified.
48
+
49
+ ### Security
50
+
51
+ 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:
52
+
53
+ ```yaml
54
+ webrick:
55
+ headers:
56
+ Content-Security-Policy: "Add a policy here"
57
+ ```
58
+
38
59
  ## Contributing
39
60
 
40
61
  Bug reports and pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
data/jekyll-kroki.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Felix van Oost"]
9
9
 
10
10
  spec.summary = "A Jekyll plugin to convert diagram descriptions into images using Kroki"
11
- spec.description = "Replaces diagram descriptions written in any Kroki-supported language in HTML files with their
12
- visual representation in SVG format"
11
+ spec.description = "Replaces diagram descriptions written in any Kroki-supported language in HTML files generated by
12
+ Jekyll with their visual representation in SVG format."
13
13
  spec.homepage = "https://github.com/felixvanoost/jekyll-kroki"
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = ">= 2.6.0"
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
+ spec.add_runtime_dependency "faraday", ["~> 2.7"]
32
+ spec.add_runtime_dependency "faraday-retry", ["~> 2.2"]
31
33
  spec.add_runtime_dependency "jekyll", ["~> 4"]
32
34
  spec.add_runtime_dependency "nokogiri", ["~> 1.15"]
33
35
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  class Kroki
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
data/lib/jekyll/kroki.rb CHANGED
@@ -3,68 +3,94 @@
3
3
  require_relative "kroki/version"
4
4
 
5
5
  require "base64"
6
+ require "faraday"
7
+ require "faraday/retry"
6
8
  require "jekyll"
7
- require "json"
8
- require "net/http"
9
9
  require "nokogiri"
10
10
  require "zlib"
11
11
 
12
12
  module Jekyll
13
- # Jekyll plugin for the Kroki diagram engine
13
+ # Converts diagram descriptions into images using Kroki
14
14
  class Kroki
15
- KROKI_INSTANCE_URL = "https://kroki.io"
15
+ KROKI_DEFAULT_URL = "https://kroki.io"
16
+ HTTP_MAX_RETRIES = 3
16
17
 
17
18
  class << self
18
- # Renders all diagram descriptions written in a Kroki-supported language in an HTML document.
19
+ # Renders all diagram descriptions in any Kroki-supported language and embeds them throughout a Jekyll site.
19
20
  #
20
- # @param [Jekyll::Page or Jekyll::Document] The document to render diagrams in
21
- def render(doc)
22
- puts "Rendering diagrams using Kroki"
21
+ # @param [Jekyll::Site] The site to embed diagrams in
22
+ def embed_site(site)
23
+ # Get the URL of the Kroki instance
24
+ kroki_url = kroki_url(site.config)
25
+ puts "[jekyll-kroki] Rendering diagrams using Kroki instance at '#{kroki_url}'"
23
26
 
24
- # Parse the HTML document
25
- parsed_doc = Nokogiri::HTML.parse(doc.output)
27
+ # Set up a Faraday connection
28
+ connection = setup_connection(kroki_url)
26
29
 
30
+ # Parse the page, render and embed the diagrams, then convert it back into HTML
31
+ site.pages.each do |page|
32
+ next unless embeddable?(page)
33
+
34
+ parsed_page = Nokogiri::HTML(page.output)
35
+ embed_page(connection, parsed_page)
36
+ page.output = parsed_page.to_html
37
+ end
38
+ end
39
+
40
+ # Renders all diagram descriptions in any Kroki-supported language and embeds them in an HTML document.
41
+ #
42
+ # @param [Faraday::Connection] The Faraday connection to use
43
+ # @param [Nokogiri::HTML4::Document] The parsed HTML document
44
+ def embed_page(connection, parsed_doc)
27
45
  # Iterate through every diagram description in each of the supported languages
28
- get_supported_languages(KROKI_INSTANCE_URL).each do |language|
46
+ get_supported_languages(connection).each do |language|
29
47
  parsed_doc.css("code[class~='language-#{language}']").each do |diagram_desc|
30
- # Get the rendered diagram using Kroki
31
- rendered_diagram = render_diagram(KROKI_INSTANCE_URL, diagram_desc, language)
32
-
33
- # Replace the diagram description with the SVG representation
34
- diagram_desc.replace(rendered_diagram)
48
+ # Replace the diagram description with the SVG representation rendered by Kroki
49
+ diagram_desc.replace(render_diagram(connection, diagram_desc, language))
35
50
  end
36
51
  end
37
-
38
- # Generate the modified HTML document
39
- doc.output = parsed_doc.to_html
40
52
  end
41
53
 
42
54
  # Renders a diagram description using Kroki.
43
55
  #
44
- # @param [String] The URL of the Kroki instance
56
+ # @param [Faraday::Connection] The Faraday connection to use
45
57
  # @param [String] The diagram description
46
58
  # @param [String] The language of the diagram description
47
59
  # @return [String] The rendered diagram in SVG format
48
- def render_diagram(kroki_url, diagram_desc, language)
49
- # Encode the diagram and construct the URI
50
- uri = URI("#{kroki_url}/#{language}/svg/#{encode_diagram(diagram_desc.text)}")
51
-
60
+ def render_diagram(connection, diagram_desc, language)
52
61
  begin
53
- response = Net::HTTP.get_response(uri)
54
- rescue StandardError => e
55
- raise e.message
56
- else
57
- response.body if response.is_a?(Net::HTTPSuccess)
62
+ response = connection.get("#{language}/svg/#{encode_diagram(diagram_desc.text)}")
63
+ rescue Faraday::Error => e
64
+ raise e.response[:body]
65
+ end
66
+ expected_content_type = "image/svg+xml"
67
+ returned_content_type = response.headers[:content_type]
68
+ if returned_content_type != expected_content_type
69
+ raise "Kroki returned an incorrect content type: " \
70
+ "expected '#{expected_content_type}', received '#{returned_content_type}'"
71
+
58
72
  end
73
+ sanitise_diagram(response.body)
74
+ end
75
+
76
+ # Sanitises a rendered diagram. Only <script> elements are removed, which is the most minimal / naive
77
+ # implementation possible and is definitely not secure.
78
+ #
79
+ # @param [String] The diagram to santise in SVG format
80
+ # @return [String] The sanitized diagram in SVG format
81
+ def sanitise_diagram(diagram_svg)
82
+ parsed_svg = Nokogiri::XML(diagram_svg)
83
+ parsed_svg.xpath('//*[name()="script"]').each(&:remove)
84
+ parsed_svg.to_xml
59
85
  end
60
86
 
61
87
  # Encodes the diagram into Kroki format using deflate + base64.
62
88
  # See https://docs.kroki.io/kroki/setup/encode-diagram/.
63
89
  #
64
- # @param [String, #read] The diagram to encode
90
+ # @param [String, #read] The diagram description to encode
65
91
  # @return [String] The encoded diagram
66
- def encode_diagram(diagram)
67
- Base64.urlsafe_encode64(Zlib.deflate(diagram))
92
+ def encode_diagram(diagram_desc)
93
+ Base64.urlsafe_encode64(Zlib.deflate(diagram_desc))
68
94
  end
69
95
 
70
96
  # Gets an array of supported diagram languages from the Kroki '/health' endpoint.
@@ -73,31 +99,58 @@ module Jekyll
73
99
  # configured Kroki instance. For example, Mermaid will still show up as a supported language even if the Mermaid
74
100
  # companion container is not running.
75
101
  #
76
- # @param [String] The URL of the Kroki instance
102
+ # @param [Faraday::Connection] The Faraday connection to use
77
103
  # @return [Array] The supported diagram languages
78
- def get_supported_languages(kroki_url)
79
- uri = URI("#{kroki_url}/health")
80
-
104
+ def get_supported_languages(connection)
81
105
  begin
82
- response = Net::HTTP.get_response(uri)
83
- rescue StandardError => e
84
- raise e.message
106
+ response = connection.get("health")
107
+ rescue Faraday::Error => e
108
+ raise e.response[:body]
109
+ end
110
+ response.body["version"].keys
111
+ end
112
+
113
+ # Sets up a new Faraday connection.
114
+ #
115
+ # @param [URI::HTTP] The URL of the Kroki instance
116
+ # @return [Faraday::Connection] The Faraday connection
117
+ def setup_connection(kroki_url)
118
+ retry_options = { max: HTTP_MAX_RETRIES, interval: 0.1, interval_randomness: 0.5, backoff_factor: 2,
119
+ exceptions: [Faraday::RequestTimeoutError, Faraday::ServerError] }
120
+
121
+ Faraday.new(url: kroki_url) do |builder|
122
+ builder.request :retry, retry_options
123
+ builder.response :json, content_type: /\bjson$/
124
+ builder.response :raise_error
125
+ end
126
+ end
127
+
128
+ # Gets the URL of the Kroki instance to use for rendering diagrams.
129
+ #
130
+ # @param The Jekyll site configuration
131
+ # @return [URI::HTTP] The URL of the Kroki instance
132
+ def kroki_url(config)
133
+ if config.key?("jekyll-kroki") && config["jekyll-kroki"].key?("kroki_url")
134
+ url = config["jekyll-kroki"]["kroki_url"]
135
+ raise TypeError, "'kroki_url' is not a valid HTTP URL" unless URI.parse(url).is_a?(URI::HTTP)
136
+
137
+ URI(url)
85
138
  else
86
- JSON.parse(response.body)["version"].keys if response.is_a?(Net::HTTPSuccess)
139
+ URI(KROKI_DEFAULT_URL)
87
140
  end
88
141
  end
89
142
 
90
- # Determines whether a document may contain renderable diagram descriptions - it is in HTML format and is either
143
+ # Determines whether a document may contain embeddable diagram descriptions - it is in HTML format and is either
91
144
  # a Jekyll::Page or writeable Jekyll::Document.
92
145
  #
93
- # @param [Jekyll::Page or Jekyll::Document] The document to check for renderability
94
- def renderable?(doc)
146
+ # @param [Jekyll::Page or Jekyll::Document] The document to check for embedability
147
+ def embeddable?(doc)
95
148
  doc.output_ext == ".html" && (doc.is_a?(Jekyll::Page) || doc.write?)
96
149
  end
97
150
  end
98
151
  end
99
152
  end
100
153
 
101
- Jekyll::Hooks.register [:pages, :documents], :post_render do |doc|
102
- Jekyll::Kroki.render(doc) if Jekyll::Kroki.renderable?(doc)
154
+ Jekyll::Hooks.register :site, :post_render do |doc|
155
+ Jekyll::Kroki.embed_site(doc)
103
156
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-kroki
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix van Oost
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-20 00:00:00.000000000 Z
11
+ date: 2023-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-retry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: jekyll
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -81,15 +109,14 @@ dependencies:
81
109
  - !ruby/object:Gem::Version
82
110
  version: '1.21'
83
111
  description: |-
84
- Replaces diagram descriptions written in any Kroki-supported language in HTML files with their
85
- visual representation in SVG format
112
+ Replaces diagram descriptions written in any Kroki-supported language in HTML files generated by
113
+ Jekyll with their visual representation in SVG format.
86
114
  email:
87
115
  executables: []
88
116
  extensions: []
89
117
  extra_rdoc_files: []
90
118
  files:
91
119
  - ".rubocop.yml"
92
- - ".rubocop_todo.yml"
93
120
  - LICENSE
94
121
  - README.md
95
122
  - Rakefile
data/.rubocop_todo.yml DELETED
@@ -1,37 +0,0 @@
1
- # This configuration was generated by
2
- # `rubocop --auto-gen-config`
3
- # on 2023-10-18 03:15:03 UTC using RuboCop version 1.57.1.
4
- # The point is for the user to remove these configuration records
5
- # one by one as the offenses are removed from the code base.
6
- # Note that changes in the inspected code, or installation of new
7
- # versions of RuboCop, may require this file to be generated again.
8
-
9
- # Offense count: 4
10
- Lint/ShadowedException:
11
- Exclude:
12
- - 'lib/jekyll-kroki.rb'
13
- - 'site/_plugins/jekyll-kroki.rb'
14
-
15
- # Offense count: 2
16
- Naming/AccessorMethodName:
17
- Exclude:
18
- - 'lib/jekyll-kroki.rb'
19
- - 'site/_plugins/jekyll-kroki.rb'
20
-
21
- # Offense count: 2
22
- # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
23
- # CheckDefinitionPathHierarchyRoots: lib, spec, test, src
24
- # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
25
- Naming/FileName:
26
- Exclude:
27
- - 'lib/jekyll-kroki.rb'
28
- - 'site/_plugins/jekyll-kroki.rb'
29
-
30
- # Offense count: 2
31
- # Configuration parameters: AllowedConstants.
32
- Style/Documentation:
33
- Exclude:
34
- - 'spec/**/*'
35
- - 'test/**/*'
36
- - 'lib/jekyll-kroki.rb'
37
- - 'site/_plugins/jekyll-kroki.rb'