From d4c902bc1667e31e3a6e50215295553d6d92316d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Wed, 10 Nov 2021 18:15:57 +0100 Subject: [PATCH 01/14] DX-1622: Remove XML prologue with REXML --- lib/kramdown-plantuml/plantuml_result.rb | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/kramdown-plantuml/plantuml_result.rb b/lib/kramdown-plantuml/plantuml_result.rb index 5108b930..a1ad5ab1 100644 --- a/lib/kramdown-plantuml/plantuml_result.rb +++ b/lib/kramdown-plantuml/plantuml_result.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'rexml/document' require_relative 'log_wrapper' require_relative 'plantuml_error' require_relative 'diagram' @@ -26,22 +27,8 @@ def initialize(diagram, stdout, stderr, exitcode) def without_xml_prologue return @stdout if @stdout.nil? || @stdout.empty? - xml_prologue_start = '' - - start_index = @stdout.index(xml_prologue_start) - - return @stdout if start_index.nil? - - end_index = @stdout.index(xml_prologue_end, xml_prologue_start.length) - - return @stdout if end_index.nil? - - end_index += xml_prologue_end.length - - @stdout.slice! start_index, end_index - - @stdout + doc = REXML::Document.new @stdout + doc.root.to_s end def valid? From 297c401ff5293671140f269d6cbbde8508ed7c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Wed, 10 Nov 2021 18:45:27 +0100 Subject: [PATCH 02/14] DX-1622: Rename diagram.* to network-diagram.* --- README.md | 2 +- spec/diagram_spec.rb | 2 +- spec/examples/{diagram.plantuml => network-diagram.puml} | 0 spec/examples/{diagram.svg => network-diagram.svg} | 0 spec/jekyll_provider_spec.rb | 2 +- spec/kramdown_html_spec.rb | 2 +- 6 files changed, 4 insertions(+), 4 deletions(-) rename spec/examples/{diagram.plantuml => network-diagram.puml} (100%) rename spec/examples/{diagram.svg => network-diagram.svg} (100%) diff --git a/README.md b/README.md index c223146d..5b183779 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ agreement][cla]. [codacy]: https://www.codacy.com/gh/SwedbankPay/kramdown-plantuml/dashboard?utm_source=github.com&utm_medium=referral&utm_content=SwedbankPay/kramdown-plantuml&utm_campaign=Badge_Grade [codecov-badge]: https://codecov.io/gh/SwedbankPay/kramdown-plantuml/branch/main/graph/badge.svg?token=U3QJLVG3HY [codecov]: https://codecov.io/gh/SwedbankPay/kramdown-plantuml/ -[diagram-svg]: ./spec/examples/diagram.svg +[diagram-svg]: ./spec/examples/network-diagram.svg [fenced]: https://www.markdownguide.org/extended-syntax/#syntax-highlighting [fork]: https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo [gem-badge]: https://badge.fury.io/rb/kramdown-plantuml.svg diff --git a/spec/diagram_spec.rb b/spec/diagram_spec.rb index b10151e3..c1ed16f9 100644 --- a/spec/diagram_spec.rb +++ b/spec/diagram_spec.rb @@ -6,7 +6,7 @@ Diagram = ::Kramdown::PlantUml::Diagram describe Diagram do - plantuml_content = File.read(File.join(__dir__, 'examples', 'diagram.plantuml')) + plantuml_content = File.read(File.join(__dir__, 'examples', 'network-diagram.puml')) describe '#convert_to_svg' do context 'gracefully fails' do diff --git a/spec/examples/diagram.plantuml b/spec/examples/network-diagram.puml similarity index 100% rename from spec/examples/diagram.plantuml rename to spec/examples/network-diagram.puml diff --git a/spec/examples/diagram.svg b/spec/examples/network-diagram.svg similarity index 100% rename from spec/examples/diagram.svg rename to spec/examples/network-diagram.svg diff --git a/spec/jekyll_provider_spec.rb b/spec/jekyll_provider_spec.rb index e1739875..d69389f9 100644 --- a/spec/jekyll_provider_spec.rb +++ b/spec/jekyll_provider_spec.rb @@ -8,7 +8,7 @@ JekyllProvider = ::Kramdown::PlantUml::JekyllProvider describe JekyllProvider do - let (:plantuml_file_contents) { File.read(File.join(__dir__, 'examples', 'diagram.plantuml')) } + let (:plantuml_file_contents) { File.read(File.join(__dir__, 'examples', 'network-diagram.puml')) } let (:plantuml) { nil } let (:options) { Options.new } subject { JekyllProvider } diff --git a/spec/kramdown_html_spec.rb b/spec/kramdown_html_spec.rb index 1bb874af..409bff73 100644 --- a/spec/kramdown_html_spec.rb +++ b/spec/kramdown_html_spec.rb @@ -8,7 +8,7 @@ let (:options) { { input: 'GFM' } } subject do - diagram = File.read(File.join(__dir__, 'examples', 'diagram.plantuml')) + diagram = File.read(File.join(__dir__, 'examples', 'network-diagram.puml')) document = "```plantuml\n#{diagram}\n```" Kramdown::Document.new(document, options).to_html end From 14aca2196b413d6e7e4dd30c3e3a855451d0e212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Thu, 11 Nov 2021 23:01:10 +0100 Subject: [PATCH 03/14] DX-1622: Rename diagram.rb to plantuml_diagram.rb --- lib/kramdown-plantuml/{diagram.rb => plantuml_diagram.rb} | 0 lib/kramdown_html.rb | 2 +- spec/executor_spec.rb | 2 +- spec/{diagram_spec.rb => plantuml_diagram_spec.rb} | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename lib/kramdown-plantuml/{diagram.rb => plantuml_diagram.rb} (100%) rename spec/{diagram_spec.rb => plantuml_diagram_spec.rb} (98%) diff --git a/lib/kramdown-plantuml/diagram.rb b/lib/kramdown-plantuml/plantuml_diagram.rb similarity index 100% rename from lib/kramdown-plantuml/diagram.rb rename to lib/kramdown-plantuml/plantuml_diagram.rb diff --git a/lib/kramdown_html.rb b/lib/kramdown_html.rb index a7a40768..4266e6c3 100644 --- a/lib/kramdown_html.rb +++ b/lib/kramdown_html.rb @@ -5,7 +5,7 @@ require_relative 'kramdown-plantuml/log_wrapper' require_relative 'kramdown-plantuml/plantuml_error' require_relative 'kramdown-plantuml/options' -require_relative 'kramdown-plantuml/diagram' +require_relative 'kramdown-plantuml/plantuml_diagram' require_relative 'kramdown-plantuml/jekyll_provider' module Kramdown diff --git a/spec/executor_spec.rb b/spec/executor_spec.rb index 3db3a2d8..aa53a69c 100644 --- a/spec/executor_spec.rb +++ b/spec/executor_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'rspec/its' -require 'kramdown-plantuml/diagram' +require 'kramdown-plantuml/plantuml_diagram' Executor = ::Kramdown::PlantUml::Executor diff --git a/spec/diagram_spec.rb b/spec/plantuml_diagram_spec.rb similarity index 98% rename from spec/diagram_spec.rb rename to spec/plantuml_diagram_spec.rb index c1ed16f9..c90a1187 100644 --- a/spec/diagram_spec.rb +++ b/spec/plantuml_diagram_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'rspec/its' -require 'kramdown-plantuml/diagram' +require 'kramdown-plantuml/plantuml_diagram' Diagram = ::Kramdown::PlantUml::Diagram From 91f8b21eb8a790d72483de63c70900e8d5e283c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Thu, 11 Nov 2021 23:06:22 +0100 Subject: [PATCH 04/14] DX-1622: Add SvgDiagram class - Rename `Diagram` to `PlantUmlDiagram`. - Refactor SVG related stuff from `PlantUmlDiagram` to a new `SvgDiagram` class. --- lib/kramdown-plantuml/executor.rb | 2 +- .../jekyll_page_processor.rb | 4 +- lib/kramdown-plantuml/plantuml_diagram.rb | 31 ++++------- lib/kramdown-plantuml/plantuml_error.rb | 10 ++-- lib/kramdown-plantuml/plantuml_result.rb | 22 ++++---- lib/kramdown-plantuml/svg_diagram.rb | 54 +++++++++++++++++++ lib/kramdown_html.rb | 4 +- spec/executor_spec.rb | 2 +- spec/plantuml_diagram_spec.rb | 22 ++++---- spec/plantuml_error_spec.rb | 4 +- spec/plantuml_result_spec.rb | 12 +++-- 11 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 lib/kramdown-plantuml/svg_diagram.rb diff --git a/lib/kramdown-plantuml/executor.rb b/lib/kramdown-plantuml/executor.rb index 59e3bb36..575c4e76 100644 --- a/lib/kramdown-plantuml/executor.rb +++ b/lib/kramdown-plantuml/executor.rb @@ -26,7 +26,7 @@ def initialize def execute(diagram) raise ArgumentError, 'diagram cannot be nil' if diagram.nil? - raise ArgumentError, "diagram must be a #{Diagram}" unless diagram.is_a?(Diagram) + raise ArgumentError, "diagram must be a #{PlantUmlDiagram}" unless diagram.is_a?(PlantUmlDiagram) cmd = "java -Djava.awt.headless=true -jar #{@plantuml_jar_file} -tsvg -failfast -pipe #{debug_args}" diff --git a/lib/kramdown-plantuml/jekyll_page_processor.rb b/lib/kramdown-plantuml/jekyll_page_processor.rb index 23a3d092..14aa0e13 100644 --- a/lib/kramdown-plantuml/jekyll_page_processor.rb +++ b/lib/kramdown-plantuml/jekyll_page_processor.rb @@ -91,8 +91,8 @@ def replace_needle(json) def decode_and_convert(hash, options) encoded_plantuml = hash['plantuml'] plantuml = HTMLEntities.new.decode encoded_plantuml - diagram = ::Kramdown::PlantUml::Diagram.new(plantuml, options) - diagram.convert_to_svg + diagram = ::Kramdown::PlantUml::PlantUmlDiagram.new(plantuml, options) + diagram.svg end def logger diff --git a/lib/kramdown-plantuml/plantuml_diagram.rb b/lib/kramdown-plantuml/plantuml_diagram.rb index 8a9b29f2..cd70851b 100644 --- a/lib/kramdown-plantuml/plantuml_diagram.rb +++ b/lib/kramdown-plantuml/plantuml_diagram.rb @@ -1,17 +1,18 @@ # frozen_string_literal: true -require_relative 'version' -require_relative 'theme' +require_relative 'executor' +require_relative 'log_wrapper' require_relative 'options' require_relative 'plantuml_error' -require_relative 'log_wrapper' -require_relative 'executor' +require_relative 'svg_diagram' +require_relative 'theme' +require_relative 'version' module Kramdown module PlantUml # Represents a PlantUML diagram that can be converted to SVG. - class Diagram - attr_reader :theme, :plantuml, :result + class PlantUmlDiagram + attr_reader :theme, :plantuml, :result, :options def initialize(plantuml, options) raise ArgumentError, 'options cannot be nil' if options.nil? @@ -25,15 +26,13 @@ def initialize(plantuml, options) @logger.warn 'PlantUML diagram is empty' if @plantuml.nil? || @plantuml.empty? end - def convert_to_svg - return @svg unless @svg.nil? - return @plantuml if @plantuml.nil? || @plantuml.empty? + def svg + return @svg_diagram unless @svg_diagram.nil? @plantuml = @theme.apply(@plantuml) log(plantuml) @result = @executor.execute(self) - @result.validate - @svg = wrap(@result.without_xml_prologue) + @svg_diagram = SvgDiagram.new(@result) rescue StandardError => e raise e if @options.raise_errors? @@ -42,16 +41,6 @@ def convert_to_svg private - def wrap(svg) - theme_class = @theme.name ? "theme-#{@theme.name}" : '' - class_name = "plantuml #{theme_class}".strip - - wrapper_element_start = "
" - wrapper_element_end = '
' - - "#{wrapper_element_start}#{svg}#{wrapper_element_end}" - end - def log(plantuml) @logger.debug 'PlantUML converting diagram:' @logger.debug_multiline plantuml diff --git a/lib/kramdown-plantuml/plantuml_error.rb b/lib/kramdown-plantuml/plantuml_error.rb index 1cc8adeb..cc24beb9 100644 --- a/lib/kramdown-plantuml/plantuml_error.rb +++ b/lib/kramdown-plantuml/plantuml_error.rb @@ -31,11 +31,11 @@ def create_message(result) end def header(result) - if theme_not_found?(result) && !result.diagram.nil? && !result.diagram.theme.nil? + if theme_not_found?(result) && !result.plantuml_diagram.nil? && !result.plantuml_diagram.theme.nil? return <<~HEADER Conversion of the following PlantUML result failed because the - theme '#{result.diagram.theme.name}' can't be found in the directory - '#{result.diagram.theme.directory}': + theme '#{result.plantuml_diagram.theme.name}' can't be found in the directory + '#{result.plantuml_diagram.theme.directory}': HEADER end @@ -50,9 +50,9 @@ def theme_not_found?(result) end def plantuml(result) - return nil if result.nil? || result.diagram.nil? + return nil if result.nil? || result.plantuml_diagram.nil? - result.diagram.plantuml + result.plantuml_diagram.plantuml end def result(result) diff --git a/lib/kramdown-plantuml/plantuml_result.rb b/lib/kramdown-plantuml/plantuml_result.rb index a1ad5ab1..779b2eed 100644 --- a/lib/kramdown-plantuml/plantuml_result.rb +++ b/lib/kramdown-plantuml/plantuml_result.rb @@ -1,34 +1,30 @@ # frozen_string_literal: true -require 'rexml/document' require_relative 'log_wrapper' require_relative 'plantuml_error' -require_relative 'diagram' +require_relative 'svg_diagram' module Kramdown module PlantUml # Executes the PlantUML Java application. class PlantUmlResult - attr_reader :diagram, :stdout, :stderr, :exitcode + attr_reader :plantuml_diagram, :stdout, :stderr, :exitcode - def initialize(diagram, stdout, stderr, exitcode) - raise ArgumentError, 'diagram cannot be nil' if diagram.nil? - raise ArgumentError, "diagram must be a #{Diagram}" unless diagram.is_a?(Diagram) + def initialize(plantuml_diagram, stdout, stderr, exitcode) + raise ArgumentError, 'diagram cannot be nil' if plantuml_diagram.nil? + raise ArgumentError, "diagram must be a #{PlantUmlDiagram}" unless plantuml_diagram.is_a?(PlantUmlDiagram) raise ArgumentError, 'exitcode cannot be nil' if exitcode.nil? raise ArgumentError, "exitcode must be a #{Integer}" unless exitcode.is_a?(Integer) - @diagram = diagram + @plantuml_diagram = plantuml_diagram @stdout = stdout @stderr = stderr @exitcode = exitcode @logger = LogWrapper.init end - def without_xml_prologue - return @stdout if @stdout.nil? || @stdout.empty? - - doc = REXML::Document.new @stdout - doc.root.to_s + def svg_diagram + @plantuml_diagram.svg end def valid? @@ -40,7 +36,7 @@ def valid? @stderr.include?('CoreText note:') end - def validate + def validate! raise PlantUmlError, self unless valid? return if @stderr.nil? || @stderr.empty? diff --git a/lib/kramdown-plantuml/svg_diagram.rb b/lib/kramdown-plantuml/svg_diagram.rb new file mode 100644 index 00000000..f9e99114 --- /dev/null +++ b/lib/kramdown-plantuml/svg_diagram.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rexml/document' +require_relative 'plantuml_diagram' + +module Kramdown + module PlantUml + # A diagram in SVG format. + class SvgDiagram + attr_accessor :width, :height, :style + + def initialize(plantuml_result) + raise ArgumentError, 'plantuml_result cannot be nil' if plantuml_result.nil? + raise ArgumentError, "plantuml_result must be a #{PlantUmlResult}" unless plantuml_result.is_a?(PlantUmlResult) + + plantuml_result.validate! + svg = plantuml_result.stdout + @doc = REXML::Document.new svg + @source = plantuml_result.plantuml_diagram + end + + def to_s + root = tweak_attributes(@doc.root) + wrap(root.to_s) + end + + private + + def wrap(svg) + return svg if svg.nil? || svg.empty? + + # TODO: Replace with proper XML DOM operations. + theme_class = @source.theme.name ? "theme-#{@source.theme.name}" : '' + class_name = "plantuml #{theme_class}".strip + + wrapper_element_start = "
" + wrapper_element_end = '
' + + "#{wrapper_element_start}#{svg}#{wrapper_element_end}" + end + + def tweak_attributes(element) + return element + # return element if element.nil? || !element.is_a?(REXML::Element) + + # TODO: Figure out how to configure removal of the attributes, we can't use nil + element.attributes.get_attribute('width').remove if @width.nil? + element.attributes.get_attribute('height').remove if @height.nil? + element.attributes.get_attribute('style').remove if @style.nil? + element + end + end + end +end diff --git a/lib/kramdown_html.rb b/lib/kramdown_html.rb index 4266e6c3..09c8d1ae 100644 --- a/lib/kramdown_html.rb +++ b/lib/kramdown_html.rb @@ -38,8 +38,8 @@ def plantuml?(element) end def convert_plantuml(plantuml, options) - diagram = ::Kramdown::PlantUml::Diagram.new(plantuml, options) - diagram.convert_to_svg + diagram = ::Kramdown::PlantUml::PlantUmlDiagram.new(plantuml, options) + diagram.svg.to_s rescue StandardError => e raise e if options.raise_errors? diff --git a/spec/executor_spec.rb b/spec/executor_spec.rb index aa53a69c..69cba2ba 100644 --- a/spec/executor_spec.rb +++ b/spec/executor_spec.rb @@ -30,7 +30,7 @@ end context 'diagram is not Diagram' do - it { expect { subject.execute('') }.to raise_error(ArgumentError, "diagram must be a #{Diagram}") } + it { expect { subject.execute('') }.to raise_error(ArgumentError, "diagram must be a #{Kramdown::PlantUml::PlantUmlDiagram}") } end end end diff --git a/spec/plantuml_diagram_spec.rb b/spec/plantuml_diagram_spec.rb index c90a1187..8cdcd41d 100644 --- a/spec/plantuml_diagram_spec.rb +++ b/spec/plantuml_diagram_spec.rb @@ -3,18 +3,18 @@ require 'rspec/its' require 'kramdown-plantuml/plantuml_diagram' -Diagram = ::Kramdown::PlantUml::Diagram +PlantUmlDiagram ||= ::Kramdown::PlantUml::PlantUmlDiagram -describe Diagram do +describe PlantUmlDiagram do plantuml_content = File.read(File.join(__dir__, 'examples', 'network-diagram.puml')) - describe '#convert_to_svg' do + describe '#svg' do context 'gracefully fails' do - subject { Diagram.new(plantuml, Options.new).convert_to_svg } + subject { PlantUmlDiagram.new(plantuml, Options.new).svg.to_s } context 'with nil plantuml' do let(:plantuml) { nil } - it { is_expected.to be_nil } + it { is_expected.to be_empty } end context 'with empty plantuml' do @@ -24,7 +24,7 @@ end context 'successfully converts' do - before(:all) { @converted_svg = Diagram.new(plantuml_content, Options.new).convert_to_svg } + before(:all) { @converted_svg = PlantUmlDiagram.new(plantuml_content, Options.new).svg.to_s } subject { @converted_svg } it { @@ -62,20 +62,20 @@ end context 'fails properly' do - subject { Diagram.new(plantuml, options) } + subject { PlantUmlDiagram.new(plantuml, options) } let(:hash) { nil } let(:options) { Options.new(hash) } context 'with invalid PlantUML' do let(:plantuml) { 'INVALID!' } - its(:convert_to_svg) do + its(:svg) do will raise_error(Kramdown::PlantUml::PlantUmlError, /INVALID!/) end context ('with raise_errors: false') do let(:hash) { { plantuml: { raise_errors: false } } } - its(:convert_to_svg) { will_not raise_error } + its(:svg) { will_not raise_error } end end @@ -83,13 +83,13 @@ let(:plantuml) { "@startuml\n@enduml" } let(:hash) { { plantuml: { theme: { name: 'xyz', directory: 'assets' } } } } - its(:convert_to_svg) do + its(:svg) do will raise_error(Kramdown::PlantUml::PlantUmlError, /theme 'xyz' can't be found in the directory 'assets'/) end context ('with raise_errors: false') do let(:hash) { { plantuml: { raise_errors: false } } } - its(:convert_to_svg) { will_not raise_error } + its(:svg) { will_not raise_error } end end end diff --git a/spec/plantuml_error_spec.rb b/spec/plantuml_error_spec.rb index 247fc1fa..55c3efb4 100644 --- a/spec/plantuml_error_spec.rb +++ b/spec/plantuml_error_spec.rb @@ -5,7 +5,7 @@ require 'kramdown-plantuml/plantuml_error' Options = ::Kramdown::PlantUml::Options -Diagram = ::Kramdown::PlantUml::Diagram +PlantUmlDiagram ||= Kramdown::PlantUml::PlantUmlDiagram PlantUmlError = ::Kramdown::PlantUml::PlantUmlError PlantUmlResult = ::Kramdown::PlantUml::PlantUmlResult @@ -14,7 +14,7 @@ let(:plantuml) { 'some plantuml' } let(:options) { Options.new } let(:exitcode) { 1 } - let(:diagram) { Diagram.new(plantuml, options) } + let(:diagram) { PlantUmlDiagram.new(plantuml, options) } let(:result) { PlantUmlResult.new(diagram, '', stderr, exitcode) } subject { PlantUmlError.new(result) } diff --git a/spec/plantuml_result_spec.rb b/spec/plantuml_result_spec.rb index 89ae6bf0..2eb02cc7 100644 --- a/spec/plantuml_result_spec.rb +++ b/spec/plantuml_result_spec.rb @@ -9,7 +9,7 @@ let (:stderr) { nil } subject { - diagram = ::Kramdown::PlantUml::Diagram.new("@startuml\n@enduml", Options.new) + diagram = ::Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", Options.new) result = ::Kramdown::PlantUml::PlantUmlResult.new(diagram, '', stderr, exitcode) result.valid? } @@ -46,7 +46,7 @@ describe '#initialize' do subject { - diagram = ::Kramdown::PlantUml::Diagram.new("@startuml\n@enduml", Options.new) + diagram = ::Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", Options.new) ::Kramdown::PlantUml::PlantUmlResult.new(diagram, stdout, stderr, exitcode) } @@ -55,8 +55,12 @@ let (:stdout) { 'some stdout' } let (:exitcode) { 1337 } - its(:diagram) { - is_expected.to be_a ::Kramdown::PlantUml::Diagram + its(:plantuml_diagram) { + is_expected.to be_a ::Kramdown::PlantUml::PlantUmlDiagram + } + + its(:svg_diagram) { + is_expected.to be_a ::Kramdown::PlantUml::SvgDiagram } its(:stdout) { From 55edf771b66ecfe47c7f647e0ae1a95d28651653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Thu, 11 Nov 2021 23:07:30 +0100 Subject: [PATCH 05/14] DX-1622: Global tweaks --- spec/jekyll_page_processor_spec.rb | 2 +- spec/jekyll_provider_spec.rb | 2 +- spec/options_spec.rb | 2 +- spec/plantuml_error_spec.rb | 6 +++--- spec/theme_spec.rb | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/jekyll_page_processor_spec.rb b/spec/jekyll_page_processor_spec.rb index a4746179..97d6db98 100644 --- a/spec/jekyll_page_processor_spec.rb +++ b/spec/jekyll_page_processor_spec.rb @@ -4,7 +4,7 @@ require 'kramdown-plantuml/options' require 'kramdown-plantuml/jekyll_page_processor' -Options = Kramdown::PlantUml::Options +Options ||= Kramdown::PlantUml::Options JekyllPageProcessor = ::Kramdown::PlantUml::JekyllPageProcessor describe JekyllPageProcessor do diff --git a/spec/jekyll_provider_spec.rb b/spec/jekyll_provider_spec.rb index d69389f9..2d14f768 100644 --- a/spec/jekyll_provider_spec.rb +++ b/spec/jekyll_provider_spec.rb @@ -4,7 +4,7 @@ require 'kramdown-plantuml/options' require 'kramdown-plantuml/jekyll_provider' -Options = Kramdown::PlantUml::Options +Options ||= Kramdown::PlantUml::Options JekyllProvider = ::Kramdown::PlantUml::JekyllProvider describe JekyllProvider do diff --git a/spec/options_spec.rb b/spec/options_spec.rb index cc2d5791..f4228599 100644 --- a/spec/options_spec.rb +++ b/spec/options_spec.rb @@ -3,7 +3,7 @@ require 'rspec/its' require 'kramdown-plantuml/options' -Options = Kramdown::PlantUml::Options +Options ||= Kramdown::PlantUml::Options describe Options do let(:hash) { {} } diff --git a/spec/plantuml_error_spec.rb b/spec/plantuml_error_spec.rb index 55c3efb4..150fa005 100644 --- a/spec/plantuml_error_spec.rb +++ b/spec/plantuml_error_spec.rb @@ -4,10 +4,10 @@ require 'kramdown-plantuml/options' require 'kramdown-plantuml/plantuml_error' -Options = ::Kramdown::PlantUml::Options +Options ||= Kramdown::PlantUml::Options PlantUmlDiagram ||= Kramdown::PlantUml::PlantUmlDiagram -PlantUmlError = ::Kramdown::PlantUml::PlantUmlError -PlantUmlResult = ::Kramdown::PlantUml::PlantUmlResult +PlantUmlError = Kramdown::PlantUml::PlantUmlError +PlantUmlResult = Kramdown::PlantUml::PlantUmlResult describe PlantUmlError do describe '#initialize' do diff --git a/spec/theme_spec.rb b/spec/theme_spec.rb index 58acb92f..4b6cd6d5 100644 --- a/spec/theme_spec.rb +++ b/spec/theme_spec.rb @@ -5,7 +5,7 @@ require 'kramdown-plantuml/theme' Theme = Kramdown::PlantUml::Theme -Options = Kramdown::PlantUml::Options +Options ||= Kramdown::PlantUml::Options describe Theme do describe '#initialize' do From b5093def02f582af1be7a3cc826b9e8d5e899609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Thu, 11 Nov 2021 23:24:44 +0100 Subject: [PATCH 06/14] DX-1622: Make :width, :height and :style configurable --- kramdown-plantuml.gemspec | 1 + lib/kramdown-plantuml/none_s.rb | 17 ++++ lib/kramdown-plantuml/options.rb | 25 +++++- lib/kramdown-plantuml/style_builder.rb | 38 +++++++++ lib/kramdown-plantuml/svg_diagram.rb | 102 ++++++++++++++++++++---- spec/executor_spec.rb | 2 +- spec/none_s_spec.rb | 27 +++++++ spec/spec_helper.rb | 4 + spec/svg_diagram_spec.rb | 106 +++++++++++++++++++++++++ 9 files changed, 305 insertions(+), 17 deletions(-) create mode 100644 lib/kramdown-plantuml/none_s.rb create mode 100644 lib/kramdown-plantuml/style_builder.rb create mode 100644 spec/none_s_spec.rb create mode 100644 spec/svg_diagram_spec.rb diff --git a/kramdown-plantuml.gemspec b/kramdown-plantuml.gemspec index 7260872c..c86819da 100644 --- a/kramdown-plantuml.gemspec +++ b/kramdown-plantuml.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec', '~> 3.2' + spec.add_development_dependency 'rspec-html-matchers', '>= 0.9' spec.add_development_dependency 'rspec-its', '~> 1.3' spec.add_development_dependency 'rubocop', '~> 1.12' spec.add_development_dependency 'rubocop-rake', '~> 0.6' diff --git a/lib/kramdown-plantuml/none_s.rb b/lib/kramdown-plantuml/none_s.rb new file mode 100644 index 00000000..7ff444ef --- /dev/null +++ b/lib/kramdown-plantuml/none_s.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Ruby's Object class. +class Object + # Performs a case insensitive, trimmed comparison of the Object and the + # String 'none' and Symbol :none. Returns true if the comparison is true, + # otherwise false. + # + # @return [Boolean] True if the Object is equal to 'none' or :none, + # otherwise false. + def none_s? + return false if nil? + return true if self == :none + + to_s.strip.casecmp?('none') + end +end diff --git a/lib/kramdown-plantuml/options.rb b/lib/kramdown-plantuml/options.rb index bd9ae51c..e8593868 100644 --- a/lib/kramdown-plantuml/options.rb +++ b/lib/kramdown-plantuml/options.rb @@ -1,18 +1,20 @@ # frozen_string_literal: true +require_relative 'none_s' require_relative 'log_wrapper' module Kramdown module PlantUml # Options for PlantUML processing class Options - attr_reader :theme_name, :theme_directory + attr_reader :theme_name, :theme_directory, :width, :height, :style def initialize(options_hash = {}) @logger = LogWrapper.init @options = massage(options_hash) || {} @raise_errors = extract_raise_errors(@options) extract_theme_options(@options) + extract_style_options(@options) end def raise_errors? @@ -64,6 +66,27 @@ def extract_raise_errors(options) boolean(raise_errors, true) end + def extract_style_options(options) + return if options.nil? || options.empty? + + set_instance_property(:width, options) + set_instance_property(:height, options) + set_instance_property(:style, options) + + # @width = options.key?(:width) ? options[:width] : nil + # @height = options.key?(:height) ? options[:height] : nil + # @style = options.key?(:style) ? options[:style] : nil + end + + def set_instance_property(name, options) + return unless options.key? name + + value = options[name] + value = :none if value.none_s? + prop_name = "@#{name}".to_sym + instance_variable_set(prop_name, value) + end + def massage(options_hash) if options_hash.nil? || !options_hash.is_a?(Hash) || options_hash.empty? @logger.debug 'No options provided' diff --git a/lib/kramdown-plantuml/style_builder.rb b/lib/kramdown-plantuml/style_builder.rb new file mode 100644 index 00000000..6a546cf0 --- /dev/null +++ b/lib/kramdown-plantuml/style_builder.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Kramdown + module PlantUml + # Builds a CSS style string from a hash of style properties. + class StyleBuilder + def initialize + @hash = {} + end + + def set(key, value) + case key + when :width, :height + @hash[key] = value + else + set_style(value) + end + end + + def to_s + @hash.map { |key, value| "#{key}:#{value}" }.join(';') + end + + private + + def set_style(style) + return if style.nil? || style.strip.empty? + + style.split(';').each do |pair| + key, value = pair.split(':') + key = key.strip.to_sym + value = value.strip + @hash[key] = value + end + end + end + end +end diff --git a/lib/kramdown-plantuml/svg_diagram.rb b/lib/kramdown-plantuml/svg_diagram.rb index f9e99114..b4ff94a5 100644 --- a/lib/kramdown-plantuml/svg_diagram.rb +++ b/lib/kramdown-plantuml/svg_diagram.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true require 'rexml/document' +require_relative 'none_s' +require_relative 'style_builder' require_relative 'plantuml_diagram' module Kramdown module PlantUml # A diagram in SVG format. class SvgDiagram - attr_accessor :width, :height, :style - def initialize(plantuml_result) raise ArgumentError, 'plantuml_result cannot be nil' if plantuml_result.nil? raise ArgumentError, "plantuml_result must be a #{PlantUmlResult}" unless plantuml_result.is_a?(PlantUmlResult) @@ -17,15 +17,98 @@ def initialize(plantuml_result) svg = plantuml_result.stdout @doc = REXML::Document.new svg @source = plantuml_result.plantuml_diagram + @style_builder = StyleBuilder.new + transfer_options(%i[style width height], plantuml_result) end def to_s - root = tweak_attributes(@doc.root) - wrap(root.to_s) + wrap(@doc.root.to_s) + end + + def width + get_xml_attribute(:width) + end + + def height + get_xml_attribute(:height) + end + + def style + get_xml_attribute(:style) end private + def get_xml_attribute(attribute_name) + return nil if @doc.root.nil? + + name = attribute_name.to_s + value = @doc.root.attributes[name] + value.nil? || value.none_s? ? :none : value + end + + def manipulate_xml_attribute(attribute_name, value) + puts "Setting '#{attribute_name}' to '#{value}' from '#{send(attribute_name)}.'" + + if value.none_s? + @doc.root.attributes.get_attribute(attribute_name.to_s).remove + elsif !value.nil? && value.is_a?(String) && !value.strip.empty? + set_xml_attribute(attribute_name, value) + end + end + + def set_xml_attribute(attribute_name, value) + name = attribute_name.to_s + @doc.root.attributes[name] = value + @style_builder.set(attribute_name, value) + + return if attribute_name == :style || style == :none + + style = @style_builder.to_s + + puts "Setting 'style' to '#{style}' from '#{self.style}.'" + + set_xml_attribute(:style, style) + end + + def build_style(attribute_name, value) + style = self.style + styles = [] + styles << "width:#{width}" unless width.none_s? + styles << "height:#{height}" unless height.none_s? + + return styles.join(';') if style.nil? || style.strip.empty? + + styles << style + + return styles.join(';') unless style.include?(attribute_name) + + replacement = "#{attribute_name}:#{value}px;" + regex = /#{attribute_name}\s?:\s?[^;]*;/ + style.gsub(regex, replacement) + end + + def transfer_options(attributes, plantuml_result) + return if @doc.root.nil? \ + || plantuml_result.nil? \ + || plantuml_result.plantuml_diagram.nil? \ + || plantuml_result.plantuml_diagram.options.nil? + + options = plantuml_result.plantuml_diagram.options + + attributes.each do |attribute| + options.public_send(attribute).tap do |option_value| + next if option_value.nil? + + option_value = option_value.to_s + + next if option_value.strip.empty? + + manipulate_xml_attribute(attribute, option_value) + end + end + end + def wrap(svg) return svg if svg.nil? || svg.empty? @@ -38,17 +121,6 @@ def wrap(svg) "#{wrapper_element_start}#{svg}#{wrapper_element_end}" end - - def tweak_attributes(element) - return element - # return element if element.nil? || !element.is_a?(REXML::Element) - - # TODO: Figure out how to configure removal of the attributes, we can't use nil - element.attributes.get_attribute('width').remove if @width.nil? - element.attributes.get_attribute('height').remove if @height.nil? - element.attributes.get_attribute('style').remove if @style.nil? - element - end end end end diff --git a/spec/executor_spec.rb b/spec/executor_spec.rb index 69cba2ba..0b892743 100644 --- a/spec/executor_spec.rb +++ b/spec/executor_spec.rb @@ -29,7 +29,7 @@ it { expect { subject.execute(nil) }.to raise_error(ArgumentError, 'diagram cannot be nil') } end - context 'diagram is not Diagram' do + context 'diagram is not PlantUmlDiagram' do it { expect { subject.execute('') }.to raise_error(ArgumentError, "diagram must be a #{Kramdown::PlantUml::PlantUmlDiagram}") } end end diff --git a/spec/none_s_spec.rb b/spec/none_s_spec.rb new file mode 100644 index 00000000..42234b10 --- /dev/null +++ b/spec/none_s_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: false + +require 'kramdown-plantuml/none_s' + +describe Object do + describe '#none_s?' do + subject { value.none_s? } + + context 'with invalid values' do + [{}, [], nil, '', ' ', 'a', '0', '1', 'true', 'false', 'nil', '[]', '{}'].each do |v| + context "'#{v}'" do + let (:value) { v } + it { is_expected.to be false } + end + end + end + + context 'with valid values' do + ['none', ' none ', 'NONE', 'NoNe', :none].each do |v| + context "'#{v}'" do + let (:value) { v } + it { is_expected.to be true } + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1a38ad5f..789eb831 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'rspec-html-matchers' + # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause @@ -16,6 +18,8 @@ # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| + config.include RSpecHtmlMatchers + # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. diff --git a/spec/svg_diagram_spec.rb b/spec/svg_diagram_spec.rb new file mode 100644 index 00000000..029b5c04 --- /dev/null +++ b/spec/svg_diagram_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: false + +require 'rspec/its' +require 'kramdown-plantuml/svg_diagram' + +Options ||= Kramdown::PlantUml::Options +SvgDiagram ||= ::Kramdown::PlantUml::SvgDiagram + +describe SvgDiagram do + describe '#initialize' do + context 'minimal diagram' do + before(:all) { @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", Options.new).svg } + subject { @svg } + + its(:width) { is_expected.not_to be_nil } + its(:height) { is_expected.not_to be_nil } + its(:style) { is_expected.not_to be_nil } + end + + context 'with string options' do + before(:all) { + options = Options.new(plantuml: { width: '1337px', height: '1234px', style: 'border: 7px solid red' }) + @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg + } + subject { @svg } + + its(:width) { is_expected.to eq '1337px' } + its(:height) { is_expected.to eq '1234px' } + its(:style) { is_expected.to eq 'border:7px solid red;width:1337px;height:1234px' } + its(:to_s) { + is_expected.to have_tag('svg', with: { + width: '1337px', + height: '1234px', + style: 'border:7px solid red;width:1337px;height:1234px' + }) + } + end + + context 'with integer options' do + before(:all) { + options = Options.new(plantuml: { width: 100, height: 200 }) + @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg + } + subject { @svg } + + its(:width) { is_expected.to eq '100' } + its(:height) { is_expected.to eq '200' } + its(:style) { is_expected.to eq 'width:100;height:200' } + its(:to_s) { + is_expected.to have_tag('svg', with: { + width: '100', + height: '200', + style: 'width:100;height:200' + }) + } + end + + context 'with none' do + before(:all) { + options = Options.new(plantuml: { width: 'none', height: 'none', style: 'none' }) + @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg + } + subject { @svg } + + its(:width) { is_expected.to eq :none } + its(:height) { is_expected.to eq :none } + its(:style) { is_expected.to eq :none } + its(:to_s) { + is_expected.not_to have_tag('svg[width]') + is_expected.not_to have_tag('svg[height]') + is_expected.not_to have_tag('svg[style]') + } + end + + context 'with :none' do + before(:all) { + options = Options.new(plantuml: { width: :none, height: :none, style: :none }) + @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg + } + subject { @svg } + + its(:width) { is_expected.to eq :none } + its(:height) { is_expected.to eq :none } + its(:style) { is_expected.to eq :none } + its(:to_s) { + is_expected.not_to have_tag('svg[width]') + is_expected.not_to have_tag('svg[height]') + is_expected.not_to have_tag('svg[style]') + } + end + + context 'fails properly' do + subject { SvgDiagram.new(result) } + + context 'with nil result' do + let(:result) { nil } + it { expect { subject }.to raise_error(ArgumentError, 'plantuml_result cannot be nil') } + end + + context 'result is not PlantUmlResult' do + let(:result) { '' } + it { expect { subject }.to raise_error(ArgumentError, "plantuml_result must be a #{Kramdown::PlantUml::PlantUmlResult}") } + end + end + end +end From f570b54cc59cbeb61ce6ff7b179c962a154cd1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Mon, 15 Nov 2021 18:28:41 +0100 Subject: [PATCH 07/14] DX-1622: Add default layout and sequence diagram --- spec/examples/_config.yml | 3 +- spec/examples/_layouts/default.html | 11 +++ spec/examples/index.md | 129 ++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 spec/examples/_layouts/default.html diff --git a/spec/examples/_config.yml b/spec/examples/_config.yml index 9cba6e78..692aa187 100644 --- a/spec/examples/_config.yml +++ b/spec/examples/_config.yml @@ -1,2 +1 @@ -plugins: - - kramdown-plantuml +plugins: [kramdown-plantuml] diff --git a/spec/examples/_layouts/default.html b/spec/examples/_layouts/default.html new file mode 100644 index 00000000..679ff825 --- /dev/null +++ b/spec/examples/_layouts/default.html @@ -0,0 +1,11 @@ + + + + + + kramdown::plantuml + + + {{ content }} + + diff --git a/spec/examples/index.md b/spec/examples/index.md index 019fe2f2..ff36d5eb 100644 --- a/spec/examples/index.md +++ b/spec/examples/index.md @@ -1,9 +1,12 @@ --- title: Fixture +layout: default --- # This is a fixture +## Network diagram + ```plantuml @startuml Diagram actor client @@ -13,3 +16,129 @@ db -> app app -> client @enduml ``` + +## Sequence diagram + +```plantuml +@startuml "Sequence" + title "Checkout Sequence" + + group Checkin + Payer --> Merchant: Start Checkin + activate Payer + activate Merchant + Merchant --> SwedbankPay: POST /psp/consumers + activate SwedbankPay + SwedbankPay --> Merchant: rel:view-consumer-identification ① + deactivate SwedbankPay + Merchant --> Payer: Show Checkin on Merchant Page + deactivate Merchant + + SwedbankPay <-> Payer: Consumer identification process + deactivate Payer + + activate SwedbankPay + SwedbankPay ->> Merchant: onConsumerIdentified(consumerProfileRef) ④ + deactivate SwedbankPay + activate Merchant + deactivate Merchant + end group + + group#fff #ebf8f2 Payment Menu + Payer -> Merchant: Initiate Purchase + deactivate Payer + Merchant -> SwedbankPay: POST /psp/paymentorders { paymentUrl, consumerProfileRef } + deactivate Merchant + SwedbankPay --> Merchant: rel:view-paymentorder + deactivate SwedbankPay + Merchant --> Payer: Display Payment Menu on Merchant Page + activate Payer + Payer ->> Payer: Initiate Payment Menu Hosted View (open iframe) + Payer --> SwedbankPay: Show Payment UI page in iframe + deactivate Payer + SwedbankPay -> Payer: Do payment logic + deactivate SwedbankPay + Payer ->> SwedbankPay: Do payment logic + deactivate Payer + + opt Consumer perform payment out of iFrame + Payer ->> Payer: Redirect to 3rd party + Payer -> 3rdParty: Redirect to 3rdPartyUrl URL + deactivate Payer + 3rdParty --> Payer: Redirect back to paymentUrl (merchant) + deactivate 3rdParty + Payer ->> Payer: Initiate Payment Menu Hosted View (open iframe) + Payer -> SwedbankPay: Show Payment UI page in iframe + deactivate Payer + end + + SwedbankPay -->> Payer: Payment status + + alt If payment is completed + activate Payer + Payer ->> Payer: Event: onPaymentCompleted + Payer -> Merchant: Check payment status + deactivate Payer + Merchant -> SwedbankPay: GET + deactivate Merchant + SwedbankPay -> Merchant: rel: paid-paymentorder + deactivate SwedbankPay + opt#fff #eee Get PaymentOrder Details (if paid-paymentorder operation exist) + activate Merchant + Merchant -> SwedbankPay: GET rel:paid-paymentorder.href + activate SwedbankPay + SwedbankPay -->> Merchant: Payment Details + deactivate SwedbankPay + deactivate Merchant + end + end + end group + + opt If payment is failed + activate Payer + Payer ->> Payer: Event: OnPaymentFailed + Payer -> Merchant: Check payment status + deactivate Payer + Merchant -> SwedbankPay: GET {paymentorder.id} + deactivate Merchant + SwedbankPay --> Merchant: rel: failed-paymentorder + + deactivate SwedbankPay + opt Get PaymentOrder Details (if failed-paymentorder operation exist) + activate Payer + deactivate Payer + Merchant -> SwedbankPay: GET rel: failed-paymentorder + deactivate Merchant + SwedbankPay -->> Merchant: Payment Details + deactivate SwedbankPay + end + end + + activate Merchant + Merchant --> Payer: Show Purchase complete + opt PaymentOrder Callback (if callbackUrls is set) + activate Payer + deactivate Payer + SwedbankPay ->> Merchant: POST Payment Callback + end + + group Capture + note left of Merchant + Capture here only if the purchased goods don't require shipping. + If shipping is required, perform capture after the goods have + shipped. Should only be used for PaymentInstruments that support + Authorizations. + end note + + Merchant -> Merchant: Capture + activate Merchant + Merchant -> SwedbankPay: GET {paymentorder.id} + activate SwedbankPay + SwedbankPay --> Merchant: paymentorder + Merchant -> SwedbankPay: rel:create-paymentorder-capture + SwedbankPay --> Merchant: Capture status + deactivate SwedbankPay + deactivate Merchant + end group +@enduml +``` From 8e31d12b2dce69146c09c663e0bb490002218875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Mon, 15 Nov 2021 18:34:00 +0100 Subject: [PATCH 08/14] DX-1622: Fix RuboCop issues --- lib/kramdown-plantuml/style_builder.rb | 4 +-- lib/kramdown-plantuml/svg_diagram.rb | 34 +++++++------------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/lib/kramdown-plantuml/style_builder.rb b/lib/kramdown-plantuml/style_builder.rb index 6a546cf0..704265ad 100644 --- a/lib/kramdown-plantuml/style_builder.rb +++ b/lib/kramdown-plantuml/style_builder.rb @@ -13,7 +13,7 @@ def set(key, value) when :width, :height @hash[key] = value else - set_style(value) + self.style = value end end @@ -23,7 +23,7 @@ def to_s private - def set_style(style) + def style=(style) return if style.nil? || style.strip.empty? style.split(';').each do |pair| diff --git a/lib/kramdown-plantuml/svg_diagram.rb b/lib/kramdown-plantuml/svg_diagram.rb index b4ff94a5..b6ff5d24 100644 --- a/lib/kramdown-plantuml/svg_diagram.rb +++ b/lib/kramdown-plantuml/svg_diagram.rb @@ -66,35 +66,11 @@ def set_xml_attribute(attribute_name, value) style = @style_builder.to_s - puts "Setting 'style' to '#{style}' from '#{self.style}.'" - set_xml_attribute(:style, style) end - def build_style(attribute_name, value) - style = self.style - styles = [] - styles << "width:#{width}" unless width.none_s? - styles << "height:#{height}" unless height.none_s? - - return styles.join(';') if style.nil? || style.strip.empty? - - styles << style - - return styles.join(';') unless style.include?(attribute_name) - - replacement = "#{attribute_name}:#{value}px;" - regex = /#{attribute_name}\s?:\s?[^;]*;/ - style.gsub(regex, replacement) - end - def transfer_options(attributes, plantuml_result) - return if @doc.root.nil? \ - || plantuml_result.nil? \ - || plantuml_result.plantuml_diagram.nil? \ - || plantuml_result.plantuml_diagram.options.nil? - - options = plantuml_result.plantuml_diagram.options + return if (options = options(plantuml_result)).nil? attributes.each do |attribute| options.public_send(attribute).tap do |option_value| @@ -109,6 +85,14 @@ def transfer_options(attributes, plantuml_result) end end + def options(plantuml_result) + return nil if @doc.root.nil? \ + || plantuml_result.nil? \ + || plantuml_result.plantuml_diagram.nil? + + plantuml_result.plantuml_diagram.options + end + def wrap(svg) return svg if svg.nil? || svg.empty? From 916dc6f08f952a558ebfc53e2a2ea8f36a5af40a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Mon, 15 Nov 2021 19:04:13 +0100 Subject: [PATCH 09/14] DX-1622: Add StyleBuilder spec --- lib/kramdown-plantuml/style_builder.rb | 26 +++++++++++++++-- lib/kramdown-plantuml/svg_diagram.rb | 4 +-- spec/style_builder_spec.rb | 39 ++++++++++++++++++++++++++ spec/svg_diagram_spec.rb | 8 +++--- 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 spec/style_builder_spec.rb diff --git a/lib/kramdown-plantuml/style_builder.rb b/lib/kramdown-plantuml/style_builder.rb index 704265ad..ff2b7fd4 100644 --- a/lib/kramdown-plantuml/style_builder.rb +++ b/lib/kramdown-plantuml/style_builder.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative 'none_s' + module Kramdown module PlantUml # Builds a CSS style string from a hash of style properties. @@ -8,21 +10,39 @@ def initialize @hash = {} end - def set(key, value) + def []=(key, value) + return if key.nil? + case key when :width, :height - @hash[key] = value + if none(value) + puts "Deleting :#{key}." + @hash.delete(key) + else + puts "Setting :#{key} to '#{value}'." + @hash[key] = value + end else self.style = value end end def to_s - @hash.map { |key, value| "#{key}:#{value}" }.join(';') + @hash.sort_by { |key, _| key }.map { |key, value| "#{key}:#{value}" }.join(';') end private + def none(value) + return true if value.nil? + + value_s = value.to_s.strip + + return true if value_s.empty? || value.none_s? + + false + end + def style=(style) return if style.nil? || style.strip.empty? diff --git a/lib/kramdown-plantuml/svg_diagram.rb b/lib/kramdown-plantuml/svg_diagram.rb index b6ff5d24..ee42f08b 100644 --- a/lib/kramdown-plantuml/svg_diagram.rb +++ b/lib/kramdown-plantuml/svg_diagram.rb @@ -48,8 +48,6 @@ def get_xml_attribute(attribute_name) end def manipulate_xml_attribute(attribute_name, value) - puts "Setting '#{attribute_name}' to '#{value}' from '#{send(attribute_name)}.'" - if value.none_s? @doc.root.attributes.get_attribute(attribute_name.to_s).remove elsif !value.nil? && value.is_a?(String) && !value.strip.empty? @@ -60,7 +58,7 @@ def manipulate_xml_attribute(attribute_name, value) def set_xml_attribute(attribute_name, value) name = attribute_name.to_s @doc.root.attributes[name] = value - @style_builder.set(attribute_name, value) + @style_builder[attribute_name] = value return if attribute_name == :style || style == :none diff --git a/spec/style_builder_spec.rb b/spec/style_builder_spec.rb new file mode 100644 index 00000000..c3e1818c --- /dev/null +++ b/spec/style_builder_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: false + +require 'rspec/its' +require 'kramdown-plantuml/style_builder' + +StyleBuilder ||= Kramdown::PlantUml::StyleBuilder + +describe StyleBuilder do + subject { StyleBuilder.new } + + context '[nil]' do + before(:each) { subject[nil] = '' } + its(:to_s) { is_expected.to eq '' } + end + + context 'nil value' do + before(:each) { %i[width height style].each { |key| subject[key] = nil }} + its(:to_s) { is_expected.to eq '' } + end + + context '[:width]' do + before(:each) { subject[:width] = '200px' } + its(:to_s) { is_expected.to eq 'width:200px' } + end + + context '[:height]' do + before(:each) { subject[:height] = '200px' } + its(:to_s) { is_expected.to eq 'height:200px' } + end + context '[:width, :height, :style]' do + before(:each) do + subject[:height] = '200px' + subject[:width] = '1337px' + subject[:style] = 'border: 1px solid red; background-color: #ffffff;' + end + + its(:to_s) { is_expected.to eq 'background-color:#ffffff;border:1px solid red;height:200px;width:1337px' } + end +end diff --git a/spec/svg_diagram_spec.rb b/spec/svg_diagram_spec.rb index 029b5c04..c85fbda9 100644 --- a/spec/svg_diagram_spec.rb +++ b/spec/svg_diagram_spec.rb @@ -26,12 +26,12 @@ its(:width) { is_expected.to eq '1337px' } its(:height) { is_expected.to eq '1234px' } - its(:style) { is_expected.to eq 'border:7px solid red;width:1337px;height:1234px' } + its(:style) { is_expected.to eq 'border:7px solid red;height:1234px;width:1337px' } its(:to_s) { is_expected.to have_tag('svg', with: { width: '1337px', height: '1234px', - style: 'border:7px solid red;width:1337px;height:1234px' + style: 'border:7px solid red;height:1234px;width:1337px' }) } end @@ -45,12 +45,12 @@ its(:width) { is_expected.to eq '100' } its(:height) { is_expected.to eq '200' } - its(:style) { is_expected.to eq 'width:100;height:200' } + its(:style) { is_expected.to eq 'height:200;width:100' } its(:to_s) { is_expected.to have_tag('svg', with: { width: '100', height: '200', - style: 'width:100;height:200' + style: 'height:200;width:100' }) } end From f9df235ccc00ad4ea783eb8ade30f54ddfbb73de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Mon, 15 Nov 2021 19:14:36 +0100 Subject: [PATCH 10/14] DX-1622: Proper XML DOM operations Replace string concatenation with proper XML DOM operations to wrap the SVG element in a DIV. --- lib/kramdown-plantuml/svg_diagram.rb | 54 ++++++++++++++-------------- spec/jekyll_provider_spec.rb | 9 +++-- spec/kramdown_html_spec.rb | 12 ++----- spec/plantuml_diagram_spec.rb | 30 ++++------------ 4 files changed, 45 insertions(+), 60 deletions(-) diff --git a/lib/kramdown-plantuml/svg_diagram.rb b/lib/kramdown-plantuml/svg_diagram.rb index ee42f08b..7a5ead1f 100644 --- a/lib/kramdown-plantuml/svg_diagram.rb +++ b/lib/kramdown-plantuml/svg_diagram.rb @@ -18,28 +18,43 @@ def initialize(plantuml_result) @doc = REXML::Document.new svg @source = plantuml_result.plantuml_diagram @style_builder = StyleBuilder.new - transfer_options(%i[style width height], plantuml_result) + transfer_options(plantuml_result) end def to_s - wrap(@doc.root.to_s) + return '' if @doc.root.nil? + + wrapper_doc = REXML::Document.new + wrapper_doc.context[:attribute_quote] = :quote + wrapper_element = REXML::Element.new('div').tap do |div| + div.add_attribute 'class', wrapper_class_name + div.add_element @doc.root + end + + wrapper_doc.add_element wrapper_element + wrapper_doc.to_s end def width - get_xml_attribute(:width) + get_xml_attribute_value(:width) end def height - get_xml_attribute(:height) + get_xml_attribute_value(:height) end def style - get_xml_attribute(:style) + get_xml_attribute_value(:style) end private - def get_xml_attribute(attribute_name) + def wrapper_class_name + theme_class = @source.theme.name ? "theme-#{@source.theme.name}" : '' + "plantuml #{theme_class}".strip + end + + def get_xml_attribute_value(attribute_name) return nil if @doc.root.nil? name = attribute_name.to_s @@ -51,11 +66,11 @@ def manipulate_xml_attribute(attribute_name, value) if value.none_s? @doc.root.attributes.get_attribute(attribute_name.to_s).remove elsif !value.nil? && value.is_a?(String) && !value.strip.empty? - set_xml_attribute(attribute_name, value) + set_xml_attribute_value(attribute_name, value) end end - def set_xml_attribute(attribute_name, value) + def set_xml_attribute_value(attribute_name, value) name = attribute_name.to_s @doc.root.attributes[name] = value @style_builder[attribute_name] = value @@ -64,19 +79,19 @@ def set_xml_attribute(attribute_name, value) style = @style_builder.to_s - set_xml_attribute(:style, style) + set_xml_attribute_value(:style, style) end - def transfer_options(attributes, plantuml_result) + def transfer_options(plantuml_result) return if (options = options(plantuml_result)).nil? - attributes.each do |attribute| + %i[style width height].each do |attribute| options.public_send(attribute).tap do |option_value| next if option_value.nil? - option_value = option_value.to_s + option_value = option_value.to_s.strip - next if option_value.strip.empty? + next if option_value.empty? manipulate_xml_attribute(attribute, option_value) end @@ -90,19 +105,6 @@ def options(plantuml_result) plantuml_result.plantuml_diagram.options end - - def wrap(svg) - return svg if svg.nil? || svg.empty? - - # TODO: Replace with proper XML DOM operations. - theme_class = @source.theme.name ? "theme-#{@source.theme.name}" : '' - class_name = "plantuml #{theme_class}".strip - - wrapper_element_start = "
" - wrapper_element_end = '
' - - "#{wrapper_element_start}#{svg}#{wrapper_element_end}" - end end end end diff --git a/spec/jekyll_provider_spec.rb b/spec/jekyll_provider_spec.rb index 2d14f768..8af389ae 100644 --- a/spec/jekyll_provider_spec.rb +++ b/spec/jekyll_provider_spec.rb @@ -59,8 +59,13 @@ subject { File.read(File.join(jekyll_destination, 'index.html')) } context 'when plantuml contains HTML entities', :jekyll do - it { is_expected.to match(/
<\/div>/m) } - it { is_expected.to match(/This is a fixture<\/h1>/m) } + it do + is_expected.to have_tag('div', with: { class: 'plantuml' }) do + with_tag('svg') + end + end + + it { is_expected.to have_tag('h1', text: 'This is a fixture') } end end end diff --git a/spec/kramdown_html_spec.rb b/spec/kramdown_html_spec.rb index 409bff73..7c1bba6e 100644 --- a/spec/kramdown_html_spec.rb +++ b/spec/kramdown_html_spec.rb @@ -14,9 +14,7 @@ end context 'clean' do - it { - is_expected.to include('class="plantuml">') - } + it { is_expected.to have_tag('div', with: { class: 'plantuml' }) } end context 'built-in theme' do @@ -31,9 +29,7 @@ } } - it { - is_expected.to include('class="plantuml theme-spacelab">') - } + it { is_expected.to have_tag('div', with: { class: 'plantuml theme-spacelab' }) } it 'has theme metadata', :debug do is_expected.to include("!theme spacelab") @@ -55,9 +51,7 @@ } } - it { - is_expected.to include('class="plantuml theme-c2a3b0">') - } + it { is_expected.to have_tag('div', with: { class: 'plantuml theme-c2a3b0' }) } it 'has theme metadata', :debug do is_expected.to include("!theme c2a3b0 from #{examples_dir}") diff --git a/spec/plantuml_diagram_spec.rb b/spec/plantuml_diagram_spec.rb index 8cdcd41d..93390df8 100644 --- a/spec/plantuml_diagram_spec.rb +++ b/spec/plantuml_diagram_spec.rb @@ -31,33 +31,17 @@ is_expected.not_to include('No @startuml/@enduml found') } - it { - is_expected.to include('') - } - it { is_expected.not_to include(' Date: Mon, 15 Nov 2021 22:24:53 +0100 Subject: [PATCH 11/14] DX-1622: Document `width`, `height` and `style` Document the `width`, `height` and `style` configuration keys. --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 5b183779..02a46ace 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,31 @@ kramdown: directory: path/to/themes ``` +### Dimensions and Styling + +It's possible to customize the dimensions of the diagram by providing the +`width` and `height` configuration keys. It's also possible to add arbitrary +styling with the `style` key. + +```yaml +kramdown: + plantuml: + width: 200px + height: 100px + style: "border: 1px solid black" +``` + +To remove the `width`, `height` and `style` attributes from the `` +element, set the key's value to `none`. + +```yaml +kramdown: + plantuml: + width: none + height: none + style: none +``` + ### Errors By default, `kramdown-plantuml` will raise an error and crash if something goes From e184d448dc623581f46fc7bf9b91321f20dbe426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Mon, 15 Nov 2021 22:38:53 +0100 Subject: [PATCH 12/14] DX-1622: Remove leftover puts statements --- lib/kramdown-plantuml/style_builder.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/kramdown-plantuml/style_builder.rb b/lib/kramdown-plantuml/style_builder.rb index ff2b7fd4..4b798aaa 100644 --- a/lib/kramdown-plantuml/style_builder.rb +++ b/lib/kramdown-plantuml/style_builder.rb @@ -16,10 +16,8 @@ def []=(key, value) case key when :width, :height if none(value) - puts "Deleting :#{key}." @hash.delete(key) else - puts "Setting :#{key} to '#{value}'." @hash[key] = value end else From 87a819add879639c2deaf29123a00d92aaeaada4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Mon, 15 Nov 2021 23:11:58 +0100 Subject: [PATCH 13/14] DX-1622: Remove :none attributes from style Remove `width` and `height` from `style` when their value is set to `:none`. --- lib/kramdown-plantuml/svg_diagram.rb | 19 +++++++----- spec/svg_diagram_spec.rb | 44 +++++++++++++++++++++------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/lib/kramdown-plantuml/svg_diagram.rb b/lib/kramdown-plantuml/svg_diagram.rb index 7a5ead1f..a50be932 100644 --- a/lib/kramdown-plantuml/svg_diagram.rb +++ b/lib/kramdown-plantuml/svg_diagram.rb @@ -64,22 +64,27 @@ def get_xml_attribute_value(attribute_name) def manipulate_xml_attribute(attribute_name, value) if value.none_s? - @doc.root.attributes.get_attribute(attribute_name.to_s).remove + remove_xml_attribute(attribute_name) elsif !value.nil? && value.is_a?(String) && !value.strip.empty? - set_xml_attribute_value(attribute_name, value) + set_xml_attribute(attribute_name, value) end + + update_style unless attribute_name == :style || style == :none + end + + def remove_xml_attribute(attribute_name) + @doc.root.attributes.get_attribute(attribute_name.to_s).remove end - def set_xml_attribute_value(attribute_name, value) + def set_xml_attribute(attribute_name, value) name = attribute_name.to_s @doc.root.attributes[name] = value @style_builder[attribute_name] = value + end - return if attribute_name == :style || style == :none - + def update_style style = @style_builder.to_s - - set_xml_attribute_value(:style, style) + set_xml_attribute(:style, style) end def transfer_options(plantuml_result) diff --git a/spec/svg_diagram_spec.rb b/spec/svg_diagram_spec.rb index c85fbda9..df40c020 100644 --- a/spec/svg_diagram_spec.rb +++ b/spec/svg_diagram_spec.rb @@ -55,7 +55,7 @@ } end - context 'with none' do + context 'with all none' do before(:all) { options = Options.new(plantuml: { width: 'none', height: 'none', style: 'none' }) @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg @@ -65,14 +65,15 @@ its(:width) { is_expected.to eq :none } its(:height) { is_expected.to eq :none } its(:style) { is_expected.to eq :none } - its(:to_s) { - is_expected.not_to have_tag('svg[width]') - is_expected.not_to have_tag('svg[height]') - is_expected.not_to have_tag('svg[style]') - } + describe '#to_s' do + subject { @svg.to_s } + it { is_expected.not_to have_tag('svg[width]') } + it { is_expected.not_to have_tag('svg[height]') } + it { is_expected.not_to have_tag('svg[style]') } + end end - context 'with :none' do + context 'with all :none' do before(:all) { options = Options.new(plantuml: { width: :none, height: :none, style: :none }) @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg @@ -82,11 +83,32 @@ its(:width) { is_expected.to eq :none } its(:height) { is_expected.to eq :none } its(:style) { is_expected.to eq :none } - its(:to_s) { - is_expected.not_to have_tag('svg[width]') - is_expected.not_to have_tag('svg[height]') - is_expected.not_to have_tag('svg[style]') + describe '#to_s' do + subject { @svg.to_s } + it { is_expected.not_to have_tag('svg[width]') } + it { is_expected.not_to have_tag('svg[height]') } + it { is_expected.not_to have_tag('svg[style]') } + end + end + + context 'with :none width and height' do + before(:all) { + options = Options.new(plantuml: { width: :none, height: :none }) + @svg = Kramdown::PlantUml::PlantUmlDiagram.new("@startuml\n@enduml", options).svg } + subject { @svg } + + its(:width) { is_expected.to eq :none } + its(:height) { is_expected.to eq :none } + its(:style) { is_expected.not_to be_nil } + describe '#to_s' do + subject { @svg.to_s } + it { is_expected.to have_tag('svg[style]') } + it { is_expected.not_to have_tag('svg[width]') } + it { is_expected.not_to have_tag('svg[height]') } + it { is_expected.not_to include 'width' } + it { is_expected.not_to include 'height' } + end end context 'fails properly' do From 0ce0d3322908896fc745501dbbf210eda4621c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Ulsberg?= Date: Tue, 16 Nov 2021 00:00:28 +0100 Subject: [PATCH 14/14] DX-1622: Remove commented code --- lib/kramdown-plantuml/options.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/kramdown-plantuml/options.rb b/lib/kramdown-plantuml/options.rb index e8593868..7210b373 100644 --- a/lib/kramdown-plantuml/options.rb +++ b/lib/kramdown-plantuml/options.rb @@ -72,10 +72,6 @@ def extract_style_options(options) set_instance_property(:width, options) set_instance_property(:height, options) set_instance_property(:style, options) - - # @width = options.key?(:width) ? options[:width] : nil - # @height = options.key?(:height) ? options[:height] : nil - # @style = options.key?(:style) ? options[:style] : nil end def set_instance_property(name, options)