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.
@@ -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 = "kramdown-plantuml allows you to use PlantUML syntax within fenced code blocks with Kramdown (Jekyll's default Markdown parser)"
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.3.0')
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 'rubocop', '~> 0.92'
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