asciidoctor-diagram 1.0.1-java → 1.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +22 -0
  3. data/README.adoc +7 -6
  4. data/examples/Gemfile +3 -0
  5. data/examples/README.adoc +6 -0
  6. data/examples/build_example.rb +9 -0
  7. data/examples/design.adoc +78 -0
  8. data/examples/features.adoc +164 -0
  9. data/lib/asciidoctor-diagram.rb +1 -0
  10. data/lib/asciidoctor-diagram/ditaa.rb +1 -0
  11. data/lib/asciidoctor-diagram/ditaa/extension.rb +23 -50
  12. data/lib/asciidoctor-diagram/ditaa/generator.rb +31 -0
  13. data/lib/asciidoctor-diagram/graphviz.rb +8 -0
  14. data/lib/asciidoctor-diagram/graphviz/extension.rb +41 -0
  15. data/lib/asciidoctor-diagram/plantuml.rb +1 -0
  16. data/lib/asciidoctor-diagram/plantuml/extension.rb +33 -71
  17. data/lib/asciidoctor-diagram/plantuml/generator.rb +50 -0
  18. data/lib/asciidoctor-diagram/{binaryio.rb → util/binaryio.rb} +0 -0
  19. data/lib/asciidoctor-diagram/util/diagram.rb +198 -0
  20. data/lib/asciidoctor-diagram/{java.rb → util/java.rb} +0 -0
  21. data/lib/asciidoctor-diagram/{java_jruby.rb → util/java_jruby.rb} +0 -0
  22. data/lib/asciidoctor-diagram/{java_rjb.rb → util/java_rjb.rb} +0 -0
  23. data/lib/asciidoctor-diagram/{png.rb → util/png.rb} +0 -0
  24. data/lib/asciidoctor-diagram/util/svg.rb +46 -0
  25. data/lib/asciidoctor-diagram/version.rb +1 -1
  26. data/spec/ditaa_spec.rb +65 -0
  27. data/spec/graphviz_spec.rb +134 -0
  28. data/spec/plantuml_spec.rb +38 -0
  29. data/spec/test_helper.rb +1 -0
  30. metadata +32 -26
  31. data/.gitignore +0 -19
  32. data/.travis.yml +0 -10
  33. data/Gemfile +0 -4
  34. data/asciidoctor-diagram.gemspec +0 -30
  35. data/ditaamini-license.txt +0 -165
  36. data/lib/asciidoctor-diagram/diagram.rb +0 -111
  37. data/lib/asciidoctor-diagram/svg.rb +0 -18
  38. data/plantuml-license.txt +0 -202
@@ -0,0 +1,8 @@
1
+ require 'asciidoctor/extensions'
2
+ require_relative 'version'
3
+
4
+ Asciidoctor::Extensions.register do
5
+ require_relative 'graphviz/extension'
6
+ block :graphviz, Asciidoctor::Diagram::GraphvizBlock
7
+ block_macro :graphviz, Asciidoctor::Diagram::GraphvizBlockMacro
8
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../util/diagram'
2
+ require_relative '../plantuml/generator'
3
+
4
+ module Asciidoctor
5
+ module Diagram
6
+ module GraphvizBase
7
+ include PlantUmlGenerator
8
+
9
+ private
10
+
11
+ def register_formats
12
+ register_format(:png, :image) do |c, p|
13
+ plantuml(p, c, 'dot')
14
+ end
15
+ register_format(:svg, :image) do |c, p|
16
+ plantuml(p, c, 'dot', '-tsvg')
17
+ end
18
+ end
19
+ end
20
+
21
+ class GraphvizBlock < Asciidoctor::Extensions::BlockProcessor
22
+ include DiagramProcessorBase
23
+ include GraphvizBase
24
+
25
+ def initialize(context, document, opts = {})
26
+ super
27
+ register_formats()
28
+ end
29
+ end
30
+
31
+ class GraphvizBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor
32
+ include DiagramProcessorBase
33
+ include GraphvizBase
34
+
35
+ def initialize(context, document, opts = {})
36
+ super
37
+ register_formats()
38
+ end
39
+ end
40
+ end
41
+ end
@@ -4,4 +4,5 @@ require_relative 'version'
4
4
  Asciidoctor::Extensions.register do
5
5
  require_relative 'plantuml/extension'
6
6
  block :plantuml, Asciidoctor::Diagram::PlantUmlBlock
7
+ block_macro :plantuml, Asciidoctor::Diagram::PlantUmlBlockMacro
7
8
  end
@@ -1,87 +1,49 @@
1
- require_relative '../diagram'
2
- require_relative '../java'
3
- require_relative '../png'
4
- require_relative '../svg'
1
+ require_relative '../util/diagram'
2
+ require_relative 'generator'
5
3
 
6
4
  module Asciidoctor
7
5
  module Diagram
8
- PLANTUML_JAR_PATH = File.expand_path File.join('../..', 'plantuml.jar'), File.dirname(__FILE__)
6
+ module PlantUmlBase
7
+ include PlantUmlGenerator
9
8
 
10
- class PlantUmlBlock < DiagramBlock
11
- option :contexts, [:listing, :literal, :open]
12
- option :content_model, :simple
13
- option :pos_attrs, ['target', 'format']
14
- option :default_attrs, {'format' => 'png'}
15
-
16
- def name
17
- "PlantUml"
18
- end
19
-
20
- def allowed_formats
21
- @allowed_formats ||= [:svg, :png, :txt]
22
- end
9
+ private
23
10
 
24
- def generate_image(parent, diagram_code, format)
25
- flags = get_default_flags(parent)
26
- if format == :svg
27
- flags << "-tsvg"
11
+ def register_formats(document)
12
+ config_args = []
13
+ config = document.attributes['plantumlconfig']
14
+ if config
15
+ config_args += ['-config', File.expand_path(config, document.attributes['docdir'])]
28
16
  end
29
17
 
30
- plantuml(diagram_code, *flags)
31
- end
32
-
33
- def generate_text(parent, diagram_code)
34
- flags = get_default_flags(parent)
35
- flags << '-tutxt'
36
-
37
- plantuml(diagram_code, *flags)
18
+ register_format(:png, :image) do |c, p|
19
+ plantuml(p, c, 'uml', *config_args)
20
+ end
21
+ register_format(:svg, :image) do |c, p|
22
+ plantuml(p, c, 'uml', '-tsvg', *config_args)
23
+ end
24
+ register_format(:txt, :literal) do |c, p|
25
+ plantuml(p, c, 'uml', '-tutxt', *config_args)
26
+ end
38
27
  end
28
+ end
39
29
 
40
- private
41
-
42
- Java.classpath << PLANTUML_JAR_PATH
43
-
44
- def plantuml(code, *flags)
45
- Java.load
46
-
47
- code = "@startuml\n#{code}\n@enduml" unless code.index '@startuml'
48
-
49
- # When the -pipe command line flag is used, PlantUML calls System.exit which kills our process. In order
50
- # to avoid this we call some lower level components of PlantUML directly.
51
- # This snippet of code corresponds approximately with net.sourceforge.plantuml.Run#managePipe
52
- cmd = ['-charset', 'UTF-8', '-failonerror']
53
- cmd += flags
54
-
55
- option = Java.net.sourceforge.plantuml.Option.new(Java.array_to_java_array(cmd, :string))
56
- source_reader = Java.net.sourceforge.plantuml.SourceStringReader.new(
57
- Java.net.sourceforge.plantuml.preproc.Defines.new(),
58
- code,
59
- option.getConfig()
60
- )
30
+ class PlantUmlBlock < Asciidoctor::Extensions::BlockProcessor
31
+ include DiagramProcessorBase
32
+ include PlantUmlBase
61
33
 
62
- bos = Java.java.io.ByteArrayOutputStream.new
63
- ps = Java.java.io.PrintStream.new(bos)
64
- source_reader.generateImage(ps, 0, option.getFileFormatOption())
65
- ps.close
66
- Java.string_from_java_bytes(bos.toByteArray)
34
+ def initialize(context, document, opts = {})
35
+ super
36
+ register_formats(document)
67
37
  end
38
+ end
68
39
 
69
- def get_default_flags(parent)
70
- flags = []
71
-
72
- document = parent.document
73
- config = document.attributes['plantumlconfig']
74
- if config
75
- flags += ['-config', File.expand_path(config, document.attributes['docdir'])]
76
- end
77
-
78
- flags
79
- end
40
+ class PlantUmlBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor
41
+ include DiagramProcessorBase
42
+ include PlantUmlBase
80
43
 
81
- def code_checksum(code)
82
- md5 = Digest::MD5.new
83
- md5 << code
84
- md5.hexdigest
44
+ def initialize(context, document, opts = {})
45
+ super
46
+ register_formats(document)
85
47
  end
86
48
  end
87
49
  end
@@ -0,0 +1,50 @@
1
+ require_relative '../util/java'
2
+
3
+ module Asciidoctor
4
+ module Diagram
5
+ module PlantUmlGenerator
6
+ private
7
+
8
+ PLANTUML_JAR_PATH = File.expand_path File.join('../..', 'plantuml.jar'), File.dirname(__FILE__)
9
+ Java.classpath << PLANTUML_JAR_PATH
10
+
11
+ def plantuml(parent, code, tag, *flags)
12
+ unless @graphvizdot
13
+ @graphvizdot = parent.document.attributes['graphvizdot']
14
+ @graphvizdot = which('dot') unless @graphvizdot && File.executable?(@graphvizdot)
15
+ raise "Could not find the Graphviz 'dot' executable in PATH; add it to the PATH or specify its location using the 'graphvizdot' document attribute" unless @graphvizdot
16
+ end
17
+
18
+ Java.load
19
+
20
+ code = "@start#{tag}\n#{code}\n@end#{tag}" unless code.index "@start#{tag}"
21
+
22
+ flags += ['-charset', 'UTF-8', '-failonerror', '-graphvizdot', @graphvizdot]
23
+
24
+ option = Java.net.sourceforge.plantuml.Option.new(Java.array_to_java_array(flags, :string))
25
+ source_reader = Java.net.sourceforge.plantuml.SourceStringReader.new(
26
+ Java.net.sourceforge.plantuml.preproc.Defines.new(),
27
+ code,
28
+ option.getConfig()
29
+ )
30
+
31
+ bos = Java.java.io.ByteArrayOutputStream.new
32
+ ps = Java.java.io.PrintStream.new(bos)
33
+ source_reader.generateImage(ps, 0, option.getFileFormatOption())
34
+ ps.close
35
+ Java.string_from_java_bytes(bos.toByteArray)
36
+ end
37
+
38
+ def which(cmd)
39
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
40
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
41
+ exts.each { |ext|
42
+ exe = File.join(path, "#{cmd}#{ext}")
43
+ return exe if File.executable? exe
44
+ }
45
+ end
46
+ nil
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,198 @@
1
+ require 'asciidoctor/extensions'
2
+ require 'digest'
3
+ require 'json'
4
+ require_relative 'java'
5
+ require_relative 'png'
6
+ require_relative 'svg'
7
+
8
+ module Asciidoctor
9
+ module Diagram
10
+ module DiagramProcessorBase
11
+ IMAGE_PARAMS = {
12
+ :svg => {
13
+ :encoding => Encoding::UTF_8,
14
+ :decoder => SVG
15
+ },
16
+ :png => {
17
+ :encoding => Encoding::ASCII_8BIT,
18
+ :decoder => PNG
19
+ }
20
+ }
21
+
22
+ def self.included(base)
23
+ base.option :pos_attrs, ['target', 'format']
24
+
25
+ if base.ancestors.include?(Asciidoctor::Extensions::BlockProcessor)
26
+ base.option :contexts, [:listing, :literal, :open]
27
+ base.option :content_model, :simple
28
+
29
+ base.instance_eval do
30
+ alias_method :process, :process_block
31
+ end
32
+ else
33
+ base.instance_eval do
34
+ alias_method :process, :process_macro
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ def process_macro(parent, target, attributes)
41
+ source = FileSource.new(File.expand_path(target, parent.document.attributes['docdir']))
42
+ attributes['target'] = File.basename(target, File.extname(target))
43
+
44
+ generate_block(parent, source, attributes)
45
+ end
46
+
47
+ def process_block(parent, reader, attributes)
48
+ generate_block(parent, ReaderSource.new(reader), attributes)
49
+ end
50
+
51
+ def generate_block(parent, source, attributes)
52
+ format = attributes.delete('format') || @default_format
53
+ format = format.to_sym if format.respond_to?(:to_sym)
54
+
55
+ raise "Format undefined" unless format
56
+
57
+ generator_info = formats[format]
58
+
59
+ raise "#{self.class.name} does not support output format #{format}" unless generator_info
60
+
61
+ case generator_info[:type]
62
+ when :image
63
+ create_image_block(parent, source, attributes, format, generator_info)
64
+ when :literal
65
+ create_literal_block(parent, source, attributes, generator_info)
66
+ else
67
+ raise "Unsupported output format: #{format}"
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ #
74
+ # Registers a supported format. The first registered format becomes the default format for the block processor.
75
+ #
76
+ # +format+ is a symbol with the format name
77
+ # +type+ is a symbol and should be either :image or :literal
78
+ # +block+ is a block that produces the diagrams from code. The block receives the parent asciidoc block and the diagram code as arguments
79
+ #
80
+ def register_format(format, type, &block)
81
+ unless @default_format
82
+ @default_format = format
83
+ end
84
+
85
+ formats[format] = {
86
+ :type => type,
87
+ :generator => block
88
+ }
89
+ end
90
+
91
+ def formats
92
+ @formats ||= {}
93
+ end
94
+
95
+ def create_image_block(parent, source, attributes, format, generator_info)
96
+ target = attributes.delete('target')
97
+
98
+ image_name = "#{target || source.checksum}.#{format}"
99
+ image_dir = File.expand_path(parent.document.attributes['imagesdir'] || '', parent.document.attributes['docdir'])
100
+ image_file = File.expand_path(image_name, image_dir)
101
+
102
+ if source.newer_than?(image_file)
103
+ cache_file = File.expand_path("#{image_name}.cache", image_dir)
104
+
105
+ if File.exists? cache_file
106
+ metadata = File.open(cache_file, 'r') { |f| JSON.load f }
107
+ else
108
+ metadata = nil
109
+ end
110
+
111
+ unless File.exists?(image_file) && metadata && metadata['checksum'] == source.checksum
112
+ params = IMAGE_PARAMS[format]
113
+
114
+ result = generator_info[:generator].call(source.code, parent)
115
+
116
+ result.force_encoding(params[:encoding])
117
+
118
+ metadata = {'checksum' => source.checksum}
119
+ metadata['width'], metadata['height'] = params[:decoder].get_image_size(result)
120
+
121
+ File.open(image_file, 'w') { |f| f.write result }
122
+ File.open(cache_file, 'w') { |f| JSON.dump(metadata, f) }
123
+ end
124
+ end
125
+
126
+ attributes['target'] = image_name
127
+ attributes['width'] ||= metadata['width'] if metadata['width']
128
+ attributes['height'] ||= metadata['height'] if metadata['height']
129
+ attributes['alt'] ||= if (title_text = attributes['title'])
130
+ title_text
131
+ elsif target
132
+ (File.basename target, (File.extname target) || '').tr '_-', ' '
133
+ else
134
+ 'Diagram'
135
+ end
136
+
137
+ Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => attributes
138
+ end
139
+
140
+ def create_literal_block(parent, source, attributes, generator_info)
141
+ attributes.delete('target')
142
+
143
+ result = generator_info[:generator].call(source.code, parent)
144
+
145
+ result.force_encoding(Encoding::UTF_8)
146
+ Asciidoctor::Block.new parent, :literal, :code => result, :attributes => attributes
147
+ end
148
+
149
+ def code_checksum(code)
150
+ md5 = Digest::MD5.new
151
+ md5 << code
152
+ md5.hexdigest
153
+ end
154
+ end
155
+
156
+ class Source
157
+ def newer_than?(image)
158
+ true
159
+ end
160
+
161
+ def checksum
162
+ @checksum ||= compute_checksum(code)
163
+ end
164
+
165
+ private
166
+
167
+ def compute_checksum(code)
168
+ md5 = Digest::MD5.new
169
+ md5 << code
170
+ md5.hexdigest
171
+ end
172
+ end
173
+
174
+ class ReaderSource < Source
175
+ def initialize(reader)
176
+ @reader = reader
177
+ end
178
+
179
+ def code
180
+ @code ||= @reader.lines.join
181
+ end
182
+ end
183
+
184
+ class FileSource < Source
185
+ def initialize(file_name)
186
+ @file_name = file_name
187
+ end
188
+
189
+ def code
190
+ @code ||= File.read(@file_name)
191
+ end
192
+
193
+ def newer_than?(image)
194
+ !File.exists?(image) || File.mtime(@file_name) > File.mtime(image)
195
+ end
196
+ end
197
+ end
198
+ end
File without changes
File without changes
@@ -0,0 +1,46 @@
1
+ require_relative 'binaryio'
2
+
3
+ module Asciidoctor
4
+ module Diagram
5
+ module SVG
6
+ def self.get_image_size(data)
7
+ get_from_style(data) || get_from_viewport(data)
8
+ end
9
+
10
+ private
11
+
12
+ SVG_STYLE_REGEX = /style\s*=\s*"width:(?<width>\d+)px;height:(?<height>\d+)px/
13
+
14
+ def self.get_from_style(data)
15
+ match_data = SVG_STYLE_REGEX.match(data)
16
+ if match_data
17
+ [match_data[:width].to_i, match_data[:height].to_i]
18
+ else
19
+ nil
20
+ end
21
+ end
22
+
23
+ SVG_VIEWPORT_REGEX = /<svg width="(?<width>\d+)(?<width_unit>[a-zA-Z]+)" height="(?<height>\d+)(?<height_unit>[a-zA-Z]+)"/
24
+
25
+ def self.get_from_viewport(data)
26
+ match_data = SVG_VIEWPORT_REGEX.match(data)
27
+ if match_data
28
+ width = match_data[:width].to_i * to_px_factor(match_data[:width_unit])
29
+ height = match_data[:height].to_i * to_px_factor(match_data[:height_unit])
30
+ [width.to_i, height.to_i]
31
+ else
32
+ nil
33
+ end
34
+ end
35
+
36
+ def self.to_px_factor(unit)
37
+ case unit
38
+ when 'pt'
39
+ 1.33
40
+ else
41
+ 1
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end