asciidoctor-diagram 1.5.19 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.adoc +10 -0
  3. data/README.adoc +22 -9
  4. data/examples/features.adoc +2 -2
  5. data/lib/asciidoctor-diagram.rb +1 -0
  6. data/lib/asciidoctor-diagram/a2s/converter.rb +55 -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/diagram_converter.rb +19 -0
  14. data/lib/asciidoctor-diagram/diagram_processor.rb +320 -0
  15. data/lib/asciidoctor-diagram/diagram_source.rb +275 -0
  16. data/lib/asciidoctor-diagram/ditaa/converter.rb +86 -0
  17. data/lib/asciidoctor-diagram/ditaa/extension.rb +6 -71
  18. data/lib/asciidoctor-diagram/erd/converter.rb +31 -0
  19. data/lib/asciidoctor-diagram/erd/extension.rb +6 -35
  20. data/lib/asciidoctor-diagram/gnuplot/converter.rb +63 -0
  21. data/lib/asciidoctor-diagram/gnuplot/extension.rb +6 -62
  22. data/lib/asciidoctor-diagram/graphviz/converter.rb +32 -0
  23. data/lib/asciidoctor-diagram/graphviz/extension.rb +6 -35
  24. data/lib/asciidoctor-diagram/http/server.rb +127 -0
  25. data/lib/asciidoctor-diagram/lilypond/converter.rb +54 -0
  26. data/lib/asciidoctor-diagram/lilypond/extension.rb +6 -53
  27. data/lib/asciidoctor-diagram/meme/converter.rb +122 -0
  28. data/lib/asciidoctor-diagram/meme/extension.rb +5 -107
  29. data/lib/asciidoctor-diagram/mermaid/converter.rb +178 -0
  30. data/lib/asciidoctor-diagram/mermaid/extension.rb +6 -159
  31. data/lib/asciidoctor-diagram/msc/converter.rb +35 -0
  32. data/lib/asciidoctor-diagram/msc/extension.rb +6 -36
  33. data/lib/asciidoctor-diagram/nomnoml/converter.rb +25 -0
  34. data/lib/asciidoctor-diagram/nomnoml/extension.rb +6 -28
  35. data/lib/asciidoctor-diagram/plantuml/converter.rb +115 -0
  36. data/lib/asciidoctor-diagram/plantuml/extension.rb +10 -119
  37. data/lib/asciidoctor-diagram/shaape/converter.rb +25 -0
  38. data/lib/asciidoctor-diagram/shaape/extension.rb +6 -28
  39. data/lib/asciidoctor-diagram/smcat/converter.rb +44 -0
  40. data/lib/asciidoctor-diagram/smcat/extension.rb +6 -42
  41. data/lib/asciidoctor-diagram/svgbob/converter.rb +25 -0
  42. data/lib/asciidoctor-diagram/svgbob/extension.rb +6 -28
  43. data/lib/asciidoctor-diagram/syntrax/converter.rb +55 -0
  44. data/lib/asciidoctor-diagram/syntrax/extension.rb +6 -51
  45. data/lib/asciidoctor-diagram/tikz/converter.rb +56 -0
  46. data/lib/asciidoctor-diagram/tikz/extension.rb +6 -60
  47. data/lib/asciidoctor-diagram/umlet/converter.rb +24 -0
  48. data/lib/asciidoctor-diagram/umlet/extension.rb +6 -28
  49. data/lib/asciidoctor-diagram/util/java.rb +1 -1
  50. data/lib/asciidoctor-diagram/util/java_socket.rb +7 -9
  51. data/lib/asciidoctor-diagram/util/which.rb +0 -29
  52. data/lib/asciidoctor-diagram/vega/converter.rb +47 -0
  53. data/lib/asciidoctor-diagram/vega/extension.rb +6 -44
  54. data/lib/asciidoctor-diagram/version.rb +1 -1
  55. data/lib/asciidoctor-diagram/wavedrom/converter.rb +50 -0
  56. data/lib/asciidoctor-diagram/wavedrom/extension.rb +6 -54
  57. data/lib/ditaa-1.3.14.jar +0 -0
  58. data/lib/plantuml-1.3.14.jar +0 -0
  59. data/lib/plantuml.jar +0 -0
  60. data/lib/server-1.3.14.jar +0 -0
  61. data/spec/bpmn-example.xml +44 -0
  62. data/spec/bpmn_spec.rb +96 -0
  63. data/spec/mermaid_spec.rb +33 -1
  64. data/spec/plantuml_spec.rb +89 -0
  65. metadata +37 -8
  66. data/lib/asciidoctor-diagram/extensions.rb +0 -568
  67. data/lib/ditaa-1.3.13.jar +0 -0
  68. data/lib/plantuml-1.3.13.jar +0 -0
  69. data/lib/server-1.3.13.jar +0 -0
@@ -314,6 +314,77 @@ User --> (Use the application) : Label
314
314
  expect(b.attributes['height']).to_not be_nil
315
315
  end
316
316
 
317
+ it "should respect the svg-type attribute when format is set to 'svg'" do
318
+ doc = <<-eos
319
+ = Hello, PlantUML!
320
+ Doc Writer <doc@example.com>
321
+
322
+ == First Section
323
+
324
+ [plantuml, format="svg", svg-type="inline"]
325
+ ----
326
+ User -> (Start)
327
+ User --> (Use the application) : Label
328
+
329
+ :Main Admin: ---> (Use the application) : Another label
330
+ ----
331
+ eos
332
+
333
+ d = load_asciidoc doc
334
+ expect(d).to_not be_nil
335
+
336
+ b = d.find { |bl| bl.context == :image }
337
+ expect(b).to_not be_nil
338
+
339
+ expect(b.content_model).to eq :empty
340
+
341
+ target = b.attributes['target']
342
+ expect(target).to_not be_nil
343
+ expect(target).to match(/\.svg/)
344
+ expect(File.exist?(target)).to be true
345
+
346
+ expect(b.attributes['opts']).to eq('inline')
347
+
348
+ expect(b.attributes['width']).to_not be_nil
349
+ expect(b.attributes['height']).to_not be_nil
350
+ end
351
+
352
+ it "should respect the diagram-svg-type attribute when format is set to 'svg'" do
353
+ doc = <<-eos
354
+ = Hello, PlantUML!
355
+ :diagram-svg-type: inline
356
+ Doc Writer <doc@example.com>
357
+
358
+ == First Section
359
+
360
+ [plantuml, format="svg"]
361
+ ----
362
+ User -> (Start)
363
+ User --> (Use the application) : Label
364
+
365
+ :Main Admin: ---> (Use the application) : Another label
366
+ ----
367
+ eos
368
+
369
+ d = load_asciidoc doc
370
+ expect(d).to_not be_nil
371
+
372
+ b = d.find { |bl| bl.context == :image }
373
+ expect(b).to_not be_nil
374
+
375
+ expect(b.content_model).to eq :empty
376
+
377
+ target = b.attributes['target']
378
+ expect(target).to_not be_nil
379
+ expect(target).to match(/\.svg/)
380
+ expect(File.exist?(target)).to be true
381
+
382
+ expect(b.attributes['opts']).to eq('inline')
383
+
384
+ expect(b.attributes['width']).to_not be_nil
385
+ expect(b.attributes['height']).to_not be_nil
386
+ end
387
+
317
388
  it "should generate literal blocks when format is set to 'txt'" do
318
389
  doc = <<-eos
319
390
  = Hello, PlantUML!
@@ -1074,4 +1145,22 @@ Doc Writer <doc@example.com>
1074
1145
  expect(b.attributes['width']).to_not be_nil
1075
1146
  expect(b.attributes['height']).to_not be_nil
1076
1147
  end
1148
+
1149
+ it "should report syntax errors" do
1150
+ doc = <<-eos
1151
+ = Hello, PlantUML!
1152
+ Doc Writer <doc@example.com>
1153
+
1154
+ == First Section
1155
+
1156
+ [plantuml,format="svg"]
1157
+ ----
1158
+ Bob; Alice; foo
1159
+ ----
1160
+ eos
1161
+
1162
+ expect {
1163
+ load_asciidoc doc
1164
+ }.to raise_error(/syntax error/i)
1165
+ end
1077
1166
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-diagram
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.19
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pepijn Van Eeckhoudt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-12 00:00:00.000000000 Z
11
+ date: 2019-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -92,42 +92,66 @@ files:
92
92
  - images/asciidoctor-diagram-process.png
93
93
  - lib/asciidoctor-diagram.rb
94
94
  - lib/asciidoctor-diagram/a2s.rb
95
+ - lib/asciidoctor-diagram/a2s/converter.rb
95
96
  - lib/asciidoctor-diagram/a2s/extension.rb
96
97
  - lib/asciidoctor-diagram/blockdiag.rb
98
+ - lib/asciidoctor-diagram/blockdiag/converter.rb
97
99
  - lib/asciidoctor-diagram/blockdiag/extension.rb
100
+ - lib/asciidoctor-diagram/bpmn.rb
101
+ - lib/asciidoctor-diagram/bpmn/converter.rb
102
+ - lib/asciidoctor-diagram/bpmn/extension.rb
103
+ - lib/asciidoctor-diagram/diagram_converter.rb
104
+ - lib/asciidoctor-diagram/diagram_processor.rb
105
+ - lib/asciidoctor-diagram/diagram_source.rb
98
106
  - lib/asciidoctor-diagram/ditaa.rb
107
+ - lib/asciidoctor-diagram/ditaa/converter.rb
99
108
  - lib/asciidoctor-diagram/ditaa/extension.rb
100
109
  - lib/asciidoctor-diagram/erd.rb
110
+ - lib/asciidoctor-diagram/erd/converter.rb
101
111
  - lib/asciidoctor-diagram/erd/extension.rb
102
- - lib/asciidoctor-diagram/extensions.rb
103
112
  - lib/asciidoctor-diagram/gnuplot.rb
113
+ - lib/asciidoctor-diagram/gnuplot/converter.rb
104
114
  - lib/asciidoctor-diagram/gnuplot/extension.rb
105
115
  - lib/asciidoctor-diagram/graphviz.rb
116
+ - lib/asciidoctor-diagram/graphviz/converter.rb
106
117
  - lib/asciidoctor-diagram/graphviz/extension.rb
118
+ - lib/asciidoctor-diagram/http/server.rb
107
119
  - lib/asciidoctor-diagram/lilypond.rb
120
+ - lib/asciidoctor-diagram/lilypond/converter.rb
108
121
  - lib/asciidoctor-diagram/lilypond/extension.rb
109
122
  - lib/asciidoctor-diagram/meme.rb
123
+ - lib/asciidoctor-diagram/meme/converter.rb
110
124
  - lib/asciidoctor-diagram/meme/extension.rb
111
125
  - lib/asciidoctor-diagram/mermaid.rb
126
+ - lib/asciidoctor-diagram/mermaid/converter.rb
112
127
  - lib/asciidoctor-diagram/mermaid/extension.rb
113
128
  - lib/asciidoctor-diagram/msc.rb
129
+ - lib/asciidoctor-diagram/msc/converter.rb
114
130
  - lib/asciidoctor-diagram/msc/extension.rb
115
131
  - lib/asciidoctor-diagram/nomnoml.rb
132
+ - lib/asciidoctor-diagram/nomnoml/converter.rb
116
133
  - lib/asciidoctor-diagram/nomnoml/extension.rb
117
134
  - lib/asciidoctor-diagram/plantuml.rb
135
+ - lib/asciidoctor-diagram/plantuml/converter.rb
118
136
  - lib/asciidoctor-diagram/plantuml/extension.rb
119
137
  - lib/asciidoctor-diagram/salt.rb
120
138
  - lib/asciidoctor-diagram/shaape.rb
139
+ - lib/asciidoctor-diagram/shaape/converter.rb
121
140
  - lib/asciidoctor-diagram/shaape/extension.rb
122
141
  - lib/asciidoctor-diagram/smcat.rb
142
+ - lib/asciidoctor-diagram/smcat/converter.rb
123
143
  - lib/asciidoctor-diagram/smcat/extension.rb
124
144
  - lib/asciidoctor-diagram/svgbob.rb
145
+ - lib/asciidoctor-diagram/svgbob/converter.rb
125
146
  - lib/asciidoctor-diagram/svgbob/extension.rb
126
147
  - lib/asciidoctor-diagram/syntrax.rb
148
+ - lib/asciidoctor-diagram/syntrax/converter.rb
127
149
  - lib/asciidoctor-diagram/syntrax/extension.rb
128
150
  - lib/asciidoctor-diagram/tikz.rb
151
+ - lib/asciidoctor-diagram/tikz/converter.rb
129
152
  - lib/asciidoctor-diagram/tikz/extension.rb
130
153
  - lib/asciidoctor-diagram/umlet.rb
154
+ - lib/asciidoctor-diagram/umlet/converter.rb
131
155
  - lib/asciidoctor-diagram/umlet/extension.rb
132
156
  - lib/asciidoctor-diagram/util/binaryio.rb
133
157
  - lib/asciidoctor-diagram/util/cli.rb
@@ -142,19 +166,23 @@ files:
142
166
  - lib/asciidoctor-diagram/util/svg.rb
143
167
  - lib/asciidoctor-diagram/util/which.rb
144
168
  - lib/asciidoctor-diagram/vega.rb
169
+ - lib/asciidoctor-diagram/vega/converter.rb
145
170
  - lib/asciidoctor-diagram/vega/extension.rb
146
171
  - lib/asciidoctor-diagram/version.rb
147
172
  - lib/asciidoctor-diagram/wavedrom.rb
173
+ - lib/asciidoctor-diagram/wavedrom/converter.rb
148
174
  - lib/asciidoctor-diagram/wavedrom/extension.rb
149
175
  - lib/batik-all-1.10.jar
150
- - lib/ditaa-1.3.13.jar
176
+ - lib/ditaa-1.3.14.jar
151
177
  - lib/ditaamini-0.12.jar
152
178
  - lib/jlatexmath-minimal-1.0.5.jar
153
- - lib/plantuml-1.3.13.jar
179
+ - lib/plantuml-1.3.14.jar
154
180
  - lib/plantuml.jar
155
- - lib/server-1.3.13.jar
181
+ - lib/server-1.3.14.jar
156
182
  - spec/a2s_spec.rb
157
183
  - spec/blockdiag_spec.rb
184
+ - spec/bpmn-example.xml
185
+ - spec/bpmn_spec.rb
158
186
  - spec/ditaa_spec.rb
159
187
  - spec/erd_spec.rb
160
188
  - spec/gnuplot_spec.rb
@@ -194,8 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
222
  - !ruby/object:Gem::Version
195
223
  version: '0'
196
224
  requirements: []
197
- rubyforge_project:
198
- rubygems_version: 2.5.1
225
+ rubygems_version: 3.0.3
199
226
  signing_key:
200
227
  specification_version: 4
201
228
  summary: An extension for asciidoctor that adds support for UML diagram generation
@@ -203,6 +230,8 @@ summary: An extension for asciidoctor that adds support for UML diagram generati
203
230
  test_files:
204
231
  - spec/a2s_spec.rb
205
232
  - spec/blockdiag_spec.rb
233
+ - spec/bpmn-example.xml
234
+ - spec/bpmn_spec.rb
206
235
  - spec/ditaa_spec.rb
207
236
  - spec/erd_spec.rb
208
237
  - spec/gnuplot_spec.rb
@@ -1,568 +0,0 @@
1
- require 'asciidoctor' unless defined? ::Asciidoctor::VERSION
2
- require 'asciidoctor/extensions'
3
- require 'asciidoctor/logging'
4
- require 'digest'
5
- require 'json'
6
- require 'fileutils'
7
- require_relative 'version'
8
- require_relative 'util/java'
9
- require_relative 'util/gif'
10
- require_relative 'util/pdf'
11
- require_relative 'util/png'
12
- require_relative 'util/svg'
13
-
14
- module Asciidoctor
15
- module Diagram
16
- module Extensions
17
-
18
- # Provides the means for diagram processors to register supported output formats and image
19
- # generation routines
20
- module FormatRegistry
21
- # Registers a supported format. The first registered format becomes the default format for the block
22
- # processor.
23
- #
24
- # @param [Symbol] format the format name
25
- # @param [Symbol] type a symbol indicating the type of block that should be generated; either :image or :literal
26
- # @yieldparam parent [Asciidoctor::AbstractNode] the asciidoc block that is being processed
27
- # @yieldparam source [DiagramSource] the source object
28
- # @yieldreturn [String] the generated diagram
29
- #
30
- # Examples
31
- #
32
- # register_format(:png, :image ) do |parent_block, source|
33
- # File.read(source.to_s)
34
- # end
35
- def register_format(format, type, &block)
36
- raise "Unsupported output type: #{type}" unless type == :image || type == :literal
37
-
38
- unless defined?(@default_format)
39
- @default_format = format
40
- end
41
-
42
- formats[format] = {
43
- :type => type,
44
- :generator => block
45
- }
46
- end
47
-
48
- # Returns the registered formats
49
- #
50
- # @return [Hash]
51
- # @api private
52
- def formats
53
- @formats ||= {}
54
- end
55
-
56
- # Returns the default format
57
- #
58
- # @return [Symbol] the default format
59
- # @api private
60
- def default_format
61
- @default_format
62
- end
63
- end
64
-
65
- # Mixin that provides the basic machinery for image generation.
66
- # When this module is included it will include the FormatRegistry into the singleton class of the target class.
67
- module DiagramProcessor
68
- include Asciidoctor::Logging
69
-
70
- IMAGE_PARAMS = {
71
- :svg => {
72
- :encoding => Encoding::UTF_8,
73
- :decoder => SVG
74
- },
75
- :gif => {
76
- :encoding => Encoding::ASCII_8BIT,
77
- :decoder => GIF
78
- },
79
- :png => {
80
- :encoding => Encoding::ASCII_8BIT,
81
- :decoder => PNG
82
- },
83
- :pdf => {
84
- :encoding => Encoding::ASCII_8BIT,
85
- :decoder => PDF
86
- }
87
- }
88
-
89
- def self.included(mod)
90
- mod.use_dsl
91
- class << mod
92
- include FormatRegistry
93
- end
94
- end
95
-
96
- # Processes the diagram block or block macro by converting it into an image or literal block.
97
- #
98
- # @param parent [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed
99
- # @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the
100
- # target value of a block macro
101
- # @param attributes [Hash] the attributes of the block or block macro
102
- # @return [Asciidoctor::AbstractBlock] a new block that replaces the original block or block macro
103
- def process(parent, reader_or_target, attributes)
104
- location = parent.document.reader.cursor_at_mark
105
-
106
- source = create_source(parent, reader_or_target, attributes.dup)
107
-
108
- begin
109
- format = source.attributes.delete('format') || source.attr('format', self.class.default_format, name)
110
- format = format.to_sym if format.respond_to?(:to_sym)
111
-
112
- raise "Format undefined" unless format
113
-
114
- generator_info = self.class.formats[format]
115
-
116
- raise "#{self.class.name} does not support output format #{format}" unless generator_info
117
-
118
- title = source.attributes.delete 'title'
119
- caption = source.attributes.delete 'caption'
120
-
121
- case generator_info[:type]
122
- when :literal
123
- block = create_literal_block(parent, source, generator_info)
124
- else
125
- block = create_image_block(parent, source, format, generator_info)
126
- end
127
-
128
- block.title = title
129
- block.assign_caption(caption, 'figure')
130
- block
131
- rescue => e
132
- case source.attr('on-error', 'log', 'diagram')
133
- when 'abort'
134
- raise e
135
- else
136
- text = "Failed to generate image: #{e.message}"
137
- warn_msg = text.dup
138
- if $VERBOSE
139
- warn_msg << "\n" << e.backtrace.join("\n")
140
- end
141
-
142
- logger.error message_with_context warn_msg, source_location: location
143
-
144
- text << "\n"
145
- text << source.code
146
- Asciidoctor::Block.new parent, :listing, :source => text, :attributes => attributes
147
- end
148
-
149
- end
150
- end
151
-
152
- protected
153
-
154
- # Creates a DiagramSource object for the block or block macro being processed. Classes using this
155
- # mixin must implement this method.
156
- #
157
- # @param parent_block [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed
158
- # @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the
159
- # target value of a block macro
160
- # @param attributes [Hash] the attributes of the block or block macro
161
- #
162
- # @return [DiagramSource] an object that implements the interface described by DiagramSource
163
- #
164
- # @abstract
165
- def create_source(parent_block, reader_or_target, attributes)
166
- raise NotImplementedError.new
167
- end
168
-
169
- private
170
- DIGIT_CHAR_RANGE = ('0'.ord)..('9'.ord)
171
-
172
- def create_image_block(parent, source, format, generator_info)
173
- image_name = "#{source.image_name}.#{format}"
174
- image_dir = image_output_dir(parent)
175
- cache_dir = cache_dir(parent)
176
- image_file = parent.normalize_system_path image_name, image_dir
177
- metadata_file = parent.normalize_system_path "#{image_name}.cache", cache_dir
178
-
179
- if File.exist? metadata_file
180
- metadata = File.open(metadata_file, 'r') { |f| JSON.load f }
181
- else
182
- metadata = {}
183
- end
184
-
185
- image_attributes = source.attributes
186
-
187
- if !File.exist?(image_file) || source.should_process?(image_file, metadata)
188
- params = IMAGE_PARAMS[format]
189
-
190
- result = instance_exec(parent, source, &generator_info[:generator])
191
-
192
- result.force_encoding(params[:encoding])
193
-
194
- metadata = source.create_image_metadata
195
- metadata['width'], metadata['height'] = params[:decoder].get_image_size(result)
196
-
197
- FileUtils.mkdir_p(File.dirname(image_file)) unless Dir.exist?(File.dirname(image_file))
198
- File.open(image_file, 'wb') { |f| f.write result }
199
-
200
- FileUtils.mkdir_p(File.dirname(metadata_file)) unless Dir.exist?(File.dirname(metadata_file))
201
- File.open(metadata_file, 'w') { |f| JSON.dump(metadata, f) }
202
- end
203
-
204
- image_attributes['target'] = parent.attr('data-uri', nil, true) ? image_file : image_name
205
-
206
- scale = image_attributes['scale']
207
- if scalematch = /(\d+(?:\.\d+))/.match(scale)
208
- scale_factor = scalematch[1].to_f
209
- else
210
- scale_factor = 1.0
211
- end
212
-
213
- if /html/i =~ parent.document.attributes['backend']
214
- image_attributes.delete('scale')
215
- if metadata['width'] && !image_attributes['width']
216
- image_attributes['width'] = (metadata['width'] * scale_factor).to_i
217
- end
218
- if metadata['height'] && !image_attributes['height']
219
- image_attributes['height'] = (metadata['height'] * scale_factor).to_i
220
- end
221
- end
222
-
223
- image_attributes['alt'] ||= if title_text = image_attributes['title']
224
- title_text
225
- elsif target = image_attributes['target']
226
- (File.basename(target, File.extname(target)) || '').tr '_-', ' '
227
- else
228
- 'Diagram'
229
- end
230
-
231
- image_attributes['alt'] = parent.sub_specialchars image_attributes['alt']
232
-
233
- parent.document.register(:images, image_name)
234
- if (scaledwidth = image_attributes['scaledwidth'])
235
- # append % to scaledwidth if ends in number (no units present)
236
- if DIGIT_CHAR_RANGE.include?((scaledwidth[-1] || 0).ord)
237
- image_attributes['scaledwidth'] = %(#{scaledwidth}%)
238
- end
239
- end
240
-
241
- Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => image_attributes
242
- end
243
-
244
- def scale(size, factor)
245
- if match = /(\d+)(.*)/.match(size)
246
- value = match[1].to_i
247
- unit = match[2]
248
- (value * factor).to_i.to_s + unit
249
- else
250
- size
251
- end
252
- end
253
-
254
- def image_output_dir(parent)
255
- document = parent.document
256
-
257
- images_dir = parent.attr('imagesoutdir', nil, true)
258
-
259
- if images_dir
260
- base_dir = nil
261
- else
262
- base_dir = parent.attr('outdir', nil, true) || doc_option(document, :to_dir)
263
- images_dir = parent.attr('imagesdir', nil, true)
264
- end
265
-
266
- parent.normalize_system_path(images_dir, base_dir)
267
- end
268
-
269
- def cache_dir(parent)
270
- document = parent.document
271
- cache_dir = '.asciidoctor/diagram'
272
- base_dir = parent.attr('outdir', nil, true) || doc_option(document, :to_dir)
273
- parent.normalize_system_path(cache_dir, base_dir)
274
- end
275
-
276
- def create_literal_block(parent, source, generator_info)
277
- literal_attributes = source.attributes
278
- literal_attributes.delete('target')
279
-
280
- result = instance_exec(parent, source, &generator_info[:generator])
281
-
282
- result.force_encoding(Encoding::UTF_8)
283
- Asciidoctor::Block.new parent, :literal, :source => result, :attributes => literal_attributes
284
- end
285
-
286
- def doc_option(document, key)
287
- if document.respond_to?(:options)
288
- value = document.options[key]
289
- else
290
- value = nil
291
- end
292
-
293
- if document.nested? && value.nil?
294
- doc_option(document.parent_document, key)
295
- else
296
- value
297
- end
298
- end
299
- end
300
-
301
- # Base class for diagram block processors.
302
- class DiagramBlockProcessor < Asciidoctor::Extensions::BlockProcessor
303
- include DiagramProcessor
304
-
305
- def self.inherited(subclass)
306
- subclass.name_positional_attributes ['target', 'format']
307
- subclass.contexts [:listing, :literal, :open]
308
- subclass.content_model :simple
309
- end
310
-
311
- # Creates a ReaderSource from the given reader.
312
- #
313
- # @return [ReaderSource] a ReaderSource
314
- def create_source(parent_block, reader, attributes)
315
- ReaderSource.new(parent_block, reader, attributes)
316
- end
317
- end
318
-
319
- # Base class for diagram block macro processors.
320
- class DiagramBlockMacroProcessor < Asciidoctor::Extensions::BlockMacroProcessor
321
- include DiagramProcessor
322
-
323
- def self.inherited(subclass)
324
- subclass.name_positional_attributes ['target', 'format']
325
- end
326
-
327
- def apply_target_subs(parent, target)
328
- if target
329
- parent.normalize_system_path(parent.sub_attributes(target, :attribute_missing => 'warn'))
330
- else
331
- nil
332
- end
333
- end
334
-
335
- # Creates a FileSource using target as the file name.
336
- #
337
- # @return [FileSource] a FileSource
338
- def create_source(parent, target, attributes)
339
- FileSource.new(parent, apply_target_subs(parent, target), attributes)
340
- end
341
- end
342
-
343
- # This module describes the duck-typed interface that diagram sources must implement. Implementations
344
- # may include this module but it is not required.
345
- module DiagramSource
346
- # @return [String] the base name for the image file that will be produced
347
- # @abstract
348
- def image_name
349
- raise NotImplementedError.new
350
- end
351
-
352
- # @return [String] the String representation of the source code for the diagram
353
- # @abstract
354
- def code
355
- raise NotImplementedError.new
356
- end
357
-
358
- # Get the value for the specified attribute. First look in the attributes on
359
- # this document and return the value of the attribute if found. Otherwise, if
360
- # this document is a child of the Document document, look in the attributes of the
361
- # Document document and return the value of the attribute if found. Otherwise,
362
- # return the default value, which defaults to nil.
363
- #
364
- # @param name [String, Symbol] the name of the attribute to lookup
365
- # @param default_value [Object] the value to return if the attribute is not found
366
- # @inherit [Boolean, String] indicates whether to check for the attribute on the AsciiDoctor::Document if not found on this document.
367
- # When a non-nil String is given the an attribute name "#{inherit}-#{name}" is looked for on the document.
368
- #
369
- # @return the value of the attribute or the default value if the attribute is not found in the attributes of this node or the document node
370
- # @abstract
371
- def attr(name, default_value = nil, inherit = nil)
372
- raise NotImplementedError.new
373
- end
374
-
375
- # @return [String] the base directory against which relative paths in this diagram should be resolved
376
- # @abstract
377
- def base_dir
378
- attr('docdir', nil, true)
379
- end
380
-
381
- # Alias for code
382
- def to_s
383
- code
384
- end
385
-
386
- # Determines if the diagram should be regenerated or not. The default implementation of this method simply
387
- # returns true.
388
- #
389
- # @param image_file [String] the path to the previously generated version of the image
390
- # @param image_metadata [Hash] the image metadata Hash that was stored during the previous diagram generation pass
391
- # @return [Boolean] true if the diagram should be regenerated; false otherwise
392
- def should_process?(image_file, image_metadata)
393
- true
394
- end
395
-
396
- # Creates an image metadata Hash that will be stored to disk alongside the generated image file. The contents
397
- # of this Hash are reread during subsequent document processing and then passed to the should_process? method
398
- # where it can be used to determine if the diagram should be regenerated or not.
399
- # The default implementation returns an empty Hash.
400
- # @return [Hash] a Hash containing metadata
401
- def create_image_metadata
402
- {}
403
- end
404
- end
405
-
406
- # Base class for diagram source implementations that uses an md5 checksum of the source code of a diagram to
407
- # determine if it has been updated or not.
408
- class BasicSource
409
- include DiagramSource
410
-
411
- attr_reader :attributes
412
-
413
- def initialize(parent_block, attributes)
414
- @parent_block = parent_block
415
- @attributes = attributes
416
- end
417
-
418
- def image_name
419
- attr('target', 'diag-' + checksum)
420
- end
421
-
422
- def attr(name, default_value=nil, inherit=nil)
423
- name = name.to_s if ::Symbol === name
424
-
425
- value = @attributes[name]
426
-
427
- if value.nil? && inherit
428
- case inherit
429
- when String, Symbol
430
- value = @parent_block.attr("#{inherit.to_s}-#{name}", default_value, true)
431
- else
432
- value = @parent_block.attr(name, default_value, true)
433
- end
434
- end
435
-
436
- value || default_value
437
- end
438
-
439
- def should_process?(image_file, image_metadata)
440
- image_metadata['checksum'] != checksum
441
- end
442
-
443
- def create_image_metadata
444
- {'checksum' => checksum}
445
- end
446
-
447
- def checksum
448
- @checksum ||= compute_checksum(code)
449
- end
450
-
451
- protected
452
- def resolve_diagram_subs
453
- if @attributes.key? 'subs'
454
- @parent_block.resolve_block_subs @attributes['subs'], nil, 'diagram'
455
- else
456
- []
457
- end
458
- end
459
-
460
- private
461
- def compute_checksum(code)
462
- md5 = Digest::MD5.new
463
- md5 << code
464
- @attributes.each do |k, v|
465
- md5 << k.to_s if k
466
- md5 << v.to_s if v
467
- end
468
- md5.hexdigest
469
- end
470
- end
471
-
472
- # A diagram source that retrieves the code for the diagram from the contents of a block.
473
- class ReaderSource < BasicSource
474
- include DiagramSource
475
-
476
- def initialize(parent_block, reader, attributes)
477
- super(parent_block, attributes)
478
- @reader = reader
479
- end
480
-
481
- def code
482
- @code ||= @parent_block.apply_subs(@reader.lines, resolve_diagram_subs).join("\n")
483
- end
484
- end
485
-
486
- # A diagram source that retrieves the code for a diagram from an external source file.
487
- class FileSource < BasicSource
488
- def initialize(parent_block, file_name, attributes)
489
- super(parent_block, attributes)
490
- @file_name = file_name
491
- end
492
-
493
- def base_dir
494
- if @file_name
495
- File.dirname(@file_name)
496
- else
497
- super
498
- end
499
- end
500
-
501
- def image_name
502
- if @attributes['target']
503
- super
504
- elsif @file_name
505
- File.basename(@file_name, File.extname(@file_name))
506
- else
507
- checksum
508
- end
509
- end
510
-
511
- def should_process?(image_file, image_metadata)
512
- (@file_name && File.mtime(@file_name) > File.mtime(image_file)) || super
513
- end
514
-
515
- def code
516
- @code ||= read_code
517
- end
518
-
519
- def read_code
520
- if @file_name
521
- lines = File.readlines(@file_name)
522
- lines = prepare_source_array(lines)
523
- @parent_block.apply_subs(lines, resolve_diagram_subs).join("\n")
524
- else
525
- ''
526
- end
527
- end
528
-
529
- private
530
-
531
- # Byte arrays for UTF-* Byte Order Marks
532
- BOM_BYTES_UTF_8 = [0xef, 0xbb, 0xbf]
533
- BOM_BYTES_UTF_16LE = [0xff, 0xfe]
534
- BOM_BYTES_UTF_16BE = [0xfe, 0xff]
535
-
536
- # Prepare the source data Array for parsing.
537
- #
538
- # Encodes the data to UTF-8, if necessary, and removes any trailing
539
- # whitespace from every line.
540
- #
541
- # If a BOM is found at the beginning of the data, a best attempt is made to
542
- # encode it to UTF-8 from the specified source encoding.
543
- #
544
- # data - the source data Array to prepare (no nil entries allowed)
545
- #
546
- # returns a String Array of prepared lines
547
- def prepare_source_array data
548
- return [] if data.empty?
549
- if (leading_2_bytes = (leading_bytes = (first = data[0]).unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
550
- data[0] = first.byteslice 2, first.bytesize
551
- # NOTE you can't split a UTF-16LE string using .lines when encoding is UTF-8; doing so will cause this line to fail
552
- return data.map {|line| (line.encode ::Encoding::UTF_8, ::Encoding::UTF_16LE).rstrip }
553
- elsif leading_2_bytes == BOM_BYTES_UTF_16BE
554
- data[0] = first.byteslice 2, first.bytesize
555
- return data.map {|line| (line.encode ::Encoding::UTF_8, ::Encoding::UTF_16BE).rstrip }
556
- elsif leading_bytes == BOM_BYTES_UTF_8
557
- data[0] = first.byteslice 3, first.bytesize
558
- end
559
- if first.encoding == ::Encoding::UTF_8
560
- data.map {|line| line.rstrip }
561
- else
562
- data.map {|line| (line.encode ::Encoding::UTF_8).rstrip }
563
- end
564
- end
565
- end
566
- end
567
- end
568
- end