asciidoctor-kroki-0.5.0/ 0000755 0000041 0000041 00000000000 14101232272 015156 5 ustar www-data www-data asciidoctor-kroki-0.5.0/Gemfile.lock 0000644 0000041 0000041 00000002113 14101232272 017375 0 ustar www-data www-data PATH
remote: .
specs:
asciidoctor-kroki (0.5.0)
asciidoctor (~> 2.0)
GEM
remote: https://rubygems.org/
specs:
asciidoctor (2.0.11)
ast (2.4.1)
diff-lcs (1.3)
jaro_winkler (1.5.4)
parallel (1.19.1)
parser (2.7.1.3)
ast (~> 2.4.0)
rainbow (3.0.0)
rake (12.3.3)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.3)
rubocop (0.74.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.6)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
ruby-progressbar (1.10.1)
unicode-display_width (1.6.1)
PLATFORMS
ruby
DEPENDENCIES
asciidoctor-kroki!
rake (~> 12.3.2)
rspec (~> 3.8.0)
rubocop (~> 0.74.0)
BUNDLED WITH
2.2.17
asciidoctor-kroki-0.5.0/tasks/ 0000755 0000041 0000041 00000000000 14101232272 016303 5 ustar www-data www-data asciidoctor-kroki-0.5.0/tasks/lint.rake 0000644 0000041 0000041 00000000130 14101232272 020107 0 ustar www-data www-data # frozen_string_literal: true
require 'rubocop/rake_task'
RuboCop::RakeTask.new :lint
asciidoctor-kroki-0.5.0/tasks/bundler.rake 0000644 0000041 0000041 00000000163 14101232272 020602 0 ustar www-data www-data # frozen_string_literal: true
begin
require 'bundler/gem_tasks'
rescue LoadError
warn $ERROR_INFO.message
end
asciidoctor-kroki-0.5.0/tasks/rspec.rake 0000644 0000041 0000041 00000000230 14101232272 020256 0 ustar www-data www-data # frozen_string_literal: true
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new :spec
rescue LoadError
warn $ERROR_INFO.message
end
asciidoctor-kroki-0.5.0/spec/ 0000755 0000041 0000041 00000000000 14101232272 016110 5 ustar www-data www-data asciidoctor-kroki-0.5.0/spec/asciidoctor_kroki_diagram_spec.rb 0000644 0000041 0000041 00000006355 14101232272 024646 0 ustar www-data www-data # frozen_string_literal: true
require 'rspec_helper'
require 'asciidoctor'
require_relative '../lib/asciidoctor/extensions/asciidoctor_kroki'
describe ::AsciidoctorExtensions::KrokiDiagram do
it 'should compute a diagram URI' do
kroki_diagram = ::AsciidoctorExtensions::KrokiDiagram.new('vegalite', 'png', '{}')
diagram_uri = kroki_diagram.get_diagram_uri('http://localhost:8000')
expect(diagram_uri).to eq('http://localhost:8000/vegalite/png/eNqrrgUAAXUA-Q==')
end
it 'should compute a diagram URI with a trailing slashes' do
kroki_diagram = ::AsciidoctorExtensions::KrokiDiagram.new('vegalite', 'png', '{}')
diagram_uri = kroki_diagram.get_diagram_uri('https://my.domain.org/kroki/')
expect(diagram_uri).to eq('https://my.domain.org/kroki/vegalite/png/eNqrrgUAAXUA-Q==')
end
it 'should compute a diagram URI with trailing slashes' do
kroki_diagram = ::AsciidoctorExtensions::KrokiDiagram.new('vegalite', 'png', '{}')
diagram_uri = kroki_diagram.get_diagram_uri('https://my-server/kroki//')
expect(diagram_uri).to eq('https://my-server/kroki/vegalite/png/eNqrrgUAAXUA-Q==')
end
it 'should encode a diagram text definition' do
kroki_diagram = ::AsciidoctorExtensions::KrokiDiagram.new('plantuml', 'txt', ' alice -> bob: hello')
diagram_definition_encoded = kroki_diagram.encode
expect(diagram_definition_encoded).to eq('eNpTSMzJTE5V0LVTSMpPslLISM3JyQcAQAwGaw==')
end
it 'should fetch a diagram from Kroki and save it to disk' do
kroki_diagram = ::AsciidoctorExtensions::KrokiDiagram.new('plantuml', 'txt', ' alice -> bob: hello')
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('https://kroki.io', 'get', kroki_http_client)
output_dir_path = "#{__dir__}/../.asciidoctor/kroki"
diagram_name = kroki_diagram.save(output_dir_path, kroki_client)
diagram_path = File.join(output_dir_path, diagram_name)
expect(File.exist?(diagram_path)).to be_truthy, "expected diagram to be saved at #{diagram_path}"
content = <<-TXT.chomp
,-----. ,---.
|alice| |bob|
`--+--' `-+-'
| hello |
|-------------->|
,--+--. ,-+-.
|alice| |bob|
`-----' `---'
TXT
expect(File.read(diagram_path).split("\n").map(&:rstrip).join("\n")).to eq(content)
end
it 'should fetch a diagram from Kroki with the same definition only once' do
kroki_diagram = ::AsciidoctorExtensions::KrokiDiagram.new('plantuml', 'png', ' guillaume -> dan: hello')
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('https://kroki.io', 'get', kroki_http_client)
output_dir_path = "#{__dir__}/../.asciidoctor/kroki"
# make sure that we are doing only one GET request
expect(kroki_http_client).to receive(:get).once
diagram_name = kroki_diagram.save(output_dir_path, kroki_client)
diagram_path = File.join(output_dir_path, diagram_name)
expect(File.exist?(diagram_path)).to be_truthy, "expected diagram to be saved at #{diagram_path}"
# calling again... should read the file from disk (and not do a GET request)
kroki_diagram.save(output_dir_path, kroki_client)
end
end
asciidoctor-kroki-0.5.0/spec/asciidoctor_kroki_spec.rb 0000644 0000041 0000041 00000014254 14101232272 023157 0 ustar www-data www-data # frozen_string_literal: true
require 'rspec_helper'
require 'asciidoctor'
require_relative '../lib/asciidoctor/extensions/asciidoctor_kroki'
describe ::AsciidoctorExtensions::KrokiBlockProcessor do
context 'convert to html5' do
it 'should convert a PlantUML block to an image' do
input = <<~'ADOC'
[plantuml]
....
alice -> bob: hello
....
ADOC
output = Asciidoctor.convert(input, standalone: false)
(expect output).to eql %(
)
end
it 'should use png if env-idea is defined' do
input = <<~'ADOC'
[plantuml]
....
alice -> bob: hello
....
ADOC
output = Asciidoctor.convert(input, attributes: { 'env-idea' => '' }, standalone: false)
(expect output).to eql %()
end
it 'should include the plantuml-include file when safe mode is safe' do
input = <<~'ADOC'
[plantuml]
....
alice -> bob: hello
....
ADOC
output = Asciidoctor.convert(input, attributes: { 'env-idea' => '', 'kroki-plantuml-include' => 'spec/fixtures/config.puml' }, standalone: false, safe: :safe)
(expect output).to eql %()
end
it 'should normalize plantuml-include path when safe mode is safe' do
input = <<~'ADOC'
[plantuml]
....
alice -> bob: hello
....
ADOC
output = Asciidoctor.convert(input, attributes: { 'env-idea' => '', 'kroki-plantuml-include' => '../../../spec/fixtures/config.puml' }, standalone: false, safe: :safe)
(expect output).to eql %()
end
it 'should not include file which reside outside of the parent directory of the source when safe mode is safe' do
input = <<~'ADOC'
[plantuml]
....
alice -> bob: hello
....
ADOC
output = Asciidoctor.convert(input, attributes: { 'env-idea' => '', 'kroki-plantuml-include' => '/etc/passwd' }, standalone: false, safe: :safe)
(expect output).to eql %()
end
it 'should not include file when safe mode is secure' do
input = <<~'ADOC'
[plantuml]
....
alice -> bob: hello
....
ADOC
output = Asciidoctor.convert(input, attributes: { 'env-idea' => '', 'kroki-plantuml-include' => 'spec/fixtures/config.puml' }, standalone: false, safe: :secure)
(expect output).to eql %()
end
it 'should create SVG diagram in imagesdir if kroki-fetch-diagram is set' do
input = <<~'ADOC'
:imagesdir: .asciidoctor/kroki
plantuml::spec/fixtures/alice.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, attributes: { 'kroki-fetch-diagram' => '' }, standalone: false, safe: :safe)
(expect output).to eql %()
end
it 'should not fetch diagram when safe mode is secure' do
input = <<~'ADOC'
:imagesdir: .asciidoctor/kroki
plantuml::spec/fixtures/alice.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, attributes: { 'kroki-fetch-diagram' => '' }, standalone: false)
(expect output).to eql %()
end
it 'should create PNG diagram in imagesdir if kroki-fetch-diagram is set' do
input = <<~'ADOC'
:imagesdir: .asciidoctor/kroki
plantuml::spec/fixtures/alice.puml[png,role=sequence]
ADOC
output = Asciidoctor.convert(input, attributes: { 'kroki-fetch-diagram' => '' }, standalone: false, safe: :safe)
(expect output).to eql %()
end
end
context 'instantiate' do
it 'should instantiate block processor without warning' do
original_stderr = $stderr
$stderr = StringIO.new
::AsciidoctorExtensions::KrokiBlockProcessor.new 'plantuml'.to_sym, {}
output = $stderr.string
(expect output).to eql ''
ensure
$stderr = original_stderr
end
end
end
describe ::AsciidoctorExtensions::Kroki do
it 'should return the list of supported diagrams' do
diagram_names = ::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES
expect(diagram_names).to include('vegalite', 'plantuml', 'bytefield', 'bpmn', 'excalidraw', 'wavedrom', 'pikchr')
end
it 'should register the extension for the list of supported diagrams' do
doc = Asciidoctor::Document.new
registry = Asciidoctor::Extensions::Registry.new
registry.activate doc
::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES.each do |name|
expect(registry.find_block_extension(name)).to_not be_nil, "expected block extension named '#{name}' to be registered"
expect(registry.find_block_macro_extension(name)).to_not be_nil, "expected block macro extension named '#{name}' to be registered "
end
end
end
asciidoctor-kroki-0.5.0/spec/fixtures/ 0000755 0000041 0000041 00000000000 14101232272 017761 5 ustar www-data www-data asciidoctor-kroki-0.5.0/spec/fixtures/config.puml 0000644 0000041 0000041 00000000032 14101232272 022120 0 ustar www-data www-data skinparam monochrome true
asciidoctor-kroki-0.5.0/spec/fixtures/alice.puml 0000644 0000041 0000041 00000000024 14101232272 021731 0 ustar www-data www-data alice -> bob: hello
asciidoctor-kroki-0.5.0/spec/.rubocop.yml 0000644 0000041 0000041 00000000103 14101232272 020354 0 ustar www-data www-data inherit_from:
- ../.rubocop.yml
Metrics/BlockLength:
Max: 500
asciidoctor-kroki-0.5.0/spec/require_spec.rb 0000644 0000041 0000041 00000000460 14101232272 021123 0 ustar www-data www-data # frozen_string_literal: true
describe 'require' do
it 'should require the library' do
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'asciidoctor-kroki'
(expect Asciidoctor::Extensions.groups[:extgrp0]).to_not be_nil
end
end
asciidoctor-kroki-0.5.0/spec/asciidoctor_kroki_client_spec.rb 0000644 0000041 0000041 00000006456 14101232272 024522 0 ustar www-data www-data # frozen_string_literal: true
require 'rspec_helper'
require 'asciidoctor'
require_relative '../lib/asciidoctor/extensions/asciidoctor_kroki'
describe ::AsciidoctorExtensions::KrokiClient do
it 'should use adaptive method when http method is invalid' do
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'patch', kroki_http_client)
expect(kroki_client.method).to eq('adaptive')
end
it 'should use post method when http method is post' do
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'POST', kroki_http_client)
expect(kroki_client.method).to eq('post')
end
it 'should use get method when http method is get' do
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'get', kroki_http_client)
expect(kroki_client.method).to eq('get')
end
it 'should use 4000 as the default max URI length' do
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'get', kroki_http_client)
expect(kroki_client.max_uri_length).to eq(4000)
end
it 'should use a custom value as max URI length' do
kroki_http_client = ::AsciidoctorExtensions::KrokiHttpClient
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'get', kroki_http_client, nil, 8000)
expect(kroki_client.max_uri_length).to eq(8000)
end
it 'should get an image with POST request if the URI length is greater than the value configured' do
kroki_http_client = Class.new do
class << self
def get(uri, _)
"GET #{uri}"
end
def post(uri, data, _)
"POST #{uri} - #{data}"
end
end
end
kroki_diagram = Class.new do
attr_reader :type, :text, :format
def initialize(type, format, text)
@text = text
@type = type
@format = format
end
def get_diagram_uri(_)
'diagram-uri'
end
end.new('type', 'format', 'text')
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'adaptive', kroki_http_client, nil, 10)
result = kroki_client.get_image(kroki_diagram, 'utf8')
expect(result).to eq('POST http://localhost:8000/type/format - text')
end
it 'should get an image with GET request if the URI length is lower or equals than the value configured' do
kroki_http_client = Class.new do
class << self
def get(uri, _)
"GET #{uri}"
end
def post(uri, data, _)
"POST #{uri} - #{data}"
end
end
end
kroki_diagram = Class.new do
attr_reader :type, :text, :format
def initialize(type, format, text)
@text = text
@type = type
@format = format
end
def get_diagram_uri(_)
'diagram-uri'
end
end.new('type', 'format', 'text')
kroki_client = ::AsciidoctorExtensions::KrokiClient.new('http://localhost:8000', 'adaptive', kroki_http_client, nil, 11)
result = kroki_client.get_image(kroki_diagram, 'utf8')
expect(result).to eq('GET diagram-uri')
end
end
asciidoctor-kroki-0.5.0/spec/asciidoctor_kroki_block_macro_spec.rb 0000644 0000041 0000041 00000012115 14101232272 025504 0 ustar www-data www-data # frozen_string_literal: true
require 'rspec_helper'
require 'asciidoctor'
require_relative '../lib/asciidoctor/extensions/asciidoctor_kroki'
require_relative '../lib/asciidoctor/extensions/asciidoctor_kroki/extension'
describe ::AsciidoctorExtensions::KrokiBlockMacroProcessor do
context 'convert to html5' do
it 'should catch exception if target is not readable' do
input = <<~'ADOC'
plantuml::spec/fixtures/missing.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, standalone: false)
(expect output).to eql %(
Unresolved block macro - plantuml::spec/fixtures/missing.puml[]
)
end
end
context 'using a custom block macro' do
it 'should disallow read' do
# noinspection RubyClassModuleNamingConvention
class DisallowReadKrokiBlockMacroProcessor < ::AsciidoctorExtensions::KrokiBlockMacroProcessor
def read_allowed?(_target)
false
end
end
registry = Asciidoctor::Extensions.create do
block_macro DisallowReadKrokiBlockMacroProcessor, 'plantuml'
end
input = <<~'ADOC'
plantuml::spec/fixtures/alice.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, standalone: false, extension_registry: registry)
(expect output).to eql %()
end
it 'should allow read if target is not a URI' do
# noinspection RubyClassModuleNamingConvention
class DisallowUriReadKrokiBlockMacroProcessor < ::AsciidoctorExtensions::KrokiBlockMacroProcessor
def read_allowed?(target)
return false if ::Asciidoctor::Helpers.uriish?(target)
true
end
end
registry = Asciidoctor::Extensions.create do
block_macro DisallowUriReadKrokiBlockMacroProcessor, 'plantuml'
end
input = <<~'ADOC'
plantuml::https://domain.org/alice.puml[svg,role=sequence]
plantuml::file://path/to/alice.puml[svg,role=sequence]
plantuml::spec/fixtures/alice.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, standalone: false, extension_registry: registry)
(expect output).to eql %(
)
end
it 'should override the resolve target method' do
# noinspection RubyClassModuleNamingConvention
class FixtureResolveTargetKrokiBlockMacroProcessor < ::AsciidoctorExtensions::KrokiBlockMacroProcessor
def resolve_target_path(target)
"spec/fixtures/#{target}"
end
end
registry = Asciidoctor::Extensions.create do
block_macro FixtureResolveTargetKrokiBlockMacroProcessor, 'plantuml'
end
input = <<~'ADOC'
plantuml::alice.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, standalone: false, extension_registry: registry)
(expect output).to eql %()
end
it 'should display unresolved block macro message when the traget cannot be resolved' do
# noinspection RubyClassModuleNamingConvention
class UnresolvedTargetKrokiBlockMacroProcessor < ::AsciidoctorExtensions::KrokiBlockMacroProcessor
def resolve_target_path(_target)
nil
end
end
registry = Asciidoctor::Extensions.create do
block_macro UnresolvedTargetKrokiBlockMacroProcessor, 'plantuml'
end
input = <<~'ADOC'
plantuml::alice.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, standalone: false, extension_registry: registry)
(expect output).to eql %(
Unresolved block macro - plantuml::[]
)
end
it 'should override the unresolved block macro message' do
# noinspection RubyClassModuleNamingConvention
class CustomUnresolvedTargetMessageKrokiBlockMacroProcessor < ::AsciidoctorExtensions::KrokiBlockMacroProcessor
def unresolved_block_macro_message(name, target)
"*[ERROR: #{name}::#{target}[] - unresolved block macro]*"
end
end
registry = Asciidoctor::Extensions.create do
block_macro CustomUnresolvedTargetMessageKrokiBlockMacroProcessor, 'plantuml'
end
input = <<~'ADOC'
plantuml::spec/fixtures/missing.puml[svg,role=sequence]
ADOC
output = Asciidoctor.convert(input, standalone: false, extension_registry: registry)
(expect output).to eql %(
[ERROR: plantuml::spec/fixtures/missing.puml[] - unresolved block macro]
)
end
end
end
asciidoctor-kroki-0.5.0/spec/rspec_helper.rb 0000644 0000041 0000041 00000000440 14101232272 021106 0 ustar www-data www-data # frozen_string_literal: true
RSpec.configure do |config|
config.before(:suite) do
FileUtils.rm(Dir.glob("#{__dir__}/../.asciidoctor/kroki/diag-*"))
end
config.after(:suite) do
FileUtils.rm(Dir.glob("#{__dir__}/../.asciidoctor/kroki/diag-*")) unless ENV['DEBUG']
end
end
asciidoctor-kroki-0.5.0/.rubocop.yml 0000644 0000041 0000041 00000000471 14101232272 017432 0 ustar www-data www-data Style/Encoding:
Enabled: false
Layout/EndOfLine:
EnforcedStyle: lf
Metrics/LineLength:
Max: 180
Metrics/ClassLength:
Max: 150
Metrics/MethodLength:
Max: 50
Metrics/CyclomaticComplexity:
Max: 10
Metrics/PerceivedComplexity:
Max: 10
Metrics/AbcSize:
Max: 30
Metrics/ParameterLists:
Max: 7
asciidoctor-kroki-0.5.0/.gitignore 0000644 0000041 0000041 00000000030 14101232272 017137 0 ustar www-data www-data pkg/
.asciidoctor/kroki
asciidoctor-kroki-0.5.0/Rakefile 0000644 0000041 0000041 00000000157 14101232272 016626 0 ustar www-data www-data # frozen_string_literal: true
Dir.glob('tasks/*.rake').each { |file| load file }
task default: %w[lint spec]
asciidoctor-kroki-0.5.0/lib/ 0000755 0000041 0000041 00000000000 14101232272 015724 5 ustar www-data www-data asciidoctor-kroki-0.5.0/lib/asciidoctor/ 0000755 0000041 0000041 00000000000 14101232272 020227 5 ustar www-data www-data asciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/ 0000755 0000041 0000041 00000000000 14101232272 022426 5 ustar www-data www-data asciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/asciidoctor_kroki/ 0000755 0000041 0000041 00000000000 14101232272 026130 5 ustar www-data www-data asciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/asciidoctor_kroki/extension.rb 0000644 0000041 0000041 00000026651 14101232272 030503 0 ustar www-data www-data # frozen_string_literal: true
require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
# Asciidoctor extensions
#
module AsciidoctorExtensions
include Asciidoctor
# A block extension that converts a diagram into an image.
#
class KrokiBlockProcessor < Extensions::BlockProcessor
use_dsl
on_context :listing, :literal
name_positional_attributes 'target', 'format'
# @param name [String] name of the block macro (optional)
# @param config [Hash] a config hash (optional)
# - :logger a logger used to log warning and errors (optional)
#
def initialize(name = nil, config = {})
@logger = (config || {}).delete(:logger) { ::Asciidoctor::LoggerManager.logger }
super(name, config)
end
def process(parent, reader, attrs)
diagram_type = @name
diagram_text = reader.string
KrokiProcessor.process(self, parent, attrs, diagram_type, diagram_text, @logger)
end
protected
attr_reader :logger
end
# A block macro extension that converts a diagram into an image.
#
class KrokiBlockMacroProcessor < Asciidoctor::Extensions::BlockMacroProcessor
use_dsl
name_positional_attributes 'format'
# @param name [String] name of the block macro (optional)
# @param config [Hash] a config hash (optional)
# - :logger a logger used to log warning and errors (optional)
#
def initialize(name = nil, config = {})
@logger = (config || {}).delete(:logger) { ::Asciidoctor::LoggerManager.logger }
super(name, config)
end
def process(parent, target, attrs)
diagram_type = @name
target = parent.apply_subs(target, [:attributes])
unless read_allowed?(target)
link = create_inline(parent, :anchor, target, type: :link, target: target)
return create_block(parent, :paragraph, link.convert, {}, content_model: :raw)
end
unless (path = resolve_target_path(target))
logger.error "#{diagram_type} block macro not found: #{target}."
create_block(parent, 'paragraph', unresolved_block_macro_message(diagram_type, target), {})
end
begin
diagram_text = read(path)
rescue => e # rubocop:disable RescueStandardError
logger.error "Failed to read #{diagram_type} file: #{path}. #{e}."
return create_block(parent, 'paragraph', unresolved_block_macro_message(diagram_type, path), {})
end
KrokiProcessor.process(self, parent, attrs, diagram_type, diagram_text, @logger)
end
protected
attr_reader :logger
def resolve_target_path(target)
target
end
def read_allowed?(_target)
true
end
def read(target)
if target.start_with?('http://') || target.start_with?('https://')
require 'open-uri'
URI.open(target, &:read)
else
File.open(target, &:read)
end
end
def unresolved_block_macro_message(name, target)
"Unresolved block macro - #{name}::#{target}[]"
end
end
# Kroki API
#
module Kroki
SUPPORTED_DIAGRAM_NAMES = %w[
actdiag
blockdiag
bpmn
bytefield
c4plantuml
ditaa
erd
excalidraw
graphviz
mermaid
nomnoml
nwdiag
packetdiag
pikchr
plantuml
rackdiag
seqdiag
svgbob
umlet
vega
vegalite
wavedrom
].freeze
end
# Internal processor
#
class KrokiProcessor
TEXT_FORMATS = %w[txt atxt utxt].freeze
class << self
def process(processor, parent, attrs, diagram_type, diagram_text, logger)
doc = parent.document
diagram_text = prepend_plantuml_config(diagram_text, diagram_type, doc, logger)
# If "subs" attribute is specified, substitute accordingly.
# Be careful not to specify "specialcharacters" or your diagram code won't be valid anymore!
if (subs = attrs['subs'])
diagram_text = parent.apply_subs(diagram_text, parent.resolve_subs(subs))
end
title = attrs.delete('title')
caption = attrs.delete('caption')
attrs.delete('opts')
role = attrs['role']
format = get_format(doc, attrs, diagram_type)
attrs['role'] = get_role(format, role)
attrs['format'] = format
kroki_diagram = KrokiDiagram.new(diagram_type, format, diagram_text)
kroki_client = KrokiClient.new(server_url(doc), http_method(doc), KrokiHttpClient, logger, max_uri_length(doc))
if TEXT_FORMATS.include?(format)
text_content = kroki_client.text_content(kroki_diagram)
block = processor.create_block(parent, 'literal', text_content, attrs)
else
attrs['alt'] = get_alt(attrs)
attrs['target'] = create_image_src(doc, kroki_diagram, kroki_client)
block = processor.create_image_block(parent, attrs)
end
block.title = title if title
block.assign_caption(caption, 'figure')
block
end
private
def prepend_plantuml_config(diagram_text, diagram_type, doc, logger)
if diagram_type == :plantuml && doc.safe < ::Asciidoctor::SafeMode::SECURE && doc.attr?('kroki-plantuml-include')
# REMIND: this behaves different than the JS version
# Once we have a preprocessor for Ruby, the value should be added in the diagram source as "!include #{plantuml_include}"
plantuml_include_path = doc.normalize_system_path(doc.attr('kroki-plantuml-include'))
if ::File.readable? plantuml_include_path
config = File.read(plantuml_include_path)
diagram_text = config + "\n" + diagram_text
else
logger.warn "Unable to read plantuml-include. File not found or not readable: #{plantuml_include_path}."
end
end
diagram_text
end
def get_alt(attrs)
if (title = attrs['title'])
title
elsif (target = attrs['target'])
target
else
'Diagram'
end
end
def get_role(format, role)
if role
if format
"#{role} kroki-format-#{format} kroki"
else
"#{role} kroki"
end
else
'kroki'
end
end
def get_format(doc, attrs, diagram_type)
format = attrs['format'] || 'svg'
# The JavaFX preview doesn't support SVG well, therefore we'll use PNG format...
if doc.attr?('env-idea') && format == 'svg'
# ... unless the diagram library does not support PNG as output format!
# Currently, mermaid, nomnoml, svgbob, wavedrom only support SVG as output format.
svg_only_diagram_types = %w[:mermaid :nomnoml :svgbob :wavedrom]
format = 'png' unless svg_only_diagram_types.include?(diagram_type)
end
format
end
def create_image_src(doc, kroki_diagram, kroki_client)
if doc.attr('kroki-fetch-diagram') && doc.safe < ::Asciidoctor::SafeMode::SECURE
kroki_diagram.save(output_dir_path(doc), kroki_client)
else
kroki_diagram.get_diagram_uri(server_url(doc))
end
end
def server_url(doc)
doc.attr('kroki-server-url', 'https://kroki.io')
end
def http_method(doc)
doc.attr('kroki-http-method', 'adaptive').downcase
end
def max_uri_length(doc)
doc.attr('kroki-max-uri-length', '4000').to_i
end
def output_dir_path(doc)
images_dir = doc.attr('imagesdir', '')
if (images_output_dir = doc.attr('imagesoutdir'))
images_output_dir
elsif (out_dir = doc.attr('outdir'))
File.join(out_dir, images_dir)
elsif (to_dir = doc.attr('to_dir'))
File.join(to_dir, images_dir)
else
File.join(doc.base_dir, images_dir)
end
end
end
end
# Kroki diagram
#
class KrokiDiagram
require 'fileutils'
require 'zlib'
require 'digest'
attr_reader :type
attr_reader :text
attr_reader :format
def initialize(type, format, text)
@text = text
@type = type
@format = format
end
def get_diagram_uri(server_url)
_join_uri_segments(server_url, @type, @format, encode)
end
def encode
Base64.urlsafe_encode64(Zlib::Deflate.deflate(@text, 9))
end
def save(output_dir_path, kroki_client)
diagram_url = get_diagram_uri(kroki_client.server_url)
diagram_name = "diag-#{Digest::SHA256.hexdigest diagram_url}.#{@format}"
file_path = File.join(output_dir_path, diagram_name)
encoding = if @format == 'txt' || @format == 'atxt' || @format == 'utxt'
'utf8'
elsif @format == 'svg'
'binary'
else
'binary'
end
# file is either (already) on the file system or we should read it from Kroki
contents = File.exist?(file_path) ? File.open(file_path, &:read) : kroki_client.get_image(self, encoding)
FileUtils.mkdir_p(output_dir_path)
if encoding == 'binary'
File.binwrite(file_path, contents)
else
File.write(file_path, contents)
end
diagram_name
end
private
def _join_uri_segments(base, *uris)
segments = []
# remove trailing slashes
segments.push(base.gsub(%r{[/]+$}, ''))
segments.concat(uris.map do |uri|
# remove leading and trailing slashes
uri.to_s
.gsub(%r{^[/]+}, '')
.gsub(%r{[/]+$}, '')
end)
segments.join('/')
end
end
# Kroki client
#
class KrokiClient
attr_reader :server_url
attr_reader :method
attr_reader :max_uri_length
SUPPORTED_HTTP_METHODS = %w[get post adaptive].freeze
def initialize(server_url, http_method, http_client, logger = ::Asciidoctor::LoggerManager.logger, max_uri_length = 4000)
@server_url = server_url
@max_uri_length = max_uri_length
@http_client = http_client
method = (http_method || 'adaptive').downcase
if SUPPORTED_HTTP_METHODS.include?(method)
@method = method
else
logger.warn "Invalid value '#{method}' for kroki-http-method attribute. The value must be either: 'get', 'post' or 'adaptive'. Proceeding using: 'adaptive'."
@method = 'adaptive'
end
end
def text_content(kroki_diagram)
get_image(kroki_diagram, 'utf-8')
end
def get_image(kroki_diagram, encoding)
type = kroki_diagram.type
format = kroki_diagram.format
text = kroki_diagram.text
if @method == 'adaptive' || @method == 'get'
uri = kroki_diagram.get_diagram_uri(server_url)
if uri.length > @max_uri_length
# The request URI is longer than the max URI length.
if @method == 'get'
# The request might be rejected by the server with a 414 Request-URI Too Large.
# Consider using the attribute kroki-http-method with the value 'adaptive'.
@http_client.get(uri, encoding)
else
@http_client.post("#{@server_url}/#{type}/#{format}", text, encoding)
end
else
@http_client.get(uri, encoding)
end
else
@http_client.post("#{@server_url}/#{type}/#{format}", text, encoding)
end
end
end
# Kroki HTTP client
#
class KrokiHttpClient
require 'net/http'
require 'uri'
require 'json'
class << self
def get(uri, _)
::OpenURI.open_uri(uri, 'r', &:read)
end
def post(uri, data, _)
res = ::Net::HTTP.request_post(uri, data)
res.body
end
end
end
end
asciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/asciidoctor_kroki.rb 0000644 0000041 0000041 00000000611 14101232272 026453 0 ustar www-data www-data # frozen_string_literal: true
require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
require_relative 'asciidoctor_kroki/extension'
Asciidoctor::Extensions.register do
::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES.each do |name|
block_macro ::AsciidoctorExtensions::KrokiBlockMacroProcessor, name
block ::AsciidoctorExtensions::KrokiBlockProcessor, name
end
end
asciidoctor-kroki-0.5.0/lib/asciidoctor-kroki.rb 0000644 0000041 0000041 00000000236 14101232272 021672 0 ustar www-data www-data # rubocop:disable Naming/FileName
# rubocop:enable Naming/FileName
# frozen_string_literal: true
require_relative 'asciidoctor/extensions/asciidoctor_kroki'
asciidoctor-kroki-0.5.0/asciidoctor-kroki.gemspec 0000644 0000041 0000041 00000001717 14101232272 022151 0 ustar www-data www-data # frozen_string_literal: true
Gem::Specification.new do |s|
s.name = 'asciidoctor-kroki'
s.version = '0.5.0'
s.summary = 'Asciidoctor extension to convert diagrams to images using Kroki'
s.description = 'An extension for Asciidoctor to convert diagrams to images using https://kroki.io'
s.authors = ['Guillaume Grossetie']
s.email = ['ggrossetie@yuzutech.fr']
s.homepage = 'https://github.com/Mogztter/asciidoctor-kroki'
s.license = 'MIT'
s.metadata = {
'bug_tracker_uri' => 'https://github.com/Mogztter/asciidoctor-kroki/issues',
'source_code_uri' => 'https://github.com/Mogztter/asciidoctor-kroki'
}
s.files = `git ls-files`.split($RS)
s.test_files = s.files.grep(%r{^(test|spec|features|tasks)/})
s.require_paths = ['lib']
s.add_runtime_dependency 'asciidoctor', '~> 2.0'
s.add_development_dependency 'rake', '~> 12.3.2'
s.add_development_dependency 'rspec', '~> 3.8.0'
s.add_development_dependency 'rubocop', '~> 0.74.0'
end
asciidoctor-kroki-0.5.0/Gemfile 0000644 0000041 0000041 00000000106 14101232272 016446 0 ustar www-data www-data # frozen_string_literal: true
source 'https://rubygems.org'
gemspec
asciidoctor-kroki-0.5.0/.ruby-version 0000644 0000041 0000041 00000000006 14101232272 017617 0 ustar www-data www-data 2.6.5
asciidoctor-kroki-0.5.0/.asciidoctor/ 0000755 0000041 0000041 00000000000 14101232272 017537 5 ustar www-data www-data asciidoctor-kroki-0.5.0/.asciidoctor/kroki/ 0000755 0000041 0000041 00000000000 14101232272 020656 5 ustar www-data www-data asciidoctor-kroki-0.5.0/.asciidoctor/kroki/.gitkeep 0000644 0000041 0000041 00000000000 14101232272 022275 0 ustar www-data www-data