kramdown-plantuml 1.0.5 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|