asciidoctor-kroki-0.5.0/0000755000004100000410000000000014101232272015156 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/Gemfile.lock0000644000004100000410000000211314101232272017375 0ustar www-datawww-dataPATH 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/0000755000004100000410000000000014101232272016303 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/tasks/lint.rake0000644000004100000410000000013014101232272020107 0ustar www-datawww-data# frozen_string_literal: true require 'rubocop/rake_task' RuboCop::RakeTask.new :lint asciidoctor-kroki-0.5.0/tasks/bundler.rake0000644000004100000410000000016314101232272020602 0ustar www-datawww-data# frozen_string_literal: true begin require 'bundler/gem_tasks' rescue LoadError warn $ERROR_INFO.message end asciidoctor-kroki-0.5.0/tasks/rspec.rake0000644000004100000410000000023014101232272020256 0ustar www-datawww-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/0000755000004100000410000000000014101232272016110 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/spec/asciidoctor_kroki_diagram_spec.rb0000644000004100000410000000635514101232272024646 0ustar www-datawww-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.rb0000644000004100000410000001425414101232272023157 0ustar www-datawww-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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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 %(
Diagram
) 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/0000755000004100000410000000000014101232272017761 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/spec/fixtures/config.puml0000644000004100000410000000003214101232272022120 0ustar www-datawww-dataskinparam monochrome true asciidoctor-kroki-0.5.0/spec/fixtures/alice.puml0000644000004100000410000000002414101232272021731 0ustar www-datawww-dataalice -> bob: hello asciidoctor-kroki-0.5.0/spec/.rubocop.yml0000644000004100000410000000010314101232272020354 0ustar www-datawww-datainherit_from: - ../.rubocop.yml Metrics/BlockLength: Max: 500 asciidoctor-kroki-0.5.0/spec/require_spec.rb0000644000004100000410000000046014101232272021123 0ustar www-datawww-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.rb0000644000004100000410000000645614101232272024522 0ustar www-datawww-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.rb0000644000004100000410000001211514101232272025504 0ustar www-datawww-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 %(

spec/fixtures/alice.puml

) 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 %(

https://domain.org/alice.puml

file://path/to/alice.puml

Diagram
) 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 %(
Diagram
) 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.rb0000644000004100000410000000044014101232272021106 0ustar www-datawww-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.yml0000644000004100000410000000047114101232272017432 0ustar www-datawww-dataStyle/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/.gitignore0000644000004100000410000000003014101232272017137 0ustar www-datawww-datapkg/ .asciidoctor/kroki asciidoctor-kroki-0.5.0/Rakefile0000644000004100000410000000015714101232272016626 0ustar www-datawww-data# frozen_string_literal: true Dir.glob('tasks/*.rake').each { |file| load file } task default: %w[lint spec] asciidoctor-kroki-0.5.0/lib/0000755000004100000410000000000014101232272015724 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/lib/asciidoctor/0000755000004100000410000000000014101232272020227 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/0000755000004100000410000000000014101232272022426 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/asciidoctor_kroki/0000755000004100000410000000000014101232272026130 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/lib/asciidoctor/extensions/asciidoctor_kroki/extension.rb0000644000004100000410000002665114101232272030503 0ustar www-datawww-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.rb0000644000004100000410000000061114101232272026453 0ustar www-datawww-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.rb0000644000004100000410000000023614101232272021672 0ustar www-datawww-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.gemspec0000644000004100000410000000171714101232272022151 0ustar www-datawww-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/Gemfile0000644000004100000410000000010614101232272016446 0ustar www-datawww-data# frozen_string_literal: true source 'https://rubygems.org' gemspec asciidoctor-kroki-0.5.0/.ruby-version0000644000004100000410000000000614101232272017617 0ustar www-datawww-data2.6.5 asciidoctor-kroki-0.5.0/.asciidoctor/0000755000004100000410000000000014101232272017537 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/.asciidoctor/kroki/0000755000004100000410000000000014101232272020656 5ustar www-datawww-dataasciidoctor-kroki-0.5.0/.asciidoctor/kroki/.gitkeep0000644000004100000410000000000014101232272022275 0ustar www-datawww-data