asciidoctor-diagram 1.2.1 → 1.3.0.preview.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +10 -5
- data/README.adoc +27 -3
- data/lib/asciidoctor-diagram/blockdiag.rb +2 -6
- data/lib/asciidoctor-diagram/blockdiag/extension.rb +84 -3
- data/lib/asciidoctor-diagram/ditaa.rb +2 -2
- data/lib/asciidoctor-diagram/ditaa/extension.rb +16 -7
- data/lib/asciidoctor-diagram/extensions.rb +325 -0
- data/lib/asciidoctor-diagram/graphviz.rb +2 -2
- data/lib/asciidoctor-diagram/graphviz/extension.rb +17 -6
- data/lib/asciidoctor-diagram/plantuml.rb +2 -2
- data/lib/asciidoctor-diagram/plantuml/extension.rb +59 -11
- data/lib/asciidoctor-diagram/shaape.rb +2 -2
- data/lib/asciidoctor-diagram/shaape/extension.rb +17 -6
- data/lib/asciidoctor-diagram/util/binaryio.rb +2 -0
- data/lib/asciidoctor-diagram/util/cli_generator.rb +2 -1
- data/lib/asciidoctor-diagram/util/java_jruby.rb +1 -0
- data/lib/asciidoctor-diagram/util/java_rjb.rb +1 -0
- data/lib/asciidoctor-diagram/util/png.rb +1 -0
- data/lib/asciidoctor-diagram/util/svg.rb +1 -0
- data/lib/asciidoctor-diagram/util/which.rb +1 -0
- data/lib/asciidoctor-diagram/version.rb +1 -1
- data/lib/plantuml.jar +0 -0
- data/spec/blockdiag_spec.rb +2 -2
- data/spec/ditaa_spec.rb +2 -2
- data/spec/graphviz_spec.rb +2 -2
- data/spec/plantuml_spec.rb +2 -2
- data/spec/shaape_spec.rb +2 -2
- metadata +4 -5
- data/lib/asciidoctor-diagram/plantuml/generator.rb +0 -46
- data/lib/asciidoctor-diagram/util/diagram.rb +0 -220
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec30717e348b864e01a962749e47514a55c1106c
|
4
|
+
data.tar.gz: 3d85e72167829121d471faa6f20586cc2eb1eab4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: daedbb3ba7ef2bc7517ba7c67052a6ee9e468adf5a5ca710bbc3ca80f3dbdf078748b679696d95f791121e651f920d25bdf039e88ace183fde759f32c18b75ba
|
7
|
+
data.tar.gz: ff54af102f2a7d21eefa591f4db10985826c97f2eff4044bb305a91f2c0cc888f5d965fddea4bba4f75ad96ed03baa2f5bcca8edf6e952d664c5cd8663183cd7
|
data/CHANGELOG.adoc
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
= Asciidoctor-diagram Changelog
|
2
2
|
|
3
|
-
== 1.
|
3
|
+
== 1.3.0
|
4
4
|
|
5
5
|
Enhancements::
|
6
6
|
|
7
|
-
*
|
7
|
+
* Introduces a documented, public extension API
|
8
|
+
* Add support for Actdiag diagrams (requires Actdiag to be installed separately)
|
9
|
+
* Add support for Seqdiag diagrams (requires Seqdiag to be installed separately)
|
10
|
+
* Add support for Nwdiag diagrams (requires Nwdiag to be installed separately)
|
11
|
+
* Updated PlantUML to revision 8004 (23/08/2014)
|
8
12
|
|
9
13
|
== 1.2.0
|
10
14
|
|
@@ -16,11 +20,12 @@ Enhancements::
|
|
16
20
|
|
17
21
|
Enhancements::
|
18
22
|
|
19
|
-
* Updated PlantUML to revision 8002 (23/
|
20
|
-
* Add support for Shaape diagrams (requires Shaape
|
21
|
-
* Add support for Blockdiag diagrams (requires Blockdiag
|
23
|
+
* Updated PlantUML to revision 8002 (23/07/2014)
|
24
|
+
* Add support for Shaape diagrams (requires Shaape to be installed separately)
|
25
|
+
* Add support for Blockdiag diagrams (requires Blockdiag to be installed separately)
|
22
26
|
|
23
27
|
Bug Fixes::
|
28
|
+
|
24
29
|
* Issue #38: Resolved Graphviz syntax errors with certain diagrams
|
25
30
|
|
26
31
|
== 1.1.5
|
data/README.adoc
CHANGED
@@ -2,7 +2,14 @@
|
|
2
2
|
Pepijn Van_Eeckhoudt
|
3
3
|
|
4
4
|
Asciidoctor Diagram is a set of extensions for http://asciidoctor.org[Asciidoctor], the Ruby-based AsciiDoc processor.
|
5
|
-
These extensions allow you to embed diagrams
|
5
|
+
These extensions allow you to embed plain text diagrams inside your AsciiDoc documents using one of the following syntaxes:
|
6
|
+
|
7
|
+
- http://plantuml.sourceforge.net[PlantUML]
|
8
|
+
- http://ditaa.sourceforge.net[Ditaa].
|
9
|
+
- https://github.com/christiangoltz/shaape[Shaape]
|
10
|
+
- http://blockdiag.com[BlockDiag, SeqDiag, ActDiag, NwDiag]
|
11
|
+
- http://www.graphviz.org/content/dot-language[GraphViz DOT]
|
12
|
+
|
6
13
|
The extension takes care of running the diagram processor to generate the images from the input text and insert them into the rendered document.
|
7
14
|
|
8
15
|
This gem was inspired by the https://code.google.com/p/asciidoc-plantuml/[AsciiDoc PlantUML filter] for AsciiDoc Python.
|
@@ -23,6 +30,14 @@ Or install it yourself as:
|
|
23
30
|
|
24
31
|
$ gem install asciidoctor-diagram
|
25
32
|
|
33
|
+
=== Additional Requirements
|
34
|
+
|
35
|
+
Certain diagram types require other tools to be installed seperately.
|
36
|
+
|
37
|
+
- PlantUML and Graphviz: the Graphviz `dot` tool.
|
38
|
+
- Shaape: the Shaape Python package
|
39
|
+
- Block/Seq/Act/Nw diag: the block/seq/act/nw diag Python packages
|
40
|
+
|
26
41
|
== Usage
|
27
42
|
|
28
43
|
=== Enable the extensions
|
@@ -32,9 +47,11 @@ In order to use extensions you should need to invoke Asciidoctor via the http://
|
|
32
47
|
In your script you can then either require one or more of the following files:
|
33
48
|
|
34
49
|
. `asciidoctor-diagram`: to enable all the diagramming extensions
|
50
|
+
. `asciidoctor-diagram/blockdiag`: to enable the block/act/seq/nw diag extension
|
35
51
|
. `asciidoctor-diagram/ditaa`: to enable the ditaa extension
|
36
52
|
. `asciidoctor-diagram/graphviz`: to enable the graphviz extension
|
37
53
|
. `asciidoctor-diagram/plantuml`: to enable the plantuml extension
|
54
|
+
. `asciidoctor-diagram/shaape`: to enable the shaape extension
|
38
55
|
|
39
56
|
Requiring one or more of these files will automatically register the extensions for all processed documents.
|
40
57
|
If you need more fine grained control over when the extensions are enabled or not, `asciidoctor-diagram/ditaa/extension`, `asciidoctor-diagram/graphviz/extension` and `asciidoctor-diagram/plantuml/extension` can be used instead.
|
@@ -43,7 +60,14 @@ You should then register the extensions yourself at the appropriate time using t
|
|
43
60
|
|
44
61
|
=== Using the extensions
|
45
62
|
|
46
|
-
Once the extensions are enabled
|
63
|
+
Once the extensions are enabled the following block types becomes available for your documents:
|
64
|
+
|
65
|
+
- `blockdiag`, `actdiag`, `seqdiag`, `nwdiag`, `rackdiag` and `packetdiag`
|
66
|
+
- `ditaa`
|
67
|
+
- `graphviz`
|
68
|
+
- `plantuml`
|
69
|
+
- `shaape`
|
70
|
+
|
47
71
|
Detailed descriptions of the supported syntax inside these blocks is available on the http://plantuml.sourceforge.net/[PlantUML], http://www.graphviz.org/content/dot-language[Graphviz] and http://ditaa.sourceforge.net/[ditaa] websites.
|
48
72
|
|
49
73
|
At this point you can start adding diagrams to your application, like the following example:
|
@@ -65,7 +89,7 @@ DiagramBlock <|-- PlantUmlBlock
|
|
65
89
|
The diagram blocks support the following attributes:
|
66
90
|
|
67
91
|
. `target` (or 2nd position): the basename of the file to generate. If not specified an auto-generated name will be used.
|
68
|
-
. `format` (or 3rd position): the output format. PlantUML blocks support `png`, `svg` and `txt`. Graphviz
|
92
|
+
. `format` (or 3rd position): the output format. PlantUML blocks support `png`, `svg` and `txt`. Graphviz, Shaape and BlockDiag support `png` and `svg`. Ditaa only supports `png`.
|
69
93
|
|
70
94
|
== Contributing
|
71
95
|
|
@@ -5,11 +5,7 @@ Asciidoctor::Extensions.register do
|
|
5
5
|
require_relative 'blockdiag/extension'
|
6
6
|
|
7
7
|
['BlockDiag', 'SeqDiag', 'ActDiag', 'NwDiag', 'RackDiag', 'PacketDiag'].each do |tool|
|
8
|
-
|
9
|
-
|
10
|
-
block_macro = Asciidoctor::Diagram.const_get("#{tool}BlockMacro")
|
11
|
-
|
12
|
-
block block, name
|
13
|
-
block_macro block_macro, name
|
8
|
+
block ::Asciidoctor::Diagram.const_get("#{tool}BlockProcessor"), tool.downcase.to_sym
|
9
|
+
block_macro ::Asciidoctor::Diagram.const_get("#{tool}BlockMacroProcessor"), tool.downcase.to_sym
|
14
10
|
end
|
15
11
|
end
|
@@ -1,13 +1,94 @@
|
|
1
|
+
require_relative '../extensions'
|
1
2
|
require_relative '../util/cli_generator'
|
2
|
-
require_relative '../util/diagram'
|
3
3
|
|
4
4
|
module Asciidoctor
|
5
5
|
module Diagram
|
6
|
+
# @private
|
7
|
+
module BlockDiag
|
8
|
+
def self.define_processors(name, &init)
|
9
|
+
block = Class.new(Extensions::DiagramBlockProcessor) do
|
10
|
+
self.instance_eval &init
|
11
|
+
end
|
12
|
+
::Asciidoctor::Diagram.const_set("#{name}BlockProcessor", block)
|
13
|
+
|
14
|
+
block_macro = Class.new(Extensions::DiagramBlockMacroProcessor) do
|
15
|
+
self.instance_eval &init
|
16
|
+
end
|
17
|
+
|
18
|
+
::Asciidoctor::Diagram.const_set("#{name}BlockMacroProcessor", block_macro)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!parse
|
23
|
+
# # Block processor converts blockdiag code into images.
|
24
|
+
# #
|
25
|
+
# # Supports PNG and SVG output.
|
26
|
+
# class BlockDiagBlockProcessor < API::DiagramBlockProcessor; end
|
27
|
+
#
|
28
|
+
# # Block macro processor converts blockdiag source files into images.
|
29
|
+
# #
|
30
|
+
# # Supports PNG and SVG output.
|
31
|
+
# class BlockDiagBlockMacroProcessor < DiagramBlockMacroProcessor; end
|
32
|
+
|
33
|
+
# @!parse
|
34
|
+
# # Block processor converts seqdiag code into images.
|
35
|
+
# #
|
36
|
+
# # Supports PNG and SVG output.
|
37
|
+
# class SeqDiagBlockProcessor < API::DiagramBlockProcessor; end
|
38
|
+
#
|
39
|
+
# # Block macro processor converts seqdiag source files into images.
|
40
|
+
# #
|
41
|
+
# # Supports PNG and SVG output.
|
42
|
+
# class SeqDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
|
43
|
+
|
44
|
+
# @!parse
|
45
|
+
# # Block processor converts actdiag code into images.
|
46
|
+
# #
|
47
|
+
# # Supports PNG and SVG output.
|
48
|
+
# class ActDiagBlockProcessor < API::DiagramBlockProcessor; end
|
49
|
+
#
|
50
|
+
# # Block macro processor converts actdiag source files into images.
|
51
|
+
# #
|
52
|
+
# # Supports PNG and SVG output.
|
53
|
+
# class ActDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
|
54
|
+
|
55
|
+
# @!parse
|
56
|
+
# # Block processor converts nwdiag code into images.
|
57
|
+
# #
|
58
|
+
# # Supports PNG and SVG output.
|
59
|
+
# class NwDiagBlockProcessor < API::DiagramBlockProcessor; end
|
60
|
+
#
|
61
|
+
# # Block macro processor converts nwdiag source files into images.
|
62
|
+
# #
|
63
|
+
# # Supports PNG and SVG output.
|
64
|
+
# class NwDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
|
65
|
+
|
66
|
+
# @!parse
|
67
|
+
# # Block processor converts rackdiag code into images.
|
68
|
+
# #
|
69
|
+
# # Supports PNG and SVG output.
|
70
|
+
# class RackDiagBlockProcessor < API::DiagramBlockProcessor; end
|
71
|
+
#
|
72
|
+
# # Block macro processor converts rackdiag source files into images.
|
73
|
+
# #
|
74
|
+
# # Supports PNG and SVG output.
|
75
|
+
# class RackDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
|
76
|
+
|
77
|
+
# @!parse
|
78
|
+
# # Block processor converts packetdiag code into images.
|
79
|
+
# #
|
80
|
+
# # Supports PNG and SVG output.
|
81
|
+
# class PacketDiagBlockProcessor < API::DiagramBlockProcessor; end
|
82
|
+
#
|
83
|
+
# # Block macro processor converts packetdiag source files into images.
|
84
|
+
# #
|
85
|
+
# # Supports PNG and SVG output.
|
86
|
+
# class PacketDiagBlockMacroProcessor < API::DiagramBlockMacroProcessor; end
|
6
87
|
['BlockDiag', 'SeqDiag', 'ActDiag', 'NwDiag', 'RackDiag', 'PacketDiag'].each do |tool|
|
7
|
-
define_processors(tool) do
|
88
|
+
BlockDiag.define_processors(tool) do
|
8
89
|
[:png, :svg].each do |f|
|
9
90
|
register_format(f, :image) do |c, p|
|
10
|
-
CliGenerator.generate(tool.downcase, p, c) do |tool_path, output_path|
|
91
|
+
CliGenerator.generate(tool.downcase, p, c.to_s) do |tool_path, output_path|
|
11
92
|
[tool_path, '-o', output_path, "-T#{f.to_s}", '-']
|
12
93
|
end
|
13
94
|
end
|
@@ -3,6 +3,6 @@ require_relative 'version'
|
|
3
3
|
|
4
4
|
Asciidoctor::Extensions.register do
|
5
5
|
require_relative 'ditaa/extension'
|
6
|
-
block Asciidoctor::Diagram::
|
7
|
-
block_macro Asciidoctor::Diagram::
|
6
|
+
block Asciidoctor::Diagram::DitaaBlockProcessor, :ditaa
|
7
|
+
block_macro Asciidoctor::Diagram::DitaaBlockMacroProcessor, :ditaa
|
8
8
|
end
|
@@ -1,13 +1,14 @@
|
|
1
|
-
require_relative '../
|
1
|
+
require_relative '../extensions'
|
2
2
|
require_relative '../util/java'
|
3
3
|
|
4
4
|
module Asciidoctor
|
5
5
|
module Diagram
|
6
|
-
|
6
|
+
# @private
|
7
|
+
module Ditaa
|
7
8
|
DITAA_JAR_PATH = File.expand_path File.join('../..', 'ditaamini0_9.jar'), File.dirname(__FILE__)
|
8
9
|
Java.classpath << DITAA_JAR_PATH
|
9
10
|
|
10
|
-
def
|
11
|
+
def ditaa(code)
|
11
12
|
Java.load
|
12
13
|
|
13
14
|
args = ['-e', 'UTF-8']
|
@@ -25,12 +26,20 @@ module Asciidoctor
|
|
25
26
|
|
26
27
|
result
|
27
28
|
end
|
28
|
-
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def self.included(mod)
|
31
|
+
mod.register_format(:png, :image) do |c|
|
32
|
+
ditaa(c.to_s)
|
33
|
+
end
|
33
34
|
end
|
34
35
|
end
|
36
|
+
|
37
|
+
class DitaaBlockProcessor < Extensions::DiagramBlockProcessor
|
38
|
+
include Ditaa
|
39
|
+
end
|
40
|
+
|
41
|
+
class DitaaBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
|
42
|
+
include Ditaa
|
43
|
+
end
|
35
44
|
end
|
36
45
|
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'asciidoctor/extensions'
|
2
|
+
require 'digest'
|
3
|
+
require 'json'
|
4
|
+
require 'fileutils'
|
5
|
+
require_relative 'util/java'
|
6
|
+
require_relative 'util/png'
|
7
|
+
require_relative 'util/svg'
|
8
|
+
|
9
|
+
module Asciidoctor
|
10
|
+
module Diagram
|
11
|
+
module Extensions
|
12
|
+
# Provides the means for diagram processors to register supported output formats and image
|
13
|
+
# generation routines
|
14
|
+
module FormatRegistry
|
15
|
+
# Registers a supported format. The first registered format becomes the default format for the block
|
16
|
+
# processor.
|
17
|
+
#
|
18
|
+
# @param [Symbol] format the format name
|
19
|
+
# @param [Symbol] type a symbol indicating the type of block that should be generated; either :image or :literal
|
20
|
+
# @yieldparam parent [Asciidoctor::AbstractNode] the asciidoc block that is being processed
|
21
|
+
# @yieldparam source [DiagramSource] the source object
|
22
|
+
# @yieldreturn [String] the generated diagram
|
23
|
+
#
|
24
|
+
# Examples
|
25
|
+
#
|
26
|
+
# register_format(:png, :image ) do |parent, source|
|
27
|
+
# File.read(source.to_s)
|
28
|
+
# end
|
29
|
+
def register_format(format, type, &block)
|
30
|
+
unless @default_format
|
31
|
+
@default_format = format
|
32
|
+
end
|
33
|
+
|
34
|
+
formats[format] = {
|
35
|
+
:type => type,
|
36
|
+
:generator => block
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the registered formats
|
41
|
+
#
|
42
|
+
# @return [Hash]
|
43
|
+
# @api private
|
44
|
+
def formats
|
45
|
+
@formats ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the default format
|
49
|
+
#
|
50
|
+
# @return [Symbol] the default format
|
51
|
+
# @api private
|
52
|
+
def default_format
|
53
|
+
@default_format
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Mixin that provides the basic machinery for image generation.
|
58
|
+
# When this module is included it will include the FormatRegistry into the singleton class of the target class.
|
59
|
+
module DiagramProcessor
|
60
|
+
IMAGE_PARAMS = {
|
61
|
+
:svg => {
|
62
|
+
:encoding => Encoding::UTF_8,
|
63
|
+
:decoder => SVG
|
64
|
+
},
|
65
|
+
:png => {
|
66
|
+
:encoding => Encoding::ASCII_8BIT,
|
67
|
+
:decoder => PNG
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
def self.included(mod)
|
72
|
+
class << mod
|
73
|
+
include FormatRegistry
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Processes the diagram block or block macro by converting it into an image or literal block.
|
78
|
+
#
|
79
|
+
# @param parent [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed
|
80
|
+
# @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the
|
81
|
+
# target value of a block macro
|
82
|
+
# @param attributes [Hash] the attributes of the block or block macro
|
83
|
+
# @return [Asciidoctor::AbstractBlock] a new block that replaces the original block or block macro
|
84
|
+
def process(parent, reader_or_target, attributes)
|
85
|
+
source = create_source(parent, reader_or_target, attributes)
|
86
|
+
|
87
|
+
format = attributes.delete('format') || self.class.default_format
|
88
|
+
format = format.to_sym if format.respond_to?(:to_sym)
|
89
|
+
|
90
|
+
raise "Format undefined" unless format
|
91
|
+
|
92
|
+
generator_info = self.class.formats[format]
|
93
|
+
|
94
|
+
raise "#{self.class.name} does not support output format #{format}" unless generator_info
|
95
|
+
|
96
|
+
case generator_info[:type]
|
97
|
+
when :image
|
98
|
+
create_image_block(parent, source, attributes, format, generator_info)
|
99
|
+
when :literal
|
100
|
+
create_literal_block(parent, source, attributes, generator_info)
|
101
|
+
else
|
102
|
+
raise "Unsupported output format: #{format}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
# Creates a DiagramSource object for the block or block macro being processed. Classes using this
|
109
|
+
# mixin must implement this method.
|
110
|
+
#
|
111
|
+
# @param parent [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed
|
112
|
+
# @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the
|
113
|
+
# target value of a block macro
|
114
|
+
# @param attributes [Hash] the attributes of the block or block macro
|
115
|
+
#
|
116
|
+
# @return [DiagramSource] an object that implements the interface described by DiagramSource
|
117
|
+
#
|
118
|
+
# @abstract
|
119
|
+
def create_source(parent, reader_or_target, attributes)
|
120
|
+
raise NotImplementedError.new
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
def create_image_block(parent, source, attributes, format, generator_info)
|
125
|
+
image_name = "#{source.image_name}.#{format}"
|
126
|
+
outdir = parent.document.attr('imagesoutdir') || parent.document.attr('outdir')
|
127
|
+
image_dir = parent.normalize_system_path parent.document.attr 'imagesdir', outdir
|
128
|
+
image_file = parent.normalize_system_path image_name, image_dir
|
129
|
+
metadata_file = parent.normalize_system_path "#{image_name}.cache", image_dir
|
130
|
+
|
131
|
+
if File.exists? metadata_file
|
132
|
+
metadata = File.open(metadata_file, 'r') { |f| JSON.load f }
|
133
|
+
else
|
134
|
+
metadata = {}
|
135
|
+
end
|
136
|
+
|
137
|
+
if !File.exists?(image_file) || source.should_process?(image_file, metadata)
|
138
|
+
params = IMAGE_PARAMS[format]
|
139
|
+
|
140
|
+
result = instance_exec(source, parent, &generator_info[:generator])
|
141
|
+
|
142
|
+
result.force_encoding(params[:encoding])
|
143
|
+
|
144
|
+
metadata = source.create_image_metadata
|
145
|
+
metadata['width'], metadata['height'] = params[:decoder].get_image_size(result)
|
146
|
+
|
147
|
+
FileUtils.mkdir_p(image_dir) unless Dir.exists?(image_dir)
|
148
|
+
File.open(image_file, 'wb') { |f| f.write result }
|
149
|
+
File.open(metadata_file, 'w') { |f| JSON.dump(metadata, f) }
|
150
|
+
end
|
151
|
+
|
152
|
+
image_attributes = attributes.dup
|
153
|
+
image_attributes['target'] = image_name
|
154
|
+
if /html/i =~ parent.document.attributes['backend']
|
155
|
+
image_attributes['width'] ||= metadata['width'] if metadata['width']
|
156
|
+
image_attributes['height'] ||= metadata['height'] if metadata['height']
|
157
|
+
end
|
158
|
+
image_attributes['alt'] ||= if title_text = attributes['title']
|
159
|
+
title_text
|
160
|
+
elsif target = attributes['target']
|
161
|
+
(File.basename target, (File.extname target) || '').tr '_-', ' '
|
162
|
+
else
|
163
|
+
'Diagram'
|
164
|
+
end
|
165
|
+
|
166
|
+
Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => image_attributes
|
167
|
+
end
|
168
|
+
|
169
|
+
def create_literal_block(parent, source, attributes, generator_info)
|
170
|
+
literal_attributes = attributes.dup
|
171
|
+
literal_attributes.delete('target')
|
172
|
+
|
173
|
+
result = instance_exec(source, parent, &generator_info[:generator])
|
174
|
+
|
175
|
+
result.force_encoding(Encoding::UTF_8)
|
176
|
+
Asciidoctor::Block.new parent, :literal, :source => result, :attributes => literal_attributes
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Base class for diagram block processors.
|
181
|
+
class DiagramBlockProcessor < Asciidoctor::Extensions::BlockProcessor
|
182
|
+
include DiagramProcessor
|
183
|
+
|
184
|
+
def self.inherited(subclass)
|
185
|
+
subclass.option :pos_attrs, ['target', 'format']
|
186
|
+
subclass.option :contexts, [:listing, :literal, :open]
|
187
|
+
subclass.option :content_model, :simple
|
188
|
+
end
|
189
|
+
|
190
|
+
# Creates a ReaderSource from the given reader.
|
191
|
+
#
|
192
|
+
# @return [ReaderSource] a ReaderSource
|
193
|
+
def create_source(parent, reader, attributes)
|
194
|
+
ReaderSource.new(reader, attributes)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Base class for diagram block macro processors.
|
199
|
+
class DiagramBlockMacroProcessor < Asciidoctor::Extensions::BlockMacroProcessor
|
200
|
+
include DiagramProcessor
|
201
|
+
|
202
|
+
def self.inherited(subclass)
|
203
|
+
subclass.option :pos_attrs, ['target', 'format']
|
204
|
+
end
|
205
|
+
|
206
|
+
# Creates a FileSource using target as the file name.
|
207
|
+
#
|
208
|
+
# @return [FileSource] a FileSource
|
209
|
+
def create_source(parent, target, attributes)
|
210
|
+
FileSource.new(File.expand_path(target, parent.document.attributes['docdir']), attributes)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# This module describes the duck-typed interface that diagram sources must implement. Implementations
|
215
|
+
# may include this module but it is not required.
|
216
|
+
module DiagramSource
|
217
|
+
# @return [String] the base name for the image file that will be produced
|
218
|
+
# @abstract
|
219
|
+
def image_name
|
220
|
+
raise NotImplementedError.new
|
221
|
+
end
|
222
|
+
|
223
|
+
# @return [String] the String representation of the source code for the diagram
|
224
|
+
# @abstract
|
225
|
+
def code
|
226
|
+
raise NotImplementedError.new
|
227
|
+
end
|
228
|
+
|
229
|
+
# Alias for code
|
230
|
+
def to_s
|
231
|
+
code
|
232
|
+
end
|
233
|
+
|
234
|
+
# Determines if the diagram should be regenerated or not. The default implementation of this method simply
|
235
|
+
# returns true.
|
236
|
+
#
|
237
|
+
# @param image_file [String] the path to the previously generated version of the image
|
238
|
+
# @param image_metadata [Hash] the image metadata Hash that was stored during the previous diagram generation pass
|
239
|
+
# @return [Boolean] true if the diagram should be regenerated; false otherwise
|
240
|
+
def should_process?(image_file, image_metadata)
|
241
|
+
true
|
242
|
+
end
|
243
|
+
|
244
|
+
# Creates an image metadata Hash that will be stored to disk alongside the generated image file. The contents
|
245
|
+
# of this Hash are reread during subsequent document processing and then passed to the should_process? method
|
246
|
+
# where it can be used to determine if the diagram should be regenerated or not.
|
247
|
+
# The default implementation returns an empty Hash.
|
248
|
+
# @return [Hash] a Hash containing metadata
|
249
|
+
def create_image_metadata
|
250
|
+
{}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Base class for diagram source implementations that uses an md5 checksum of the source code of a diagram to
|
255
|
+
# determine if it has been updated or not.
|
256
|
+
class BasicSource
|
257
|
+
include DiagramSource
|
258
|
+
|
259
|
+
def initialize(attributes)
|
260
|
+
@attributes = attributes
|
261
|
+
end
|
262
|
+
|
263
|
+
def image_name
|
264
|
+
@attributes['target'] || ('diag-' + checksum)
|
265
|
+
end
|
266
|
+
|
267
|
+
def should_process?(image_file, image_metadata)
|
268
|
+
image_metadata['checksum'] != checksum
|
269
|
+
end
|
270
|
+
|
271
|
+
def create_image_metadata
|
272
|
+
{'checksum' => checksum}
|
273
|
+
end
|
274
|
+
|
275
|
+
def checksum
|
276
|
+
@checksum ||= compute_checksum(code)
|
277
|
+
end
|
278
|
+
|
279
|
+
def compute_checksum(code)
|
280
|
+
md5 = Digest::MD5.new
|
281
|
+
md5 << code
|
282
|
+
md5.hexdigest
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# A diagram source that retrieves the code for the diagram from the contents of a block.
|
287
|
+
class ReaderSource < BasicSource
|
288
|
+
include DiagramSource
|
289
|
+
|
290
|
+
def initialize(reader, attributes)
|
291
|
+
super(attributes)
|
292
|
+
@reader = reader
|
293
|
+
end
|
294
|
+
|
295
|
+
def code
|
296
|
+
@code ||= @reader.lines.join("\n")
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# A diagram source that retrieves the code for a diagram from an external source file.
|
301
|
+
class FileSource < BasicSource
|
302
|
+
def initialize(file_name, attributes)
|
303
|
+
super(attributes)
|
304
|
+
@file_name = file_name
|
305
|
+
end
|
306
|
+
|
307
|
+
def image_name
|
308
|
+
if @attributes['target']
|
309
|
+
super
|
310
|
+
else
|
311
|
+
File.basename(@file_name, File.extname(@file_name))
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def should_process?(image_file, image_metadata)
|
316
|
+
File.mtime(@file_name) > File.mtime(image_file) || super
|
317
|
+
end
|
318
|
+
|
319
|
+
def code
|
320
|
+
@code ||= File.read(@file_name)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -3,6 +3,6 @@ require_relative 'version'
|
|
3
3
|
|
4
4
|
Asciidoctor::Extensions.register do
|
5
5
|
require_relative 'graphviz/extension'
|
6
|
-
block Asciidoctor::Diagram::
|
7
|
-
block_macro Asciidoctor::Diagram::
|
6
|
+
block Asciidoctor::Diagram::GraphvizBlockProcessor, :graphviz
|
7
|
+
block_macro Asciidoctor::Diagram::GraphvizBlockMacroProcessor, :graphviz
|
8
8
|
end
|
@@ -1,16 +1,27 @@
|
|
1
|
+
require_relative '../extensions'
|
1
2
|
require_relative '../util/cli_generator'
|
2
|
-
require_relative '../util/diagram'
|
3
3
|
|
4
4
|
module Asciidoctor
|
5
5
|
module Diagram
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
# @private
|
7
|
+
module Graphviz
|
8
|
+
def self.included(mod)
|
9
|
+
[:png, :svg].each do |f|
|
10
|
+
mod.register_format(f, :image) do |c, p|
|
11
|
+
CliGenerator.generate('dot', p, c.to_s) do |tool_path, output_path|
|
12
|
+
[tool_path, "-o#{output_path}", "-T#{f.to_s}"]
|
13
|
+
end
|
11
14
|
end
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
18
|
+
|
19
|
+
class GraphvizBlockProcessor < Extensions::DiagramBlockProcessor
|
20
|
+
include Graphviz
|
21
|
+
end
|
22
|
+
|
23
|
+
class GraphvizBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
|
24
|
+
include Graphviz
|
25
|
+
end
|
15
26
|
end
|
16
27
|
end
|
@@ -4,6 +4,6 @@ require_relative 'version'
|
|
4
4
|
Asciidoctor::Extensions.register do
|
5
5
|
require_relative 'plantuml/extension'
|
6
6
|
|
7
|
-
block Asciidoctor::Diagram::
|
8
|
-
block_macro Asciidoctor::Diagram::
|
7
|
+
block Asciidoctor::Diagram::PlantUmlBlockProcessor, :plantuml
|
8
|
+
block_macro Asciidoctor::Diagram::PlantUmlBlockMacroProcessor, :plantuml
|
9
9
|
end
|
@@ -1,9 +1,47 @@
|
|
1
|
-
require_relative '../
|
2
|
-
require_relative 'generator'
|
1
|
+
require_relative '../extensions'
|
3
2
|
|
4
3
|
module Asciidoctor
|
5
4
|
module Diagram
|
6
|
-
|
5
|
+
# @private
|
6
|
+
module PlantUml
|
7
|
+
private
|
8
|
+
|
9
|
+
PLANTUML_JAR_PATH = File.expand_path File.join('../..', 'plantuml.jar'), File.dirname(__FILE__)
|
10
|
+
Java.classpath << PLANTUML_JAR_PATH
|
11
|
+
|
12
|
+
def plantuml(parent, code, tag, *flags)
|
13
|
+
unless @graphvizdot
|
14
|
+
@graphvizdot = parent.document.attributes['dot']
|
15
|
+
@graphvizdot = ::Asciidoctor::Diagram.which('dot') unless @graphvizdot && File.executable?(@graphvizdot)
|
16
|
+
raise "Could not find the Graphviz 'dot' executable in PATH; add it to the PATH or specify its location using the 'dot' document attribute" unless @graphvizdot
|
17
|
+
end
|
18
|
+
|
19
|
+
Java.load
|
20
|
+
|
21
|
+
code = "@start#{tag}\n#{code}\n@end#{tag}" unless code.index "@start#{tag}"
|
22
|
+
|
23
|
+
flags += ['-charset', 'UTF-8', '-failonerror', '-graphvizdot', @graphvizdot]
|
24
|
+
|
25
|
+
config_file = parent.document.attributes['plantumlconfig']
|
26
|
+
if config_file
|
27
|
+
flags += ['-config', File.expand_path(config_file, parent.document.attributes['docdir'])]
|
28
|
+
end
|
29
|
+
|
30
|
+
option = Java.new_object( Java.net.sourceforge.plantuml.Option, '[Ljava.lang.String;', Java.array_to_java_array(flags, :string) )
|
31
|
+
source_reader = Java.new_object( Java.net.sourceforge.plantuml.SourceStringReader,
|
32
|
+
'Lnet.sourceforge.plantuml.preproc.Defines;Ljava.lang.String;Ljava.util.List;',
|
33
|
+
Java.new_object( Java.net.sourceforge.plantuml.preproc.Defines ),
|
34
|
+
code,
|
35
|
+
option.getConfig()
|
36
|
+
)
|
37
|
+
|
38
|
+
bos = Java.new_object( Java.java.io.ByteArrayOutputStream )
|
39
|
+
ps = Java.new_object( Java.java.io.PrintStream, 'Ljava.io.OutputStream;', bos )
|
40
|
+
source_reader.generateImage(ps, 0, option.getFileFormatOption())
|
41
|
+
ps.close
|
42
|
+
Java.string_from_java_bytes(bos.toByteArray)
|
43
|
+
end
|
44
|
+
|
7
45
|
def config_args(parent)
|
8
46
|
config_args = []
|
9
47
|
config = parent.document.attributes['plantumlconfig']
|
@@ -14,15 +52,25 @@ module Asciidoctor
|
|
14
52
|
config_args
|
15
53
|
end
|
16
54
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
55
|
+
def self.included(mod)
|
56
|
+
mod.register_format(:png, :image) do |c, p|
|
57
|
+
plantuml(p, c.to_s, 'uml', *config_args(p))
|
58
|
+
end
|
59
|
+
mod.register_format(:svg, :image) do |c, p|
|
60
|
+
plantuml(p, c.to_s, 'uml', '-tsvg', *config_args(p))
|
61
|
+
end
|
62
|
+
mod.register_format(:txt, :literal) do |c, p|
|
63
|
+
plantuml(p, c.to_s, 'uml', '-tutxt', *config_args(p))
|
64
|
+
end
|
25
65
|
end
|
26
66
|
end
|
67
|
+
|
68
|
+
class PlantUmlBlockProcessor < Extensions::DiagramBlockProcessor
|
69
|
+
include PlantUml
|
70
|
+
end
|
71
|
+
|
72
|
+
class PlantUmlBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
|
73
|
+
include PlantUml
|
74
|
+
end
|
27
75
|
end
|
28
76
|
end
|
@@ -4,6 +4,6 @@ require_relative 'version'
|
|
4
4
|
Asciidoctor::Extensions.register do
|
5
5
|
require_relative 'shaape/extension'
|
6
6
|
|
7
|
-
block Asciidoctor::Diagram::
|
8
|
-
block_macro Asciidoctor::Diagram::
|
7
|
+
block Asciidoctor::Diagram::ShaapeBlockProcessor, :shaape
|
8
|
+
block_macro Asciidoctor::Diagram::ShaapeBlockMacroProcessor, :shaape
|
9
9
|
end
|
@@ -1,16 +1,27 @@
|
|
1
|
+
require_relative '../extensions'
|
1
2
|
require_relative '../util/cli_generator'
|
2
|
-
require_relative '../util/diagram'
|
3
3
|
|
4
4
|
module Asciidoctor
|
5
5
|
module Diagram
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
# @private
|
7
|
+
module Shaape
|
8
|
+
def self.included(mod)
|
9
|
+
[:png, :svg].each do |f|
|
10
|
+
mod.register_format(f, :image) do |c, p|
|
11
|
+
CliGenerator.generate('shaape', p, c.to_s) do |tool_path, output_path|
|
12
|
+
[tool_path, '-o', output_path, '-t', f.to_s, '-']
|
13
|
+
end
|
11
14
|
end
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
18
|
+
|
19
|
+
class ShaapeBlockProcessor < Extensions::DiagramBlockProcessor
|
20
|
+
include Shaape
|
21
|
+
end
|
22
|
+
|
23
|
+
class ShaapeBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
|
24
|
+
include Shaape
|
25
|
+
end
|
15
26
|
end
|
16
27
|
end
|
@@ -5,6 +5,7 @@ require_relative '../util/which'
|
|
5
5
|
|
6
6
|
module Asciidoctor
|
7
7
|
module Diagram
|
8
|
+
# @private
|
8
9
|
module CliGenerator
|
9
10
|
def self.generate(tool, parent, code)
|
10
11
|
tool_var = '@' + tool
|
@@ -13,7 +14,7 @@ module Asciidoctor
|
|
13
14
|
unless tool_path
|
14
15
|
tool_path = parent.document.attributes[tool]
|
15
16
|
tool_path = ::Asciidoctor::Diagram.which(tool) unless tool_path && File.executable?(tool_path)
|
16
|
-
raise "Could not find the '#{tool}' executable in PATH; add it to the PATH or specify its location using the '
|
17
|
+
raise "Could not find the '#{tool}' executable in PATH; add it to the PATH or specify its location using the '#{tool}' document attribute" unless tool_path
|
17
18
|
instance_variable_set(tool_var, tool_path)
|
18
19
|
end
|
19
20
|
|
data/lib/plantuml.jar
CHANGED
Binary file
|
data/spec/blockdiag_spec.rb
CHANGED
@@ -7,7 +7,7 @@ blockdiag {
|
|
7
7
|
}
|
8
8
|
eos
|
9
9
|
|
10
|
-
describe Asciidoctor::Diagram::
|
10
|
+
describe Asciidoctor::Diagram::BlockDiagBlockMacroProcessor do
|
11
11
|
it "should generate PNG images when format is set to 'png'" do
|
12
12
|
File.write('blockdiag.txt', code)
|
13
13
|
|
@@ -38,7 +38,7 @@ blockdiag::blockdiag.txt[format="png"]
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
describe Asciidoctor::Diagram::
|
41
|
+
describe Asciidoctor::Diagram::BlockDiagBlockProcessor do
|
42
42
|
it "should generate PNG images when format is set to 'png'" do
|
43
43
|
doc = <<-eos
|
44
44
|
= Hello, BlockDiag!
|
data/spec/ditaa_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
describe Asciidoctor::Diagram::
|
3
|
+
describe Asciidoctor::Diagram::DitaaBlockMacroProcessor do
|
4
4
|
it "should generate PNG images when format is set to 'png'" do
|
5
5
|
code = <<-eos
|
6
6
|
+--------+ +-------+ +-------+
|
@@ -43,7 +43,7 @@ ditaa::ditaa.txt[format="png"]
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
describe Asciidoctor::Diagram::
|
46
|
+
describe Asciidoctor::Diagram::DitaaBlockProcessor do
|
47
47
|
it "should generate PNG images when format is set to 'png'" do
|
48
48
|
doc = <<-eos
|
49
49
|
= Hello, ditaa!
|
data/spec/graphviz_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
describe Asciidoctor::Diagram::
|
3
|
+
describe Asciidoctor::Diagram::GraphvizBlockMacroProcessor do
|
4
4
|
it "should generate PNG images when format is set to 'png'" do
|
5
5
|
code = <<-eos
|
6
6
|
digraph foo {
|
@@ -42,7 +42,7 @@ graphviz::graphviz.txt[format="png"]
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
describe Asciidoctor::Diagram::
|
45
|
+
describe Asciidoctor::Diagram::GraphvizBlockProcessor do
|
46
46
|
it "should generate PNG images when format is set to 'png'" do
|
47
47
|
doc = <<-eos
|
48
48
|
= Hello, graphviz!
|
data/spec/plantuml_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
describe Asciidoctor::Diagram::
|
3
|
+
describe Asciidoctor::Diagram::PlantUmlBlockMacroProcessor do
|
4
4
|
it "should generate PNG images when format is set to 'png'" do
|
5
5
|
code = <<-eos
|
6
6
|
User -> (Start)
|
@@ -38,7 +38,7 @@ plantuml::plantuml.txt[format="png"]
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
describe Asciidoctor::Diagram::
|
41
|
+
describe Asciidoctor::Diagram::PlantUmlBlockProcessor do
|
42
42
|
it "should generate PNG images when format is set to 'png'" do
|
43
43
|
doc = <<-eos
|
44
44
|
= Hello, PlantUML!
|
data/spec/shaape_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
describe Asciidoctor::Diagram::
|
3
|
+
describe Asciidoctor::Diagram::ShaapeBlockMacroProcessor do
|
4
4
|
it "should generate PNG images when format is set to 'png'" do
|
5
5
|
code = <<-eos
|
6
6
|
+--------+ +-------------+
|
@@ -40,7 +40,7 @@ shaape::shaape.txt[format="png"]
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
describe Asciidoctor::Diagram::
|
43
|
+
describe Asciidoctor::Diagram::ShaapeBlockProcessor do
|
44
44
|
it "should generate PNG images when format is set to 'png'" do
|
45
45
|
doc = <<-eos
|
46
46
|
= Hello, Shaape!
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asciidoctor-diagram
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0.preview.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pepijn Van Eeckhoudt
|
@@ -100,16 +100,15 @@ files:
|
|
100
100
|
- lib/asciidoctor-diagram/blockdiag/extension.rb
|
101
101
|
- lib/asciidoctor-diagram/ditaa.rb
|
102
102
|
- lib/asciidoctor-diagram/ditaa/extension.rb
|
103
|
+
- lib/asciidoctor-diagram/extensions.rb
|
103
104
|
- lib/asciidoctor-diagram/graphviz.rb
|
104
105
|
- lib/asciidoctor-diagram/graphviz/extension.rb
|
105
106
|
- lib/asciidoctor-diagram/plantuml.rb
|
106
107
|
- lib/asciidoctor-diagram/plantuml/extension.rb
|
107
|
-
- lib/asciidoctor-diagram/plantuml/generator.rb
|
108
108
|
- lib/asciidoctor-diagram/shaape.rb
|
109
109
|
- lib/asciidoctor-diagram/shaape/extension.rb
|
110
110
|
- lib/asciidoctor-diagram/util/binaryio.rb
|
111
111
|
- lib/asciidoctor-diagram/util/cli_generator.rb
|
112
|
-
- lib/asciidoctor-diagram/util/diagram.rb
|
113
112
|
- lib/asciidoctor-diagram/util/java.rb
|
114
113
|
- lib/asciidoctor-diagram/util/java_jruby.rb
|
115
114
|
- lib/asciidoctor-diagram/util/java_rjb.rb
|
@@ -140,9 +139,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
139
|
version: '0'
|
141
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
141
|
requirements:
|
143
|
-
- - '
|
142
|
+
- - '>'
|
144
143
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
144
|
+
version: 1.3.1
|
146
145
|
requirements: []
|
147
146
|
rubyforge_project:
|
148
147
|
rubygems_version: 2.0.14
|
@@ -1,46 +0,0 @@
|
|
1
|
-
require_relative '../util/java'
|
2
|
-
require_relative '../util/which'
|
3
|
-
|
4
|
-
module Asciidoctor
|
5
|
-
module Diagram
|
6
|
-
module PlantUmlGenerator
|
7
|
-
private
|
8
|
-
|
9
|
-
PLANTUML_JAR_PATH = File.expand_path File.join('../..', 'plantuml.jar'), File.dirname(__FILE__)
|
10
|
-
Java.classpath << PLANTUML_JAR_PATH
|
11
|
-
|
12
|
-
def self.plantuml(parent, code, tag, *flags)
|
13
|
-
unless @graphvizdot
|
14
|
-
@graphvizdot = parent.document.attributes['graphvizdot']
|
15
|
-
@graphvizdot = ::Asciidoctor::Diagram.which('dot') unless @graphvizdot && File.executable?(@graphvizdot)
|
16
|
-
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
|
17
|
-
end
|
18
|
-
|
19
|
-
Java.load
|
20
|
-
|
21
|
-
code = "@start#{tag}\n#{code}\n@end#{tag}" unless code.index "@start#{tag}"
|
22
|
-
|
23
|
-
flags += ['-charset', 'UTF-8', '-failonerror', '-graphvizdot', @graphvizdot]
|
24
|
-
|
25
|
-
config_file = parent.document.attributes['plantumlconfig']
|
26
|
-
if config_file
|
27
|
-
flags += ['-config', File.expand_path(config_file, parent.document.attributes['docdir'])]
|
28
|
-
end
|
29
|
-
|
30
|
-
option = Java.new_object( Java.net.sourceforge.plantuml.Option, '[Ljava.lang.String;', Java.array_to_java_array(flags, :string) )
|
31
|
-
source_reader = Java.new_object( Java.net.sourceforge.plantuml.SourceStringReader,
|
32
|
-
'Lnet.sourceforge.plantuml.preproc.Defines;Ljava.lang.String;Ljava.util.List;',
|
33
|
-
Java.new_object( Java.net.sourceforge.plantuml.preproc.Defines ),
|
34
|
-
code,
|
35
|
-
option.getConfig()
|
36
|
-
)
|
37
|
-
|
38
|
-
bos = Java.new_object( Java.java.io.ByteArrayOutputStream )
|
39
|
-
ps = Java.new_object( Java.java.io.PrintStream, 'Ljava.io.OutputStream;', bos )
|
40
|
-
source_reader.generateImage(ps, 0, option.getFileFormatOption())
|
41
|
-
ps.close
|
42
|
-
Java.string_from_java_bytes(bos.toByteArray)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,220 +0,0 @@
|
|
1
|
-
require 'asciidoctor/extensions'
|
2
|
-
require 'digest'
|
3
|
-
require 'json'
|
4
|
-
require 'fileutils'
|
5
|
-
require_relative 'java'
|
6
|
-
require_relative 'png'
|
7
|
-
require_relative 'svg'
|
8
|
-
|
9
|
-
module Asciidoctor
|
10
|
-
module Diagram
|
11
|
-
def self.define_processors(name, &init)
|
12
|
-
block = Class.new(Asciidoctor::Extensions::BlockProcessor) do
|
13
|
-
class << self
|
14
|
-
include FormatRegistry
|
15
|
-
end
|
16
|
-
include DiagramProcessor
|
17
|
-
|
18
|
-
option :pos_attrs, ['target', 'format']
|
19
|
-
option :contexts, [:listing, :literal, :open]
|
20
|
-
option :content_model, :simple
|
21
|
-
|
22
|
-
def process(parent, reader, attributes)
|
23
|
-
generate_block(parent, ReaderSource.new(reader), attributes)
|
24
|
-
end
|
25
|
-
|
26
|
-
self.instance_eval &init
|
27
|
-
end
|
28
|
-
|
29
|
-
block_macro = Class.new(Asciidoctor::Extensions::BlockMacroProcessor) do
|
30
|
-
class << self
|
31
|
-
include FormatRegistry
|
32
|
-
end
|
33
|
-
include DiagramProcessor
|
34
|
-
|
35
|
-
option :pos_attrs, ['target', 'format']
|
36
|
-
|
37
|
-
def process(parent, target, attributes)
|
38
|
-
source = FileSource.new(File.expand_path(target, parent.document.attributes['docdir']))
|
39
|
-
attributes['target'] ||= File.basename(target, File.extname(target))
|
40
|
-
|
41
|
-
generate_block(parent, source, attributes)
|
42
|
-
end
|
43
|
-
|
44
|
-
self.instance_eval &init
|
45
|
-
end
|
46
|
-
|
47
|
-
Asciidoctor::Diagram.const_set("#{name}Block", block)
|
48
|
-
Asciidoctor::Diagram.const_set("#{name}BlockMacro", block_macro)
|
49
|
-
end
|
50
|
-
|
51
|
-
module FormatRegistry
|
52
|
-
#
|
53
|
-
# Registers a supported format. The first registered format becomes the default format for the block processor.
|
54
|
-
#
|
55
|
-
# +format+ is a symbol with the format name
|
56
|
-
# +type+ is a symbol and should be either :image or :literal
|
57
|
-
# +block+ is a block that produces the diagrams from code. The block receives the parent asciidoc block and the diagram code as arguments
|
58
|
-
#
|
59
|
-
def register_format(format, type, &block)
|
60
|
-
unless @default_format
|
61
|
-
@default_format = format
|
62
|
-
end
|
63
|
-
|
64
|
-
formats[format] = {
|
65
|
-
:type => type,
|
66
|
-
:generator => block
|
67
|
-
}
|
68
|
-
end
|
69
|
-
|
70
|
-
def formats
|
71
|
-
@formats ||= {}
|
72
|
-
end
|
73
|
-
|
74
|
-
def default_format
|
75
|
-
@default_format
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
module DiagramProcessor
|
80
|
-
IMAGE_PARAMS = {
|
81
|
-
:svg => {
|
82
|
-
:encoding => Encoding::UTF_8,
|
83
|
-
:decoder => SVG
|
84
|
-
},
|
85
|
-
:png => {
|
86
|
-
:encoding => Encoding::ASCII_8BIT,
|
87
|
-
:decoder => PNG
|
88
|
-
}
|
89
|
-
}
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def generate_block(parent, source, attributes)
|
94
|
-
format = attributes.delete('format') || self.class.default_format
|
95
|
-
format = format.to_sym if format.respond_to?(:to_sym)
|
96
|
-
|
97
|
-
raise "Format undefined" unless format
|
98
|
-
|
99
|
-
generator_info = self.class.formats[format]
|
100
|
-
|
101
|
-
raise "#{self.class.name} does not support output format #{format}" unless generator_info
|
102
|
-
|
103
|
-
case generator_info[:type]
|
104
|
-
when :image
|
105
|
-
create_image_block(parent, source, attributes, format, generator_info)
|
106
|
-
when :literal
|
107
|
-
create_literal_block(parent, source, attributes, generator_info)
|
108
|
-
else
|
109
|
-
raise "Unsupported output format: #{format}"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def create_image_block(parent, source, attributes, format, generator_info)
|
114
|
-
target = attributes.delete('target')
|
115
|
-
|
116
|
-
image_name = "#{target || ('diag-' + source.checksum)}.#{format}"
|
117
|
-
image_dir = File.expand_path(parent.document.attributes['imagesdir'] || '', parent.document.attributes['outdir'] || parent.document.attributes['docdir'])
|
118
|
-
image_file = File.expand_path(image_name, image_dir)
|
119
|
-
metadata_file = File.expand_path("#{image_name}.cache", image_dir)
|
120
|
-
|
121
|
-
if File.exists? metadata_file
|
122
|
-
metadata = File.open(metadata_file, 'r') { |f| JSON.load f }
|
123
|
-
else
|
124
|
-
metadata = {}
|
125
|
-
end
|
126
|
-
|
127
|
-
if source.should_process?(image_file, metadata['checksum'])
|
128
|
-
params = IMAGE_PARAMS[format]
|
129
|
-
|
130
|
-
result = generator_info[:generator].call(source.code, parent)
|
131
|
-
|
132
|
-
result.force_encoding(params[:encoding])
|
133
|
-
|
134
|
-
metadata = {'checksum' => source.checksum}
|
135
|
-
metadata['width'], metadata['height'] = params[:decoder].get_image_size(result)
|
136
|
-
|
137
|
-
FileUtils.mkdir_p(image_dir) unless Dir.exists?(image_dir)
|
138
|
-
File.open(image_file, 'wb') { |f| f.write result }
|
139
|
-
File.open(metadata_file, 'w') { |f| JSON.dump(metadata, f) }
|
140
|
-
end
|
141
|
-
|
142
|
-
attributes['target'] = image_name
|
143
|
-
if /html/i =~ parent.document.attributes['backend']
|
144
|
-
attributes['width'] ||= metadata['width'] if metadata['width']
|
145
|
-
attributes['height'] ||= metadata['height'] if metadata['height']
|
146
|
-
end
|
147
|
-
attributes['alt'] ||= if title_text = attributes['title']
|
148
|
-
title_text
|
149
|
-
elsif target
|
150
|
-
(File.basename target, (File.extname target) || '').tr '_-', ' '
|
151
|
-
else
|
152
|
-
'Diagram'
|
153
|
-
end
|
154
|
-
|
155
|
-
Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => attributes
|
156
|
-
end
|
157
|
-
|
158
|
-
def create_literal_block(parent, source, attributes, generator_info)
|
159
|
-
attributes.delete('target')
|
160
|
-
|
161
|
-
result = generator_info[:generator].call(source.code, parent)
|
162
|
-
|
163
|
-
result.force_encoding(Encoding::UTF_8)
|
164
|
-
Asciidoctor::Block.new parent, :literal, :code => result, :attributes => attributes
|
165
|
-
end
|
166
|
-
|
167
|
-
def code_checksum(code)
|
168
|
-
md5 = Digest::MD5.new
|
169
|
-
md5 << code
|
170
|
-
md5.hexdigest
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
class Source
|
175
|
-
def checksum
|
176
|
-
@checksum ||= compute_checksum(code)
|
177
|
-
end
|
178
|
-
|
179
|
-
def should_process?(image_file, old_checksum)
|
180
|
-
!File.exists?(image_file) || (newer_than?(image_file) && old_checksum != checksum)
|
181
|
-
end
|
182
|
-
|
183
|
-
private
|
184
|
-
|
185
|
-
def compute_checksum(code)
|
186
|
-
md5 = Digest::MD5.new
|
187
|
-
md5 << code
|
188
|
-
md5.hexdigest
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
class ReaderSource < Source
|
193
|
-
def initialize(reader)
|
194
|
-
@reader = reader
|
195
|
-
end
|
196
|
-
|
197
|
-
def newer_than?(image_file)
|
198
|
-
true
|
199
|
-
end
|
200
|
-
|
201
|
-
def code
|
202
|
-
@code ||= @reader.lines.join("\n")
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
class FileSource < Source
|
207
|
-
def initialize(file_name)
|
208
|
-
@file_name = file_name
|
209
|
-
end
|
210
|
-
|
211
|
-
def newer_than?(image_file)
|
212
|
-
File.mtime(@file_name) > File.mtime(image_file)
|
213
|
-
end
|
214
|
-
|
215
|
-
def code
|
216
|
-
@code ||= File.read(@file_name)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|