kramdown-plantuml 1.0.5 → 1.1.4
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 +4 -4
- data/.github/codecov.yml +2 -0
- data/.github/dependabot.yml +23 -0
- data/.github/mergify.yml +22 -0
- data/.github/scripts/amend.sh +176 -0
- data/.github/scripts/publish-gem.sh +24 -6
- data/.github/scripts/test-gem.sh +69 -41
- data/.github/scripts/variables.sh +4 -2
- data/.github/workflows/amend.yml +19 -0
- data/.github/workflows/no-java.yml +1 -2
- data/.github/workflows/no-plantuml.yml +4 -7
- data/.github/workflows/ruby.yml +45 -29
- data/.github/workflows/shell.yml +1 -5
- data/.gitignore +4 -0
- data/.rubocop.yml +1 -0
- data/GitVersion.yml +5 -0
- data/README.md +106 -18
- data/Rakefile +18 -1
- data/bin/net/sourceforge/plantuml/plantuml/{1.2020.18/plantuml-1.2020.18.jar → 1.2021.9/plantuml-1.2021.9.jar} +0 -0
- data/kramdown-plantuml.gemspec +10 -3
- data/lib/kramdown-plantuml/bool_env.rb +24 -0
- data/lib/kramdown-plantuml/console_logger.rb +56 -0
- data/lib/kramdown-plantuml/diagram.rb +57 -0
- data/lib/kramdown-plantuml/executor.rb +52 -0
- data/lib/kramdown-plantuml/logger.rb +89 -0
- data/lib/kramdown-plantuml/plantuml_error.rb +70 -0
- data/lib/kramdown-plantuml/plantuml_result.rb +66 -0
- data/lib/kramdown-plantuml/theme.rb +76 -0
- data/lib/kramdown-plantuml/version.rb +1 -1
- data/lib/kramdown-plantuml.rb +0 -8
- data/lib/kramdown_html.rb +8 -5
- data/pom.xml +1 -1
- metadata +78 -10
- data/.github/scripts/bundle-install.sh +0 -6
- data/lib/kramdown-plantuml/converter.rb +0 -44
Binary file
|
data/kramdown-plantuml.gemspec
CHANGED
@@ -9,10 +9,13 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Swedbank Pay']
|
10
10
|
spec.email = ['opensource@swedbankpay.com']
|
11
11
|
|
12
|
-
spec.summary =
|
12
|
+
spec.summary = <<~SUMMARY
|
13
|
+
kramdown-plantuml allows you to use PlantUML syntax within fenced code
|
14
|
+
blocks with Kramdown (Jekyll's default Markdown parser).
|
15
|
+
SUMMARY
|
13
16
|
spec.homepage = 'https://github.com/SwedbankPay/kramdown-plantuml'
|
14
17
|
spec.license = 'MIT'
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
18
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
16
19
|
|
17
20
|
spec.metadata['homepage_uri'] = spec.homepage
|
18
21
|
spec.metadata['source_code_uri'] = 'https://github.com/SwedbankPay/kramdown-plantuml'
|
@@ -33,9 +36,13 @@ Gem::Specification.new do |spec|
|
|
33
36
|
spec.require_paths = ['lib']
|
34
37
|
|
35
38
|
spec.add_dependency 'kramdown', '~> 2.3'
|
39
|
+
spec.add_dependency 'kramdown-parser-gfm', '~> 1.1'
|
36
40
|
spec.add_dependency 'open3', '~> 0.1'
|
37
41
|
|
38
42
|
spec.add_development_dependency 'rake', '~> 13.0'
|
39
43
|
spec.add_development_dependency 'rspec', '~> 3.2'
|
40
|
-
spec.add_development_dependency '
|
44
|
+
spec.add_development_dependency 'rspec-its', '~> 1.3'
|
45
|
+
spec.add_development_dependency 'rubocop', '~> 1.12'
|
46
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.6'
|
47
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.4'
|
41
48
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kramdown
|
4
|
+
module PlantUml
|
5
|
+
# Converts envrionment variables to boolean values
|
6
|
+
class BoolEnv
|
7
|
+
TRUTHY_VALUES = %w[t true yes y 1].freeze
|
8
|
+
FALSEY_VALUES = %w[f false n no 0].freeze
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
value = ENV.fetch(name, nil)
|
13
|
+
@value = value.to_s.downcase unless value.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def true?
|
17
|
+
return true if TRUTHY_VALUES.include?(@value)
|
18
|
+
return false if FALSEY_VALUES.include?(@value) || @value.nil? || value.empty?
|
19
|
+
|
20
|
+
raise "The value '#{@value}' of '#{@name}' can't be converted to a boolean"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'bool_env'
|
4
|
+
|
5
|
+
module Kramdown
|
6
|
+
module PlantUml
|
7
|
+
# Logs to $stdout and $stderr
|
8
|
+
class ConsoleLogger
|
9
|
+
LOG_LEVELS = %i[debug info warn error].freeze
|
10
|
+
|
11
|
+
def initialize(level)
|
12
|
+
@configured_log_level = level
|
13
|
+
end
|
14
|
+
|
15
|
+
def debug(message)
|
16
|
+
write(:debug, message)
|
17
|
+
end
|
18
|
+
|
19
|
+
def info(message)
|
20
|
+
write(:info, message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def warn(message)
|
24
|
+
write(:warn, message)
|
25
|
+
end
|
26
|
+
|
27
|
+
def error(message)
|
28
|
+
write(:error, message)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def write(level, message)
|
34
|
+
return false unless write_message?(level)
|
35
|
+
|
36
|
+
pipe = pipe_for(level)
|
37
|
+
pipe.write("#{message}\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_message?(level_of_message)
|
41
|
+
LOG_LEVELS.index(@configured_log_level) <= LOG_LEVELS.index(level_of_message)
|
42
|
+
end
|
43
|
+
|
44
|
+
def pipe_for(level)
|
45
|
+
case level
|
46
|
+
when :debug, :info
|
47
|
+
$stdout
|
48
|
+
when :warn, :error
|
49
|
+
$stderr
|
50
|
+
else
|
51
|
+
raise ArgumentError, "Unknown log level '#{level}'."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'version'
|
4
|
+
require_relative 'theme'
|
5
|
+
require_relative 'plantuml_error'
|
6
|
+
require_relative 'logger'
|
7
|
+
require_relative 'executor'
|
8
|
+
|
9
|
+
module Kramdown
|
10
|
+
module PlantUml
|
11
|
+
# Represents a PlantUML diagram that can be converted to SVG.
|
12
|
+
class Diagram
|
13
|
+
attr_reader :theme, :plantuml, :result
|
14
|
+
|
15
|
+
def initialize(plantuml, options = {})
|
16
|
+
@plantuml = plantuml
|
17
|
+
@theme = Theme.new(options || {})
|
18
|
+
@logger = Logger.init
|
19
|
+
@executor = Executor.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def convert_to_svg
|
23
|
+
return @svg unless @svg.nil?
|
24
|
+
|
25
|
+
if @plantuml.nil? || @plantuml.empty?
|
26
|
+
@logger.warn ' kramdown-plantuml: PlantUML diagram is empty'
|
27
|
+
return @plantuml
|
28
|
+
end
|
29
|
+
|
30
|
+
@plantuml = @theme.apply(@plantuml)
|
31
|
+
@plantuml = plantuml.strip
|
32
|
+
log(plantuml)
|
33
|
+
@result = @executor.execute(self)
|
34
|
+
@result.validate
|
35
|
+
@svg = wrap(@result.without_xml_prologue)
|
36
|
+
@svg
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def wrap(svg)
|
42
|
+
theme_class = @theme.name ? "theme-#{@theme.name}" : ''
|
43
|
+
class_name = "plantuml #{theme_class}".strip
|
44
|
+
|
45
|
+
wrapper_element_start = "<div class=\"#{class_name}\">"
|
46
|
+
wrapper_element_end = '</div>'
|
47
|
+
|
48
|
+
"#{wrapper_element_start}#{svg}#{wrapper_element_end}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def log(plantuml)
|
52
|
+
@logger.debug ' kramdown-plantuml: PlantUML converting diagram:'
|
53
|
+
@logger.debug_with_prefix ' kramdown-plantuml: ', plantuml
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require_relative '../which'
|
5
|
+
require_relative 'logger'
|
6
|
+
require_relative 'plantuml_result'
|
7
|
+
|
8
|
+
module Kramdown
|
9
|
+
module PlantUml
|
10
|
+
# Executes the PlantUML Java application.
|
11
|
+
class Executor
|
12
|
+
def initialize
|
13
|
+
@logger = Logger.init
|
14
|
+
@plantuml_jar_file = find_plantuml_jar_file
|
15
|
+
|
16
|
+
raise IOError, 'Java can not be found' unless Which.which('java')
|
17
|
+
raise IOError, "No 'plantuml.jar' file could be found" if @plantuml_jar_file.nil?
|
18
|
+
raise IOError, "'#{@plantuml_jar_file}' does not exist" unless File.exist? @plantuml_jar_file
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(diagram)
|
22
|
+
raise ArgumentError, 'diagram cannot be nil' if diagram.nil?
|
23
|
+
raise ArgumentError, "diagram must be a #{Diagram}" unless diagram.is_a?(Diagram)
|
24
|
+
|
25
|
+
cmd = "java -Djava.awt.headless=true -jar #{@plantuml_jar_file} -tsvg -failfast -pipe #{debug_args}"
|
26
|
+
|
27
|
+
@logger.debug " kramdown-plantuml: Executing '#{cmd}'."
|
28
|
+
|
29
|
+
stdout, stderr, status = Open3.capture3 cmd, stdin_data: diagram.plantuml
|
30
|
+
|
31
|
+
@logger.debug " kramdown-plantuml: PlantUML exit code '#{status.exitstatus}'."
|
32
|
+
|
33
|
+
PlantUmlResult.new(diagram, stdout, stderr, status.exitstatus)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_plantuml_jar_file
|
39
|
+
dir = File.dirname __dir__
|
40
|
+
jar_glob = File.join dir, '../bin/**/plantuml*.jar'
|
41
|
+
first_jar = Dir[jar_glob].first
|
42
|
+
File.expand_path first_jar unless first_jar.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def debug_args
|
46
|
+
return ' -verbose' if @logger.debug?
|
47
|
+
|
48
|
+
' -nometadata'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'console_logger'
|
4
|
+
|
5
|
+
module Kramdown
|
6
|
+
module PlantUml
|
7
|
+
# Logs stuff
|
8
|
+
class Logger
|
9
|
+
def initialize(logger)
|
10
|
+
raise ArgumentError, 'logger cannot be nil' if logger.nil?
|
11
|
+
raise ArgumentError, 'logger must respond to #debug' unless logger.respond_to? :debug
|
12
|
+
raise ArgumentError, 'logger must respond to #info' unless logger.respond_to? :info
|
13
|
+
raise ArgumentError, 'logger must respond to #warn' unless logger.respond_to? :warn
|
14
|
+
raise ArgumentError, 'logger must respond to #error' unless logger.respond_to? :error
|
15
|
+
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def debug(message)
|
20
|
+
@logger.debug message
|
21
|
+
end
|
22
|
+
|
23
|
+
def debug_with_prefix(prefix, multiline_string)
|
24
|
+
return if multiline_string.nil? || multiline_string.empty?
|
25
|
+
|
26
|
+
lines = multiline_string.lines
|
27
|
+
lines.each do |line|
|
28
|
+
@logger.debug "#{prefix}#{line.rstrip}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def info(message)
|
33
|
+
@logger.info message
|
34
|
+
end
|
35
|
+
|
36
|
+
def warn(message)
|
37
|
+
@logger.warn message
|
38
|
+
end
|
39
|
+
|
40
|
+
def error(message)
|
41
|
+
@logger.error message
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug?
|
45
|
+
self.class.level == :debug
|
46
|
+
end
|
47
|
+
|
48
|
+
def level
|
49
|
+
@level ||= level_from_logger || self.class.env
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
def init
|
54
|
+
inner = nil
|
55
|
+
|
56
|
+
begin
|
57
|
+
require 'jekyll'
|
58
|
+
inner = Jekyll.logger
|
59
|
+
rescue LoadError
|
60
|
+
inner = ConsoleLogger.new level
|
61
|
+
end
|
62
|
+
|
63
|
+
Logger.new inner
|
64
|
+
end
|
65
|
+
|
66
|
+
def level
|
67
|
+
@level ||= level_from_env
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def level_from_env
|
73
|
+
return :debug if BoolEnv.new('DEBUG').true?
|
74
|
+
return :debug if BoolEnv.new('VERBOSE').true?
|
75
|
+
|
76
|
+
:warn
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def level_from_logger
|
83
|
+
return @logger.level if @logger.respond_to? :level
|
84
|
+
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'plantuml_result'
|
4
|
+
|
5
|
+
module Kramdown
|
6
|
+
module PlantUml
|
7
|
+
# PlantUML Error
|
8
|
+
class PlantUmlError < StandardError
|
9
|
+
def initialize(result)
|
10
|
+
raise ArgumentError, 'result cannot be nil' if result.nil?
|
11
|
+
raise ArgumentError, "result must be a #{PlantUmlResult}" unless result.is_a?(PlantUmlResult)
|
12
|
+
|
13
|
+
super create_message(result)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def create_message(result)
|
19
|
+
header = header(result).gsub("\n", ' ').strip
|
20
|
+
plantuml = plantuml(result)
|
21
|
+
result = result(result)
|
22
|
+
message = <<~MESSAGE
|
23
|
+
#{header}
|
24
|
+
|
25
|
+
#{plantuml}
|
26
|
+
|
27
|
+
#{result}
|
28
|
+
MESSAGE
|
29
|
+
|
30
|
+
message.strip
|
31
|
+
end
|
32
|
+
|
33
|
+
def header(result)
|
34
|
+
if theme_not_found?(result) && !result.diagram.nil? && !result.diagram.theme.nil?
|
35
|
+
return <<~HEADER
|
36
|
+
Conversion of the following PlantUML result failed because the
|
37
|
+
theme '#{result.diagram.theme.name}' can't be found in the directory
|
38
|
+
'#{result.diagram.theme.directory}':
|
39
|
+
HEADER
|
40
|
+
end
|
41
|
+
|
42
|
+
'Conversion of the following PlantUML result failed:'
|
43
|
+
end
|
44
|
+
|
45
|
+
def theme_not_found?(result)
|
46
|
+
!result.nil? \
|
47
|
+
&& !result.stderr.nil? \
|
48
|
+
&& result.stderr.include?('NullPointerException') \
|
49
|
+
&& result.stderr.include?('getTheme')
|
50
|
+
end
|
51
|
+
|
52
|
+
def plantuml(result)
|
53
|
+
return nil if result.nil? || result.diagram.nil?
|
54
|
+
|
55
|
+
result.diagram.plantuml
|
56
|
+
end
|
57
|
+
|
58
|
+
def result(result)
|
59
|
+
return nil if result.nil?
|
60
|
+
|
61
|
+
<<~RESULT
|
62
|
+
The error received from PlantUML was:
|
63
|
+
|
64
|
+
Exit code: #{result.exitcode}
|
65
|
+
#{result.stderr}
|
66
|
+
RESULT
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'logger'
|
4
|
+
require_relative 'plantuml_error'
|
5
|
+
require_relative 'diagram'
|
6
|
+
|
7
|
+
module Kramdown
|
8
|
+
module PlantUml
|
9
|
+
# Executes the PlantUML Java application.
|
10
|
+
class PlantUmlResult
|
11
|
+
attr_reader :diagram, :stdout, :stderr, :exitcode
|
12
|
+
|
13
|
+
def initialize(diagram, stdout, stderr, exitcode)
|
14
|
+
raise ArgumentError, 'diagram cannot be nil' if diagram.nil?
|
15
|
+
raise ArgumentError, "diagram must be a #{Diagram}" unless diagram.is_a?(Diagram)
|
16
|
+
raise ArgumentError, 'exitcode cannot be nil' if exitcode.nil?
|
17
|
+
raise ArgumentError, "exitcode must be a #{Integer}" unless exitcode.is_a?(Integer)
|
18
|
+
|
19
|
+
@diagram = diagram
|
20
|
+
@stdout = stdout
|
21
|
+
@stderr = stderr
|
22
|
+
@exitcode = exitcode
|
23
|
+
@logger = Logger.init
|
24
|
+
end
|
25
|
+
|
26
|
+
def without_xml_prologue
|
27
|
+
return @stdout if @stdout.nil? || @stdout.empty?
|
28
|
+
|
29
|
+
xml_prologue_start = '<?xml'
|
30
|
+
xml_prologue_end = '?>'
|
31
|
+
|
32
|
+
start_index = @stdout.index(xml_prologue_start)
|
33
|
+
|
34
|
+
return @stdout if start_index.nil?
|
35
|
+
|
36
|
+
end_index = @stdout.index(xml_prologue_end, xml_prologue_start.length)
|
37
|
+
|
38
|
+
return @stdout if end_index.nil?
|
39
|
+
|
40
|
+
end_index += xml_prologue_end.length
|
41
|
+
|
42
|
+
@stdout.slice! start_index, end_index
|
43
|
+
|
44
|
+
@stdout
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid?
|
48
|
+
return true if @exitcode.zero? || @stderr.nil? || @stderr.empty?
|
49
|
+
|
50
|
+
# If stderr is not empty, but contains the string 'CoreText note:',
|
51
|
+
# the error is caused by a bug in Java, and should be ignored.
|
52
|
+
# Circumvents https://bugs.openjdk.java.net/browse/JDK-8244621
|
53
|
+
@stderr.include?('CoreText note:')
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate
|
57
|
+
raise PlantUmlError, self unless valid?
|
58
|
+
|
59
|
+
return if @stderr.nil? || @stderr.empty?
|
60
|
+
|
61
|
+
@logger.debug ' kramdown-plantuml: PlantUML log:'
|
62
|
+
@logger.debug_with_prefix ' kramdown-plantuml: ', @stderr
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|