asciidoctor-diagram 1.5.19 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.adoc +71 -0
  3. data/README.adoc +66 -20
  4. data/examples/features.adoc +2 -2
  5. data/lib/asciidoctor-diagram.rb +5 -0
  6. data/lib/asciidoctor-diagram/a2s/converter.rb +59 -0
  7. data/lib/asciidoctor-diagram/a2s/extension.rb +6 -52
  8. data/lib/asciidoctor-diagram/blockdiag/converter.rb +37 -0
  9. data/lib/asciidoctor-diagram/blockdiag/extension.rb +9 -116
  10. data/lib/asciidoctor-diagram/bpmn.rb +7 -0
  11. data/lib/asciidoctor-diagram/bpmn/converter.rb +62 -0
  12. data/lib/asciidoctor-diagram/bpmn/extension.rb +14 -0
  13. data/lib/asciidoctor-diagram/bytefield.rb +7 -0
  14. data/lib/asciidoctor-diagram/bytefield/converter.rb +26 -0
  15. data/lib/asciidoctor-diagram/bytefield/extension.rb +14 -0
  16. data/lib/asciidoctor-diagram/diagram_converter.rb +23 -0
  17. data/lib/asciidoctor-diagram/diagram_processor.rb +374 -0
  18. data/lib/asciidoctor-diagram/diagram_source.rb +322 -0
  19. data/lib/asciidoctor-diagram/ditaa/converter.rb +90 -0
  20. data/lib/asciidoctor-diagram/ditaa/extension.rb +6 -71
  21. data/lib/asciidoctor-diagram/dpic.rb +7 -0
  22. data/lib/asciidoctor-diagram/dpic/converter.rb +30 -0
  23. data/lib/asciidoctor-diagram/dpic/extension.rb +14 -0
  24. data/lib/asciidoctor-diagram/erd/converter.rb +31 -0
  25. data/lib/asciidoctor-diagram/erd/extension.rb +6 -35
  26. data/lib/asciidoctor-diagram/gnuplot/converter.rb +63 -0
  27. data/lib/asciidoctor-diagram/gnuplot/extension.rb +6 -62
  28. data/lib/asciidoctor-diagram/graphviz/converter.rb +32 -0
  29. data/lib/asciidoctor-diagram/graphviz/extension.rb +6 -35
  30. data/lib/asciidoctor-diagram/http/converter.rb +99 -0
  31. data/lib/asciidoctor-diagram/http/server.rb +132 -0
  32. data/lib/asciidoctor-diagram/lilypond/converter.rb +54 -0
  33. data/lib/asciidoctor-diagram/lilypond/extension.rb +6 -53
  34. data/lib/asciidoctor-diagram/meme/converter.rb +122 -0
  35. data/lib/asciidoctor-diagram/meme/extension.rb +5 -107
  36. data/lib/asciidoctor-diagram/mermaid/converter.rb +192 -0
  37. data/lib/asciidoctor-diagram/mermaid/extension.rb +6 -159
  38. data/lib/asciidoctor-diagram/msc/converter.rb +35 -0
  39. data/lib/asciidoctor-diagram/msc/extension.rb +6 -36
  40. data/lib/asciidoctor-diagram/nomnoml/converter.rb +25 -0
  41. data/lib/asciidoctor-diagram/nomnoml/extension.rb +6 -28
  42. data/lib/asciidoctor-diagram/pikchr.rb +7 -0
  43. data/lib/asciidoctor-diagram/pikchr/converter.rb +26 -0
  44. data/lib/asciidoctor-diagram/pikchr/extension.rb +14 -0
  45. data/lib/asciidoctor-diagram/plantuml/converter.rb +117 -0
  46. data/lib/asciidoctor-diagram/plantuml/extension.rb +10 -119
  47. data/lib/asciidoctor-diagram/shaape/converter.rb +25 -0
  48. data/lib/asciidoctor-diagram/shaape/extension.rb +6 -28
  49. data/lib/asciidoctor-diagram/smcat/converter.rb +44 -0
  50. data/lib/asciidoctor-diagram/smcat/extension.rb +6 -42
  51. data/lib/asciidoctor-diagram/svgbob/converter.rb +49 -0
  52. data/lib/asciidoctor-diagram/svgbob/extension.rb +6 -28
  53. data/lib/asciidoctor-diagram/symbolator.rb +7 -0
  54. data/lib/asciidoctor-diagram/symbolator/converter.rb +23 -0
  55. data/lib/asciidoctor-diagram/symbolator/extension.rb +14 -0
  56. data/lib/asciidoctor-diagram/syntrax/converter.rb +58 -0
  57. data/lib/asciidoctor-diagram/syntrax/extension.rb +6 -51
  58. data/lib/asciidoctor-diagram/tikz/converter.rb +75 -0
  59. data/lib/asciidoctor-diagram/tikz/extension.rb +6 -60
  60. data/lib/asciidoctor-diagram/umlet/converter.rb +33 -0
  61. data/lib/asciidoctor-diagram/umlet/extension.rb +6 -28
  62. data/lib/asciidoctor-diagram/util/cli.rb +14 -3
  63. data/lib/asciidoctor-diagram/util/cli_generator.rb +19 -1
  64. data/lib/asciidoctor-diagram/util/gif.rb +2 -2
  65. data/lib/asciidoctor-diagram/util/java.rb +114 -1
  66. data/lib/asciidoctor-diagram/util/java_socket.rb +8 -120
  67. data/lib/asciidoctor-diagram/util/pdf.rb +2 -2
  68. data/lib/asciidoctor-diagram/util/png.rb +2 -2
  69. data/lib/asciidoctor-diagram/util/svg.rb +46 -19
  70. data/lib/asciidoctor-diagram/util/which.rb +0 -29
  71. data/lib/asciidoctor-diagram/vega/converter.rb +47 -0
  72. data/lib/asciidoctor-diagram/vega/extension.rb +6 -44
  73. data/lib/asciidoctor-diagram/version.rb +1 -1
  74. data/lib/asciidoctor-diagram/wavedrom/converter.rb +50 -0
  75. data/lib/asciidoctor-diagram/wavedrom/extension.rb +6 -54
  76. data/lib/ditaa-1.3.15.jar +0 -0
  77. data/lib/plantuml-1.3.15.jar +0 -0
  78. data/lib/plantuml.jar +0 -0
  79. data/lib/server-1.3.15.jar +0 -0
  80. data/spec/a2s_spec.rb +2 -140
  81. data/spec/blockdiag_spec.rb +2 -200
  82. data/spec/bpmn_spec.rb +56 -0
  83. data/spec/bytefield_spec.rb +92 -0
  84. data/spec/ditaa_spec.rb +37 -143
  85. data/spec/dpic_spec.rb +19 -0
  86. data/spec/erd_spec.rb +2 -199
  87. data/spec/gnuplot_spec.rb +2 -255
  88. data/spec/graphviz_spec.rb +6 -145
  89. data/spec/lilypond_spec.rb +2 -140
  90. data/spec/mermaid_spec.rb +62 -209
  91. data/spec/msc_spec.rb +2 -199
  92. data/spec/nomnoml_spec.rb +4 -142
  93. data/spec/pikchr_spec.rb +51 -0
  94. data/spec/plantuml_spec.rb +24 -507
  95. data/spec/shaape_spec.rb +9 -221
  96. data/spec/shared_examples.rb +603 -0
  97. data/spec/smcat_spec.rb +2 -140
  98. data/spec/svgbob_spec.rb +2 -140
  99. data/spec/symbolator_spec.rb +23 -0
  100. data/spec/syntrax_spec.rb +5 -215
  101. data/spec/test_helper.rb +1 -21
  102. data/spec/tikz_spec.rb +65 -15
  103. data/spec/umlet_spec.rb +2 -58
  104. data/spec/vega_spec.rb +4 -117
  105. data/spec/wavedrom_spec.rb +2 -199
  106. metadata +58 -8
  107. data/lib/asciidoctor-diagram/extensions.rb +0 -568
  108. data/lib/ditaa-1.3.13.jar +0 -0
  109. data/lib/plantuml-1.3.13.jar +0 -0
  110. data/lib/server-1.3.13.jar +0 -0
@@ -0,0 +1,37 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require_relative '../util/platform'
4
+
5
+ module Asciidoctor
6
+ module Diagram
7
+ ['BlockDiag', 'SeqDiag', 'ActDiag', 'NwDiag', 'RackDiag', 'PacketDiag'].each do |name|
8
+ converter = Class.new do
9
+ include DiagramConverter
10
+ include CliGenerator
11
+
12
+ def supported_formats
13
+ [:png, :pdf, :svg]
14
+ end
15
+
16
+ def convert(source, format, options)
17
+ # On Debian based systems the Python 3.x packages python3-(act|block|nw|seq)diag executables with
18
+ # a '3' suffix.
19
+ cmd_name = self.class.const_get(:TOOL)
20
+ alt_cmd_name = "#{cmd_name}3"
21
+
22
+ font_path = source.attr('fontpath')
23
+
24
+ generate_stdin(source.find_command(cmd_name, :alt_cmds => [alt_cmd_name]), format.to_s, source.to_s) do |tool_path, output_path|
25
+ args = [tool_path, '-a', '-o', Platform.native_path(output_path), "-T#{format.to_s}"]
26
+ args << "-f#{Platform.native_path(font_path)}" if font_path
27
+ args << '-'
28
+ args
29
+ end
30
+ end
31
+ end
32
+ converter.const_set(:TOOL, name.downcase)
33
+
34
+ ::Asciidoctor::Diagram.const_set("#{name}Converter", converter)
35
+ end
36
+ end
37
+ end
@@ -1,125 +1,18 @@
1
- require_relative '../extensions'
2
- require_relative '../util/cli_generator'
3
- require_relative '../util/platform'
4
- require_relative '../util/which'
1
+ require_relative '../diagram_processor'
2
+ require_relative 'converter'
5
3
 
6
4
  module Asciidoctor
7
5
  module Diagram
8
- # @!parse
9
- # # Block processor converts blockdiag code into images.
10
- # #
11
- # # Supports PNG and SVG output.
12
- # class BlockDiagBlockProcessor < API::DiagramBlockProcessor; end
13
- #
14
- # # Block macro processor converts blockdiag source files into images.
15
- # #
16
- # # Supports PNG and SVG output.
17
- # class BlockDiagBlockMacroProcessor < DiagramBlockMacroProcessor; end
18
-
19
- # @!parse
20
- # # Block processor converts seqdiag code into images.
21
- # #
22
- # # Supports PNG and SVG output.
23
- # class SeqDiagBlockProcessor < API::DiagramBlockProcessor; end
24
- #
25
- # # Block macro processor converts seqdiag source files into images.
26
- # #
27
- # # Supports PNG and SVG output.
28
- # class SeqDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
29
-
30
- # @!parse
31
- # # Block processor converts actdiag code into images.
32
- # #
33
- # # Supports PNG and SVG output.
34
- # class ActDiagBlockProcessor < API::DiagramBlockProcessor; end
35
- #
36
- # # Block macro processor converts actdiag source files into images.
37
- # #
38
- # # Supports PNG and SVG output.
39
- # class ActDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
40
-
41
- # @!parse
42
- # # Block processor converts nwdiag code into images.
43
- # #
44
- # # Supports PNG and SVG output.
45
- # class NwDiagBlockProcessor < API::DiagramBlockProcessor; end
46
- #
47
- # # Block macro processor converts nwdiag source files into images.
48
- # #
49
- # # Supports PNG and SVG output.
50
- # class NwDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
51
-
52
- # @!parse
53
- # # Block processor converts rackdiag code into images.
54
- # #
55
- # # Supports PNG and SVG output.
56
- # class RackDiagBlockProcessor < API::DiagramBlockProcessor; end
57
- #
58
- # # Block macro processor converts rackdiag source files into images.
59
- # #
60
- # # Supports PNG and SVG output.
61
- # class RackDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
62
-
63
- # @!parse
64
- # # Block processor converts packetdiag code into images.
65
- # #
66
- # # Supports PNG and SVG output.
67
- # class PacketDiagBlockProcessor < API::DiagramBlockProcessor; end
68
- #
69
- # # Block macro processor converts packetdiag source files into images.
70
- # #
71
- # # Supports PNG and SVG output.
72
- # class PacketDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
73
-
74
- # @private
75
- module BlockDiag
76
- def self.define_processors(name)
77
- init = Proc.new do
78
- include ::Asciidoctor::Diagram::BlockDiag
79
-
80
- [:png, :pdf, :svg].each do |f|
81
- register_format(f, :image) do |p, c|
82
- blockdiag(name, p, c, f)
83
- end
84
- end
85
- end
86
-
87
- block = Class.new(Extensions::DiagramBlockProcessor) do
88
- self.instance_eval(&init)
89
- end
90
- ::Asciidoctor::Diagram.const_set("#{name}BlockProcessor", block)
91
-
92
- block_macro = Class.new(Extensions::DiagramBlockMacroProcessor) do
93
- self.instance_eval(&init)
94
- end
95
-
96
- ::Asciidoctor::Diagram.const_set("#{name}BlockMacroProcessor", block_macro)
6
+ ['BlockDiag', 'SeqDiag', 'ActDiag', 'NwDiag', 'RackDiag', 'PacketDiag'].each do |tool|
7
+ block = Class.new(DiagramBlockProcessor) do
8
+ use_converter ::Asciidoctor::Diagram.const_get("#{tool}Converter")
97
9
  end
10
+ ::Asciidoctor::Diagram.const_set("#{tool}BlockProcessor", block)
98
11
 
99
- include CliGenerator
100
- include Which
101
-
102
- def blockdiag(tool, parent, source, format)
103
- inherit_prefix = name
104
- cmd_name = tool.downcase
105
-
106
- # On Debian based systems the Python 3.x packages python3-(act|block|nw|seq)diag executables with
107
- # a '3' suffix.
108
- alt_cmd_name = "#{tool.downcase}3"
109
-
110
- font_path = source.attr('fontpath', nil, inherit_prefix)
111
-
112
- generate_stdin(which(parent, cmd_name, :alt_cmds => [alt_cmd_name]), format.to_s, source.to_s) do |tool_path, output_path|
113
- args = [tool_path, '-a', '-o', Platform.native_path(output_path), "-T#{format.to_s}"]
114
- args << "-f#{Platform.native_path(font_path)}" if font_path
115
- args << '-'
116
- args
117
- end
12
+ block_macro = Class.new(DiagramBlockMacroProcessor) do
13
+ use_converter ::Asciidoctor::Diagram.const_get("#{tool}Converter")
118
14
  end
119
- end
120
-
121
- ['BlockDiag', 'SeqDiag', 'ActDiag', 'NwDiag', 'RackDiag', 'PacketDiag'].each do |tool|
122
- BlockDiag.define_processors(tool)
15
+ ::Asciidoctor::Diagram.const_set("#{tool}BlockMacroProcessor", block_macro)
123
16
  end
124
17
  end
125
18
  end
@@ -0,0 +1,7 @@
1
+ require 'asciidoctor/extensions'
2
+ require_relative 'bpmn/extension'
3
+
4
+ Asciidoctor::Extensions.register do
5
+ block Asciidoctor::Diagram::BpmnBlockProcessor, :bpmn
6
+ block_macro Asciidoctor::Diagram::BpmnBlockMacroProcessor, :bpmn
7
+ end
@@ -0,0 +1,62 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli'
3
+ require_relative '../util/cli_generator'
4
+ require_relative '../util/platform'
5
+
6
+ module Asciidoctor
7
+ module Diagram
8
+ # @private
9
+ class BpmnConverter
10
+ include DiagramConverter
11
+ include CliGenerator
12
+
13
+
14
+ def supported_formats
15
+ [:png, :svg, :pdf, :jpeg]
16
+ end
17
+
18
+ def collect_options(source)
19
+ options = {}
20
+
21
+ options[:width] = source.attr('width')
22
+ options[:height] = source.attr('height')
23
+
24
+ options
25
+ end
26
+
27
+ def convert(source, format, options)
28
+ opts = {}
29
+
30
+ opts[:width] = options[:width]
31
+
32
+ bpmnjs = source.find_command('bpmn-js')
33
+ opts[:height] = options[:height]
34
+ opts[:theme] = options[:theme]
35
+ config = options[:config]
36
+ if config
37
+ opts[:config] = source.resolve_path(config)
38
+ end
39
+ run_bpmnjs(bpmnjs, source, format, opts)
40
+ end
41
+
42
+ private
43
+
44
+ def run_bpmnjs(bpmnjs, source, format, options = {})
45
+ generate_file(bpmnjs, 'bpmn', format.to_s, source.to_s) do |tool_path, input_path, output_path|
46
+ args = [tool_path, Platform.native_path(input_path), '-o', Platform.native_path(output_path), '-t', format.to_s]
47
+
48
+
49
+ if options[:width]
50
+ args << '--width' << options[:width]
51
+ end
52
+
53
+ if options[:height]
54
+ args << '--height' << options[:height]
55
+ end
56
+
57
+ args
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
3
+
4
+ module Asciidoctor
5
+ module Diagram
6
+ class BpmnBlockProcessor < DiagramBlockProcessor
7
+ use_converter BpmnConverter
8
+ end
9
+
10
+ class BpmnBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter BpmnConverter
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ require 'asciidoctor/extensions'
2
+ require_relative 'bytefield/extension'
3
+
4
+ Asciidoctor::Extensions.register do
5
+ block Asciidoctor::Diagram::BytefieldBlockProcessor, :bytefield
6
+ block_macro Asciidoctor::Diagram::BytefieldBlockMacroProcessor, :bytefield
7
+ end
@@ -0,0 +1,26 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require_relative '../util/platform'
4
+
5
+ module Asciidoctor
6
+ module Diagram
7
+ # @private
8
+ class BytefieldConverter
9
+ include DiagramConverter
10
+ include CliGenerator
11
+
12
+
13
+ def supported_formats
14
+ [:svg]
15
+ end
16
+
17
+ def convert(source, format, options)
18
+ bytefield_path = source.find_command('bytefield-svg')
19
+
20
+ generate_stdin(bytefield_path, format.to_s, source.to_s) do |tool_path, output_path|
21
+ [tool_path, "--output", Platform.native_path(output_path)]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
3
+
4
+ module Asciidoctor
5
+ module Diagram
6
+ class BytefieldBlockProcessor < DiagramBlockProcessor
7
+ use_converter BytefieldConverter
8
+ end
9
+
10
+ class BytefieldBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter BytefieldConverter
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ module Asciidoctor
2
+ module Diagram
3
+ # This module describes the duck-typed interface that diagram converters must implement. Implementations
4
+ # may include this module but it is not required.
5
+ module DiagramConverter
6
+ def supported_formats
7
+ raise NotImplementedError.new
8
+ end
9
+
10
+ def collect_options(source)
11
+ {}
12
+ end
13
+
14
+ def convert(source, format, options)
15
+ raise NotImplementedError.new
16
+ end
17
+
18
+ def native_scaling?
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,374 @@
1
+ require 'asciidoctor' unless defined? ::Asciidoctor::VERSION
2
+ require 'asciidoctor/extensions'
3
+ require 'digest'
4
+ require 'json'
5
+ require 'fileutils'
6
+ require 'pathname'
7
+ require_relative 'diagram_source.rb'
8
+ require_relative 'http/converter'
9
+ require_relative 'version'
10
+ require_relative 'util/java'
11
+ require_relative 'util/gif'
12
+ require_relative 'util/pdf'
13
+ require_relative 'util/png'
14
+ require_relative 'util/svg'
15
+
16
+ module Asciidoctor
17
+ module Diagram
18
+ # Mixin that provides the basic machinery for image generation.
19
+ # When this module is included it will include the FormatRegistry into the singleton class of the target class.
20
+ module DiagramProcessor
21
+ include Asciidoctor::Logging
22
+
23
+ module ClassMethods
24
+ def use_converter(converter_type)
25
+ config[:converter] = converter_type
26
+ end
27
+ end
28
+
29
+ def self.included(host_class)
30
+ host_class.use_dsl
31
+ host_class.extend(ClassMethods)
32
+ end
33
+
34
+ IMAGE_PARAMS = {
35
+ :svg => {
36
+ :encoding => Encoding::UTF_8,
37
+ :decoder => SVG
38
+ },
39
+ :gif => {
40
+ :encoding => Encoding::ASCII_8BIT,
41
+ :decoder => GIF
42
+ },
43
+ :png => {
44
+ :encoding => Encoding::ASCII_8BIT,
45
+ :decoder => PNG
46
+ },
47
+ :pdf => {
48
+ :encoding => Encoding::ASCII_8BIT,
49
+ :decoder => PDF
50
+ }
51
+ }
52
+
53
+ # Processes the diagram block or block macro by converting it into an image or literal block.
54
+ #
55
+ # @param parent [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed
56
+ # @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the
57
+ # target value of a block macro
58
+ # @param attributes [Hash] the attributes of the block or block macro
59
+ # @return [Asciidoctor::AbstractBlock] a new block that replaces the original block or block macro
60
+ def process(parent, reader_or_target, attributes)
61
+ location = parent.document.reader.cursor_at_mark
62
+
63
+ normalised_attributes = attributes.inject({}) { |h, (k, v)| h[normalise_attribute_name(k)] = v; h }
64
+ source = create_source(parent, reader_or_target, normalised_attributes)
65
+
66
+ converter = config[:converter].new
67
+
68
+ supported_formats = converter.supported_formats
69
+
70
+ begin
71
+ format = source.attributes.delete('format') || source.global_attr('format', supported_formats[0])
72
+ format = format.to_sym if format.respond_to?(:to_sym)
73
+
74
+ raise "Format undefined" unless format
75
+
76
+ raise "#{self.class.name} does not support output format #{format}" unless supported_formats.include?(format)
77
+
78
+
79
+ title = source.attributes.delete 'title'
80
+ caption = source.attributes.delete 'caption'
81
+
82
+ case format
83
+ when :txt, :atxt, :utxt
84
+ block = create_literal_block(parent, source, format, converter)
85
+ else
86
+ block = create_image_block(parent, source, format, converter)
87
+ end
88
+
89
+ block.title = title
90
+ block.assign_caption(caption, 'figure')
91
+ block
92
+ rescue => e
93
+ case source.global_attr('on-error', 'log')
94
+ when 'abort'
95
+ raise e
96
+ else
97
+ text = "Failed to generate image: #{e.message}"
98
+ warn_msg = text.dup
99
+ if $VERBOSE
100
+ warn_msg << "\n" << e.backtrace.join("\n")
101
+ end
102
+
103
+ logger.error message_with_context warn_msg, source_location: location
104
+
105
+ text << "\n"
106
+ text << source.code
107
+ Asciidoctor::Block.new parent, :listing, :source => text, :attributes => attributes
108
+ end
109
+
110
+ end
111
+ end
112
+
113
+ protected
114
+
115
+ # Creates a DiagramSource object for the block or block macro being processed. Classes using this
116
+ # mixin must implement this method.
117
+ #
118
+ # @param parent_block [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed
119
+ # @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the
120
+ # target value of a block macro
121
+ # @param attributes [Hash] the attributes of the block or block macro
122
+ #
123
+ # @return [DiagramSource] an object that implements the interface described by DiagramSource
124
+ #
125
+ # @abstract
126
+ def create_source(parent_block, reader_or_target, attributes)
127
+ raise NotImplementedError.new
128
+ end
129
+
130
+ private
131
+
132
+ def normalise_attribute_name(k)
133
+ case k
134
+ when String
135
+ k.downcase
136
+ when Symbol
137
+ k.to_s.downcase.to_sym
138
+ else
139
+ k
140
+ end
141
+ end
142
+
143
+ DIGIT_CHAR_RANGE = ('0'.ord)..('9'.ord)
144
+
145
+ def create_image_block(parent, source, format, converter)
146
+ image_name = "#{source.image_name}.#{format}"
147
+
148
+ image_file = parent.normalize_system_path(image_name, image_output_dir(parent))
149
+ metadata_file = parent.normalize_system_path("#{image_name}.cache", cache_dir(source, parent))
150
+
151
+ if File.exist? metadata_file
152
+ metadata = File.open(metadata_file, 'r') {|f| JSON.load(f, nil, :symbolize_names => true, :create_additions => false) }
153
+ else
154
+ metadata = {}
155
+ end
156
+
157
+ image_attributes = source.attributes
158
+ options = converter.collect_options(source)
159
+
160
+ if !File.exist?(image_file) || source.should_process?(image_file, metadata) || options != metadata[:options]
161
+ params = IMAGE_PARAMS[format]
162
+
163
+ server_url = source.global_attr('server-url')
164
+ if server_url
165
+ server_type = source.global_attr('server-type')
166
+ converter = HttpConverter.new(server_url, server_type.to_sym, converter)
167
+ end
168
+
169
+ options = converter.collect_options(source)
170
+ result = converter.convert(source, format, options)
171
+
172
+ result.force_encoding(params[:encoding])
173
+
174
+ metadata = source.create_image_metadata
175
+ metadata[:options] = options
176
+
177
+ result, metadata[:width], metadata[:height] = params[:decoder].post_process_image(result)
178
+
179
+ FileUtils.mkdir_p(File.dirname(image_file)) unless Dir.exist?(File.dirname(image_file))
180
+ File.open(image_file, 'wb') {|f| f.write result}
181
+
182
+ FileUtils.mkdir_p(File.dirname(metadata_file)) unless Dir.exist?(File.dirname(metadata_file))
183
+ File.open(metadata_file, 'w') {|f| JSON.dump(metadata, f)}
184
+ end
185
+
186
+ scale = image_attributes['scale']
187
+ if !converter.native_scaling? && scalematch = /([0-9]+(?:\.[0-9]+)?)/.match(scale)
188
+ scale_factor = scalematch[1].to_f
189
+ else
190
+ scale_factor = 1.0
191
+ end
192
+
193
+ if /html/i =~ parent.document.attributes['backend']
194
+ image_attributes.delete('scale')
195
+ if metadata[:width] && !image_attributes['width']
196
+ image_attributes['width'] = (metadata[:width] * scale_factor).to_i
197
+ end
198
+ if metadata[:height] && !image_attributes['height']
199
+ image_attributes['height'] = (metadata[:height] * scale_factor).to_i
200
+ end
201
+ end
202
+
203
+ parent.document.register(:images, image_name)
204
+
205
+ node = Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => image_attributes
206
+
207
+ alt_text = node.attr('alt')
208
+ alt_text ||= if title_text = image_attributes['title']
209
+ title_text
210
+ elsif target = image_attributes['target']
211
+ (File.basename(target, File.extname(target)) || '').tr '_-', ' '
212
+ else
213
+ 'Diagram'
214
+ end
215
+ alt_text = parent.sub_specialchars(alt_text)
216
+
217
+ node.set_attr('alt', alt_text)
218
+
219
+ if (scaledwidth = node.attr('scaledwidth'))
220
+ # append % to scaledwidth if ends in number (no units present)
221
+ if DIGIT_CHAR_RANGE.include?((scaledwidth[-1] || 0).ord)
222
+ node.set_attr('scaledwidth', %(#{scaledwidth}%))
223
+ end
224
+ end
225
+
226
+ use_absolute_path = source.attr('data-uri', nil, true)
227
+
228
+ if format == :svg
229
+ svg_type = source.global_attr('svg-type')
230
+ case svg_type
231
+ when nil, 'static'
232
+ when 'inline', 'interactive'
233
+ node.set_option(svg_type)
234
+ use_absolute_path = true
235
+ else
236
+ raise "Unsupported SVG type: #{svg_type}"
237
+ end
238
+ end
239
+
240
+ if use_absolute_path
241
+ node.set_attr('target', image_file)
242
+ else
243
+ node.set_attr('target', image_name)
244
+
245
+ if source.global_attr('autoimagesdir')
246
+ image_path = Pathname.new(image_file)
247
+ output_path = Pathname.new(parent.normalize_system_path(output_dir(parent)))
248
+
249
+ imagesdir = image_path.relative_path_from(output_path).dirname.to_s
250
+ node.set_attr('imagesdir', imagesdir)
251
+ end
252
+ end
253
+
254
+ node
255
+ end
256
+
257
+ def scale(size, factor)
258
+ if match = /(\d+)(.*)/.match(size)
259
+ value = match[1].to_i
260
+ unit = match[2]
261
+ (value * factor).to_i.to_s + unit
262
+ else
263
+ size
264
+ end
265
+ end
266
+
267
+ # Returns the image output directory as an absolute path
268
+ def image_output_dir(parent)
269
+ images_out_dir = parent.attr('imagesoutdir', nil, true)
270
+
271
+ if images_out_dir
272
+ resolve_path(parent, images_out_dir)
273
+ else
274
+ images_dir = parent.attr('imagesdir', nil, true)
275
+ output_dir = output_dir(parent)
276
+ resolve_path(parent, images_dir, output_dir)
277
+ end
278
+ end
279
+
280
+ # Returns the cache directory as an absolute path
281
+ def cache_dir(source, parent)
282
+ cache_dir = source.global_attr('cachedir')
283
+ if cache_dir
284
+ resolve_path(parent, cache_dir)
285
+ else
286
+ output_dir = output_dir(parent)
287
+ resolve_path(parent, '.asciidoctor/diagram', output_dir)
288
+ end
289
+ end
290
+
291
+ # Returns the general output directory for Asciidoctor as an absolute path
292
+ def output_dir(parent)
293
+ resolve_path(parent, parent.attr('outdir', nil, true) || doc_option(parent.document, :to_dir))
294
+ end
295
+
296
+ def resolve_path(parent, path, base_dir = nil)
297
+ if path.nil?
298
+ # Resolve the base dir itself
299
+ parent.document.path_resolver.system_path(base_dir)
300
+ else
301
+ # Resolve the path with respect to the base dir
302
+ parent.document.path_resolver.system_path(path, base_dir)
303
+ end
304
+ end
305
+
306
+ def create_literal_block(parent, source, format, converter)
307
+ literal_attributes = source.attributes
308
+ literal_attributes.delete('target')
309
+
310
+ options = converter.collect_options(source)
311
+ result = converter.convert(source, format, options)
312
+
313
+ result.force_encoding(Encoding::UTF_8)
314
+ Asciidoctor::Block.new parent, :literal, :source => result, :attributes => literal_attributes
315
+ end
316
+
317
+ def doc_option(document, key)
318
+ if document.respond_to?(:options)
319
+ value = document.options[key]
320
+ else
321
+ value = nil
322
+ end
323
+
324
+ if document.nested? && value.nil?
325
+ doc_option(document.parent_document, key)
326
+ else
327
+ value
328
+ end
329
+ end
330
+ end
331
+
332
+ # Base class for diagram block processors.
333
+ class DiagramBlockProcessor < Asciidoctor::Extensions::BlockProcessor
334
+ include DiagramProcessor
335
+
336
+ def self.inherited(subclass)
337
+ subclass.name_positional_attributes ['target', 'format']
338
+ subclass.contexts [:listing, :literal, :open]
339
+ subclass.content_model :simple
340
+ end
341
+
342
+ # Creates a ReaderSource from the given reader.
343
+ #
344
+ # @return [ReaderSource] a ReaderSource
345
+ def create_source(parent_block, reader, attributes)
346
+ ReaderSource.new(self, parent_block, reader, attributes)
347
+ end
348
+ end
349
+
350
+ # Base class for diagram block macro processors.
351
+ class DiagramBlockMacroProcessor < Asciidoctor::Extensions::BlockMacroProcessor
352
+ include DiagramProcessor
353
+
354
+ def self.inherited(subclass)
355
+ subclass.name_positional_attributes ['format']
356
+ end
357
+
358
+ def apply_target_subs(parent, target)
359
+ if target
360
+ parent.normalize_system_path(parent.sub_attributes(target, :attribute_missing => 'warn'))
361
+ else
362
+ nil
363
+ end
364
+ end
365
+
366
+ # Creates a FileSource using target as the file name.
367
+ #
368
+ # @return [FileSource] a FileSource
369
+ def create_source(parent, target, attributes)
370
+ FileSource.new(self, parent, apply_target_subs(parent, target), attributes)
371
+ end
372
+ end
373
+ end
374
+ end