Skip to content

Commit

Permalink
Handle HTTP retries (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
felixvanoost authored Oct 22, 2023
1 parent 88cd70b commit f7ae4ac
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 81 deletions.
37 changes: 0 additions & 37 deletions .rubocop_todo.yml

This file was deleted.

10 changes: 10 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ PATH
remote: .
specs:
jekyll-kroki (0.1.0)
faraday (~> 2.7)
faraday-retry (~> 2.2)
jekyll (~> 4)
nokogiri (~> 1.15)

Expand All @@ -18,6 +20,13 @@ GEM
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
faraday (2.7.11)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
faraday-retry (2.2.0)
faraday (~> 2.0)
ffi (1.16.3)
forwardable-extended (2.6.0)
google-protobuf (3.24.4-x86_64-linux)
Expand Down Expand Up @@ -89,6 +98,7 @@ GEM
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
safe_yaml (1.0.5)
sass-embedded (1.69.4)
google-protobuf (~> 3.23)
Expand Down
6 changes: 4 additions & 2 deletions jekyll-kroki.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
spec.authors = ["Felix van Oost"]

spec.summary = "A Jekyll plugin to convert diagram descriptions into images using Kroki"
spec.description = "Replaces diagram descriptions written in any Kroki-supported language in HTML files with their
visual representation in SVG format"
spec.description = "Replaces diagram descriptions written in any Kroki-supported language in HTML files generated by
Jekyll with their visual representation in SVG format."
spec.homepage = "https://github.com/felixvanoost/jekyll-kroki"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.6.0"
Expand All @@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_runtime_dependency "faraday", ["~> 2.7"]
spec.add_runtime_dependency "faraday-retry", ["~> 2.2"]
spec.add_runtime_dependency "jekyll", ["~> 4"]
spec.add_runtime_dependency "nokogiri", ["~> 1.15"]

Expand Down
99 changes: 57 additions & 42 deletions lib/jekyll/kroki.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,73 @@
require_relative "kroki/version"

require "base64"
require "faraday"
require "faraday/retry"
require "jekyll"
require "json"
require "net/http"
require "nokogiri"
require "zlib"

module Jekyll
# Converts diagram descriptions into images using Kroki
class Kroki
KROKI_DEFAULT_URL = "https://kroki.io"
HTTP_MAX_RETRIES = 3

class << self
# Renders all diagram descriptions written in a Kroki-supported language in an HTML document.
#
# @param [Jekyll::Page or Jekyll::Document] The document to render diagrams in
def render(doc)
# @param [Jekyll::Page or Jekyll::Document] The document to embed diagrams in
def embed(doc)
# Get the URL of the Kroki instance
kroki_url = kroki_url(doc.site.config)
puts "[jekyll-kroki] Rendering diagrams in '#{doc.name}' using '#{kroki_url}'"
puts "[jekyll-kroki] Rendering diagrams in '#{doc.name}' using Kroki instance '#{kroki_url}'"

# Parse the HTML document
parsed_doc = Nokogiri::HTML.parse(doc.output)
# Set up a Faraday connection
connection = setup_connection(kroki_url)

# Parse the HTML document, render and embed the diagrams, then convert it back into HTML
parsed_doc = Nokogiri::HTML(doc.output)
embed_diagrams_in_doc(connection, parsed_doc)
doc.output = parsed_doc.to_html
end

# Renders all diagram descriptions in any Kroki-supported language and embeds them in an HTML document.
#
# @param [Faraday::Connection] The Faraday connection to use
# @param [Nokogiri::HTML4::Document] The parsed HTML document
def embed_diagrams_in_doc(connection, parsed_doc)
# Iterate through every diagram description in each of the supported languages
get_supported_languages(kroki_url).each do |language|
get_supported_languages(connection).each do |language|
parsed_doc.css("code[class~='language-#{language}']").each do |diagram_desc|
# Get the rendered diagram using Kroki
rendered_diagram = render_diagram(kroki_url, diagram_desc, language)

# Replace the diagram description with the SVG representation
diagram_desc.replace(rendered_diagram)
# Replace the diagram description with the SVG representation rendered by Kroki
diagram_desc.replace(render_diagram(connection, diagram_desc, language))
end
end

# Generate the modified HTML document
doc.output = parsed_doc.to_html
end

# Renders a diagram description using Kroki.
#
# @param [String] The URL of the Kroki instance
# @param [Faraday::Connection] The Faraday connection to use
# @param [String] The diagram description
# @param [String] The language of the diagram description
# @return [String] The rendered diagram in SVG format
def render_diagram(kroki_url, diagram_desc, language)
# Encode the diagram and construct the URI
uri = URI("#{kroki_url}/#{language}/svg/#{encode_diagram(diagram_desc.text)}")

def render_diagram(connection, diagram_desc, language)
begin
response = Net::HTTP.get_response(uri)
rescue StandardError => e
raise e.message
else
response.body if response.is_a?(Net::HTTPSuccess)
encoded_diagram = encode_diagram(diagram_desc.text)
response = connection.get("#{language}/svg/#{encoded_diagram}")
rescue Faraday::Error => e
raise e.response[:body]
end
response.body
end

# Encodes the diagram into Kroki format using deflate + base64.
# See https://docs.kroki.io/kroki/setup/encode-diagram/.
#
# @param [String, #read] The diagram to encode
# @param [String, #read] The diagram description to encode
# @return [String] The encoded diagram
def encode_diagram(diagram)
Base64.urlsafe_encode64(Zlib.deflate(diagram))
def encode_diagram(diagram_desc)
Base64.urlsafe_encode64(Zlib.deflate(diagram_desc))
end

# Gets an array of supported diagram languages from the Kroki '/health' endpoint.
Expand All @@ -75,17 +78,29 @@ def encode_diagram(diagram)
# configured Kroki instance. For example, Mermaid will still show up as a supported language even if the Mermaid
# companion container is not running.
#
# @param [String] The URL of the Kroki instance
# @param [Faraday::Connection] The Faraday connection to use
# @return [Array] The supported diagram languages
def get_supported_languages(kroki_url)
uri = URI("#{kroki_url}/health")

def get_supported_languages(connection)
begin
response = Net::HTTP.get_response(uri)
rescue StandardError => e
raise e.message
else
JSON.parse(response.body)["version"].keys if response.is_a?(Net::HTTPSuccess)
response = connection.get("health")
rescue Faraday::Error => e
raise e.response[:body]
end
response.body["version"].keys
end

# Sets up a new Faraday connection.
#
# @param [URI::HTTP] The URL of the Kroki instance
# @return [Faraday::Connection] The Faraday connection
def setup_connection(kroki_url)
retry_options = { max: HTTP_MAX_RETRIES, interval: 0.1, interval_randomness: 0.5, backoff_factor: 2,
exceptions: [Faraday::RequestTimeoutError, Faraday::ServerError] }

Faraday.new(url: kroki_url) do |builder|
builder.request :retry, retry_options
builder.response :json, content_type: /\bjson$/
builder.response :raise_error
end
end

Expand All @@ -104,17 +119,17 @@ def kroki_url(config)
end
end

# Determines whether a document may contain renderable diagram descriptions - it is in HTML format and is either
# Determines whether a document may contain embeddable diagram descriptions - it is in HTML format and is either
# a Jekyll::Page or writeable Jekyll::Document.
#
# @param [Jekyll::Page or Jekyll::Document] The document to check for renderability
def renderable?(doc)
# @param [Jekyll::Page or Jekyll::Document] The document to check for embedability
def embeddable?(doc)
doc.output_ext == ".html" && (doc.is_a?(Jekyll::Page) || doc.write?)
end
end
end
end

Jekyll::Hooks.register [:pages, :documents], :post_render do |doc|
Jekyll::Kroki.render(doc) if Jekyll::Kroki.renderable?(doc)
Jekyll::Kroki.embed(doc) if Jekyll::Kroki.embeddable?(doc)
end

0 comments on commit f7ae4ac

Please sign in to comment.