asciidoctor-diagram 1.5.18 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +75 -0
  3. data/README.adoc +98 -23
  4. data/examples/features.adoc +2 -2
  5. data/lib/asciidoctor-diagram.rb +8 -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 +357 -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.rb +7 -0
  27. data/lib/asciidoctor-diagram/gnuplot/converter.rb +63 -0
  28. data/lib/asciidoctor-diagram/gnuplot/extension.rb +14 -0
  29. data/lib/asciidoctor-diagram/graphviz/converter.rb +32 -0
  30. data/lib/asciidoctor-diagram/graphviz/extension.rb +6 -35
  31. data/lib/asciidoctor-diagram/http/converter.rb +99 -0
  32. data/lib/asciidoctor-diagram/http/server.rb +132 -0
  33. data/lib/asciidoctor-diagram/lilypond.rb +7 -0
  34. data/lib/asciidoctor-diagram/lilypond/converter.rb +54 -0
  35. data/lib/asciidoctor-diagram/lilypond/extension.rb +14 -0
  36. data/lib/asciidoctor-diagram/meme/converter.rb +122 -0
  37. data/lib/asciidoctor-diagram/meme/extension.rb +5 -107
  38. data/lib/asciidoctor-diagram/mermaid/converter.rb +179 -0
  39. data/lib/asciidoctor-diagram/mermaid/extension.rb +6 -159
  40. data/lib/asciidoctor-diagram/msc/converter.rb +35 -0
  41. data/lib/asciidoctor-diagram/msc/extension.rb +6 -36
  42. data/lib/asciidoctor-diagram/nomnoml/converter.rb +25 -0
  43. data/lib/asciidoctor-diagram/nomnoml/extension.rb +6 -28
  44. data/lib/asciidoctor-diagram/pikchr.rb +7 -0
  45. data/lib/asciidoctor-diagram/pikchr/converter.rb +26 -0
  46. data/lib/asciidoctor-diagram/pikchr/extension.rb +14 -0
  47. data/lib/asciidoctor-diagram/plantuml/converter.rb +117 -0
  48. data/lib/asciidoctor-diagram/plantuml/extension.rb +10 -119
  49. data/lib/asciidoctor-diagram/shaape/converter.rb +25 -0
  50. data/lib/asciidoctor-diagram/shaape/extension.rb +6 -28
  51. data/lib/asciidoctor-diagram/smcat.rb +7 -0
  52. data/lib/asciidoctor-diagram/smcat/converter.rb +44 -0
  53. data/lib/asciidoctor-diagram/smcat/extension.rb +14 -0
  54. data/lib/asciidoctor-diagram/svgbob/converter.rb +49 -0
  55. data/lib/asciidoctor-diagram/svgbob/extension.rb +6 -28
  56. data/lib/asciidoctor-diagram/symbolator.rb +7 -0
  57. data/lib/asciidoctor-diagram/symbolator/converter.rb +23 -0
  58. data/lib/asciidoctor-diagram/symbolator/extension.rb +14 -0
  59. data/lib/asciidoctor-diagram/syntrax/converter.rb +58 -0
  60. data/lib/asciidoctor-diagram/syntrax/extension.rb +6 -51
  61. data/lib/asciidoctor-diagram/tikz/converter.rb +56 -0
  62. data/lib/asciidoctor-diagram/tikz/extension.rb +6 -60
  63. data/lib/asciidoctor-diagram/umlet/converter.rb +24 -0
  64. data/lib/asciidoctor-diagram/umlet/extension.rb +6 -28
  65. data/lib/asciidoctor-diagram/util/cli.rb +14 -3
  66. data/lib/asciidoctor-diagram/util/cli_generator.rb +19 -1
  67. data/lib/asciidoctor-diagram/util/gif.rb +2 -2
  68. data/lib/asciidoctor-diagram/util/java.rb +1 -1
  69. data/lib/asciidoctor-diagram/util/java_socket.rb +7 -9
  70. data/lib/asciidoctor-diagram/util/pdf.rb +2 -2
  71. data/lib/asciidoctor-diagram/util/png.rb +2 -2
  72. data/lib/asciidoctor-diagram/util/svg.rb +46 -19
  73. data/lib/asciidoctor-diagram/util/which.rb +0 -29
  74. data/lib/asciidoctor-diagram/vega/converter.rb +47 -0
  75. data/lib/asciidoctor-diagram/vega/extension.rb +6 -44
  76. data/lib/asciidoctor-diagram/version.rb +1 -1
  77. data/lib/asciidoctor-diagram/wavedrom/converter.rb +50 -0
  78. data/lib/asciidoctor-diagram/wavedrom/extension.rb +6 -46
  79. data/lib/ditaa-1.3.15.jar +0 -0
  80. data/lib/ditaamini-0.12.jar +0 -0
  81. data/lib/plantuml-1.3.15.jar +0 -0
  82. data/lib/plantuml.jar +0 -0
  83. data/lib/server-1.3.15.jar +0 -0
  84. data/spec/a2s_spec.rb +2 -140
  85. data/spec/blockdiag_spec.rb +2 -200
  86. data/spec/bpmn_spec.rb +56 -0
  87. data/spec/bytefield_spec.rb +92 -0
  88. data/spec/ditaa_spec.rb +37 -143
  89. data/spec/dpic_spec.rb +19 -0
  90. data/spec/erd_spec.rb +2 -199
  91. data/spec/gnuplot_spec.rb +225 -0
  92. data/spec/graphviz_spec.rb +6 -145
  93. data/spec/lilypond_spec.rb +13 -0
  94. data/spec/mermaid_spec.rb +35 -200
  95. data/spec/msc_spec.rb +2 -199
  96. data/spec/nomnoml_spec.rb +4 -142
  97. data/spec/pikchr_spec.rb +51 -0
  98. data/spec/plantuml_spec.rb +24 -507
  99. data/spec/shaape_spec.rb +9 -221
  100. data/spec/shared_examples.rb +552 -0
  101. data/spec/smcat_spec.rb +26 -0
  102. data/spec/svgbob_spec.rb +2 -140
  103. data/spec/symbolator_spec.rb +23 -0
  104. data/spec/syntrax_spec.rb +5 -215
  105. data/spec/test_helper.rb +1 -18
  106. data/spec/tikz_spec.rb +4 -24
  107. data/spec/umlet_spec.rb +2 -58
  108. data/spec/vega_spec.rb +4 -117
  109. data/spec/wavedrom_spec.rb +2 -199
  110. metadata +73 -11
  111. data/lib/asciidoctor-diagram/extensions.rb +0 -568
  112. data/lib/ditaa-1.3.13.jar +0 -0
  113. data/lib/ditaamini-0.11.jar +0 -0
  114. data/lib/plantuml-1.3.13.jar +0 -0
  115. data/lib/server-1.3.13.jar +0 -0
@@ -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