network_drawer 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +130 -0
- data/Rakefile +18 -0
- data/bin/network_drawer +5 -0
- data/examples/simple.dot +53 -0
- data/examples/simple.json +41 -0
- data/examples/simple.png +0 -0
- data/examples/simple.svg +166 -0
- data/examples/simple_style.json +7 -0
- data/lib/network_drawer.rb +5 -0
- data/lib/network_drawer/cli.rb +21 -0
- data/lib/network_drawer/diagram.rb +144 -0
- data/lib/network_drawer/source.rb +10 -0
- data/lib/network_drawer/style.rb +14 -0
- data/lib/network_drawer/version.rb +3 -0
- data/network_drawer.gemspec +27 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 62b2a058777853b011aa999b5bbfaff1f5aea5e9
|
4
|
+
data.tar.gz: ca9f5bdc9aaa639146504074fa6b2d769ecf9656
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e6368873678680cf9cb5c72f6c666e7f453fe992ce3c8659b9b7fab18eedff3157f7e01ac41c8cbe5d41dca0338e241f101217efd42e2a7258c503013a6eb600
|
7
|
+
data.tar.gz: 8a6ede793ae512557266d3e48591e77fe55609962c686dd71bff6c07e2cee18eff3a664491225ca47ab952d6bb4bbfe9d4b3cd1246dc598c28b02eecf2a4036d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Hiroshi Ota
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# NetworkDrawer
|
2
|
+
|
3
|
+
A network diagram drawer with json.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Ensure you can use [Graphviz](http://www.graphviz.org/), before installing network_drawer,
|
8
|
+
|
9
|
+
You can install Graphviz as follow
|
10
|
+
```
|
11
|
+
# Mac OS X with brew
|
12
|
+
brew install graphviz
|
13
|
+
|
14
|
+
# CentOS
|
15
|
+
yum -y install graphviz
|
16
|
+
|
17
|
+
# Ubuntu
|
18
|
+
apt-get install graphviz
|
19
|
+
```
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'network_drawer'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install network_drawer
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
You can draw a network diagram:
|
38
|
+
|
39
|
+
$ bundle exec network_drawer draw examples/simple.json
|
40
|
+
|
41
|
+
You can draw a network diagram with specified style and png format:
|
42
|
+
|
43
|
+
$ bundle exec network_drawer draw examples/simple.json -s examples/simple_style.json
|
44
|
+
|
45
|
+
You can draw a network diagram with specified style and png format:
|
46
|
+
|
47
|
+
$ bundle exec network_drawer draw examples/simple.json -f png
|
48
|
+
|
49
|
+
Then You will get a network diagram.
|
50
|
+
|
51
|
+
[sample diagram(SVG)](examples/simple.svg)
|
52
|
+
|
53
|
+

|
54
|
+
|
55
|
+
## How to describe
|
56
|
+
|
57
|
+
You can describe diagram with JSON.
|
58
|
+
|
59
|
+
### Network diagram
|
60
|
+
|
61
|
+
You can describe diagram as follows:
|
62
|
+
|
63
|
+
|
64
|
+
```json
|
65
|
+
{
|
66
|
+
"layers": {
|
67
|
+
"Web": {
|
68
|
+
"nodes": [
|
69
|
+
{
|
70
|
+
"WebLB": {
|
71
|
+
"ports": ["80/tcp", "443/tcp"],
|
72
|
+
"type": "LB",
|
73
|
+
"url": "https://github.com/otahi/network_drawer/"
|
74
|
+
}
|
75
|
+
},
|
76
|
+
{ "Web001": { "ports": ["80/tcp"] } },
|
77
|
+
{ "Web002": { "ports": ["80/tcp"] } }
|
78
|
+
]
|
79
|
+
},
|
80
|
+
"App": {
|
81
|
+
"nodes": [
|
82
|
+
{ "AppLB": { "ports": ["80/tcp", "25/tcp"], "type": "LB" } },
|
83
|
+
{ "App001": { "ports": ["80/tcp", "25/tcp"] } },
|
84
|
+
{ "App002": { "ports": ["80/tcp", "25/tcp"] } }
|
85
|
+
]
|
86
|
+
}
|
87
|
+
},
|
88
|
+
"nodes": [
|
89
|
+
{ "Browser": {}, "type": "Client" },
|
90
|
+
{ "Mail Server": {}, "type": "Client" }
|
91
|
+
],
|
92
|
+
"connections": [
|
93
|
+
{ "from": "Browser", "to": "WebLB:80/tcp", "type": "HTTP" },
|
94
|
+
{ "from": "Browser", "to": "WebLB:443/tcp" },
|
95
|
+
{ "from": "Mail Server", "to": "AppLB:25/tcp" , "type": "SMTP" },
|
96
|
+
{ "from": "WebLB", "to": "Web001:80/tcp", "type": "HTTP" },
|
97
|
+
{ "from": "WebLB", "to": "Web002:80/tcp", "type": "HTTP" },
|
98
|
+
{ "from": "Web001", "to": "AppLB:80/tcp", "type": "HTTP" },
|
99
|
+
{ "from": "Web002", "to": "AppLB:80/tcp", "type": "HTTP" },
|
100
|
+
{ "from": "AppLB", "to": "App001:25/tcp", "type": "SMTP" },
|
101
|
+
{ "from": "AppLB", "to": "App001:80/tcp", "type": "HTTP" },
|
102
|
+
{ "from": "AppLB", "to": "App002:25/tcp", "type": "SMTP" },
|
103
|
+
{ "from": "AppLB", "to": "App002:80/tcp", "type": "HTTP" }
|
104
|
+
]
|
105
|
+
}
|
106
|
+
|
107
|
+
```
|
108
|
+
|
109
|
+
### Style
|
110
|
+
|
111
|
+
You can specify your style for nodes or edges with [Graphviz attributes](http://www.graphviz.org/content/attrs).
|
112
|
+
|
113
|
+
```json
|
114
|
+
{
|
115
|
+
"types": {
|
116
|
+
"LB": { "style": "rounded,filled,dotted", "fillcolor": "azure" },
|
117
|
+
"HTTP": { "color": "blue" },
|
118
|
+
"SMTP": { "color": "green" }
|
119
|
+
}
|
120
|
+
}
|
121
|
+
```
|
122
|
+
|
123
|
+
|
124
|
+
## Contributing
|
125
|
+
|
126
|
+
1. Fork it ( https://github.com/otahi/network_drawer/fork )
|
127
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
128
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
129
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
130
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
task default: :update
|
4
|
+
|
5
|
+
task :update do
|
6
|
+
env = {
|
7
|
+
'BUNDLE_GEMFILE' => nil,
|
8
|
+
'GEM_HOME' => nil
|
9
|
+
}
|
10
|
+
|
11
|
+
%w(png svg).each do |f|
|
12
|
+
command = 'bundle exec bin/network_drawer '
|
13
|
+
command << 'draw examples/simple.json '
|
14
|
+
command << "-s examples/simple_style.json -f #{f}"
|
15
|
+
|
16
|
+
system(env, command)
|
17
|
+
end
|
18
|
+
end
|
data/bin/network_drawer
ADDED
data/examples/simple.dot
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
digraph simple {
|
2
|
+
subgraph cluster1 {
|
3
|
+
label="Web";
|
4
|
+
fontname="Helvetica";
|
5
|
+
3[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td border='1' port="p80tcp">80/tcp</td><td border='1' port="p443tcp">443/tcp</td></tr><tr border='1'><td border='1' colspan="2">WebLB</td></tr></table>>,tooltip="WebLB",URL="https://github.com/otahi/network_drawer/",style="rounded,filled,dotted",fillcolor="azure"];
|
6
|
+
}
|
7
|
+
subgraph cluster1 {
|
8
|
+
label="Web";
|
9
|
+
fontname="Helvetica";
|
10
|
+
4[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td border='1' port="p80tcp">80/tcp</td></tr><tr border='1'><td border='1' colspan="1">Web001</td></tr></table>>,tooltip="Web001",URL=""];
|
11
|
+
}
|
12
|
+
subgraph cluster1 {
|
13
|
+
label="Web";
|
14
|
+
fontname="Helvetica";
|
15
|
+
5[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td border='1' port="p80tcp">80/tcp</td></tr><tr border='1'><td border='1' colspan="1">Web002</td></tr></table>>,tooltip="Web002",URL=""];
|
16
|
+
}
|
17
|
+
subgraph cluster3 {
|
18
|
+
label="App";
|
19
|
+
fontname="Helvetica";
|
20
|
+
6[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td border='1' port="p80tcp">80/tcp</td><td border='1' port="p25tcp">25/tcp</td></tr><tr border='1'><td border='1' colspan="2">AppLB</td></tr></table>>,tooltip="AppLB",URL="",style="rounded,filled,dotted",fillcolor="azure"];
|
21
|
+
}
|
22
|
+
subgraph cluster3 {
|
23
|
+
label="App";
|
24
|
+
fontname="Helvetica";
|
25
|
+
7[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td border='1' port="p80tcp">80/tcp</td><td border='1' port="p25tcp">25/tcp</td></tr><tr border='1'><td border='1' colspan="2">App001</td></tr></table>>,tooltip="App001",URL=""];
|
26
|
+
}
|
27
|
+
subgraph cluster3 {
|
28
|
+
label="App";
|
29
|
+
fontname="Helvetica";
|
30
|
+
8[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td border='1' port="p80tcp">80/tcp</td><td border='1' port="p25tcp">25/tcp</td></tr><tr border='1'><td border='1' colspan="2">App002</td></tr></table>>,tooltip="App002",URL=""];
|
31
|
+
}
|
32
|
+
rankdir="TB";
|
33
|
+
fontname="Helvetica";
|
34
|
+
1[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td>Browser</td></tr></table>>,tooltip="Browser",URL=""];
|
35
|
+
2[fontname="Helvetica",shape="box",label=<<table border='0'><tr border='1'><td>Mail Server</td></tr></table>>,tooltip="Mail Server",URL=""];
|
36
|
+
3;
|
37
|
+
6;
|
38
|
+
4;
|
39
|
+
5;
|
40
|
+
7;
|
41
|
+
8;
|
42
|
+
1 -> 3:p80tcp[color="blue"];
|
43
|
+
1 -> 3:p443tcp;
|
44
|
+
2 -> 6:p25tcp[color="green"];
|
45
|
+
3 -> 4:p80tcp[color="blue"];
|
46
|
+
3 -> 5:p80tcp[color="blue"];
|
47
|
+
4 -> 6:p80tcp[color="blue"];
|
48
|
+
5 -> 6:p80tcp[color="blue"];
|
49
|
+
6 -> 7:p25tcp[color="green"];
|
50
|
+
6 -> 7:p80tcp[color="blue"];
|
51
|
+
6 -> 8:p25tcp[color="green"];
|
52
|
+
6 -> 8:p80tcp[color="blue"];
|
53
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"layers": {
|
3
|
+
"Web": {
|
4
|
+
"nodes": [
|
5
|
+
{
|
6
|
+
"WebLB": {
|
7
|
+
"ports": ["80/tcp", "443/tcp"],
|
8
|
+
"type": "LB",
|
9
|
+
"url": "https://github.com/otahi/network_drawer/"
|
10
|
+
}
|
11
|
+
},
|
12
|
+
{ "Web001": { "ports": ["80/tcp"] } },
|
13
|
+
{ "Web002": { "ports": ["80/tcp"] } }
|
14
|
+
]
|
15
|
+
},
|
16
|
+
"App": {
|
17
|
+
"nodes": [
|
18
|
+
{ "AppLB": { "ports": ["80/tcp", "25/tcp"], "type": "LB" } },
|
19
|
+
{ "App001": { "ports": ["80/tcp", "25/tcp"] } },
|
20
|
+
{ "App002": { "ports": ["80/tcp", "25/tcp"] } }
|
21
|
+
]
|
22
|
+
}
|
23
|
+
},
|
24
|
+
"nodes": [
|
25
|
+
{ "Browser": {}, "type": "Client" },
|
26
|
+
{ "Mail Server": {}, "type": "Client" }
|
27
|
+
],
|
28
|
+
"connections": [
|
29
|
+
{ "from": "Browser", "to": "WebLB:80/tcp", "type": "HTTP" },
|
30
|
+
{ "from": "Browser", "to": "WebLB:443/tcp" },
|
31
|
+
{ "from": "Mail Server", "to": "AppLB:25/tcp" , "type": "SMTP" },
|
32
|
+
{ "from": "WebLB", "to": "Web001:80/tcp", "type": "HTTP" },
|
33
|
+
{ "from": "WebLB", "to": "Web002:80/tcp", "type": "HTTP" },
|
34
|
+
{ "from": "Web001", "to": "AppLB:80/tcp", "type": "HTTP" },
|
35
|
+
{ "from": "Web002", "to": "AppLB:80/tcp", "type": "HTTP" },
|
36
|
+
{ "from": "AppLB", "to": "App001:25/tcp", "type": "SMTP" },
|
37
|
+
{ "from": "AppLB", "to": "App001:80/tcp", "type": "HTTP" },
|
38
|
+
{ "from": "AppLB", "to": "App002:25/tcp", "type": "SMTP" },
|
39
|
+
{ "from": "AppLB", "to": "App002:80/tcp", "type": "HTTP" }
|
40
|
+
]
|
41
|
+
}
|
data/examples/simple.png
ADDED
Binary file
|
data/examples/simple.svg
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
3
|
+
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
4
|
+
<!-- Generated by graphviz version 2.34.0 (20140129.0153)
|
5
|
+
-->
|
6
|
+
<!-- Title: simple Pages: 1 -->
|
7
|
+
<svg width="336pt" height="460pt"
|
8
|
+
viewBox="0.00 0.00 336.00 460.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
9
|
+
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 456)">
|
10
|
+
<title>simple</title>
|
11
|
+
<polygon fill="white" stroke="white" points="-4,4 -4,-456 332,-456 332,4 -4,4"/>
|
12
|
+
<g id="clust1" class="cluster"><title>cluster1</title>
|
13
|
+
<polygon fill="none" stroke="black" points="105,-212 105,-408 295,-408 295,-212 105,-212"/>
|
14
|
+
<text text-anchor="middle" x="200" y="-390" font-family="Helvetica,sans-Serif" font-size="14.00">Web</text>
|
15
|
+
</g>
|
16
|
+
<g id="clust2" class="cluster"><title>cluster3</title>
|
17
|
+
<polygon fill="none" stroke="black" points="63,-8 63,-204 320,-204 320,-8 63,-8"/>
|
18
|
+
<text text-anchor="middle" x="191.5" y="-186" font-family="Helvetica,sans-Serif" font-size="14.00">App</text>
|
19
|
+
</g>
|
20
|
+
<!-- 3 -->
|
21
|
+
<g id="node1" class="node"><title>3</title>
|
22
|
+
<g id="a_node1"><a xlink:href="https://github.com/otahi/network_drawer/" xlink:title="WebLB">
|
23
|
+
<path fill="azure" stroke="black" stroke-dasharray="1,5" d="M247,-375.5C247,-375.5 153,-375.5 153,-375.5 147,-375.5 141,-369.5 141,-363.5 141,-363.5 141,-328.5 141,-328.5 141,-322.5 147,-316.5 153,-316.5 153,-316.5 247,-316.5 247,-316.5 253,-316.5 259,-322.5 259,-328.5 259,-328.5 259,-363.5 259,-363.5 259,-369.5 253,-375.5 247,-375.5"/>
|
24
|
+
<polygon fill="none" stroke="black" points="151,-346 151,-369 196,-369 196,-346 151,-346"/>
|
25
|
+
<text text-anchor="start" x="154.431" y="-351.9" font-family="Helvetica,sans-Serif" font-size="14.00">80/tcp</text>
|
26
|
+
<polygon fill="none" stroke="black" points="198,-346 198,-369 249,-369 249,-346 198,-346"/>
|
27
|
+
<text text-anchor="start" x="200.538" y="-351.9" font-family="Helvetica,sans-Serif" font-size="14.00">443/tcp</text>
|
28
|
+
<polygon fill="none" stroke="black" points="151,-322 151,-344 249,-344 249,-322 151,-322"/>
|
29
|
+
<text text-anchor="start" x="177.168" y="-327" font-family="Helvetica,sans-Serif" font-size="14.00">WebLB</text>
|
30
|
+
</a>
|
31
|
+
</g>
|
32
|
+
</g>
|
33
|
+
<!-- 4 -->
|
34
|
+
<g id="node2" class="node"><title>4</title>
|
35
|
+
<g id="a_node2"><a xlink:title="Web001">
|
36
|
+
<polygon fill="none" stroke="black" points="191,-279.5 113,-279.5 113,-220.5 191,-220.5 191,-279.5"/>
|
37
|
+
<polygon fill="none" stroke="black" points="123,-250 123,-273 181,-273 181,-250 123,-250"/>
|
38
|
+
<text text-anchor="start" x="132.931" y="-255.9" font-family="Helvetica,sans-Serif" font-size="14.00">80/tcp</text>
|
39
|
+
<polygon fill="none" stroke="black" points="123,-226 123,-248 181,-248 181,-226 123,-226"/>
|
40
|
+
<text text-anchor="start" x="126.051" y="-231" font-family="Helvetica,sans-Serif" font-size="14.00">Web001</text>
|
41
|
+
</a>
|
42
|
+
</g>
|
43
|
+
</g>
|
44
|
+
<!-- 3->4 -->
|
45
|
+
<g id="edge4" class="edge"><title>3->4:p80tcp</title>
|
46
|
+
<path fill="none" stroke="blue" d="M199.141,-316.436C198.045,-299.18 195.572,-278.608 190.026,-268.401"/>
|
47
|
+
<polygon fill="blue" stroke="blue" points="192,-265.499 182,-262 187.636,-270.972 192,-265.499"/>
|
48
|
+
</g>
|
49
|
+
<!-- 5 -->
|
50
|
+
<g id="node3" class="node"><title>5</title>
|
51
|
+
<g id="a_node3"><a xlink:title="Web002">
|
52
|
+
<polygon fill="none" stroke="black" points="287,-279.5 209,-279.5 209,-220.5 287,-220.5 287,-279.5"/>
|
53
|
+
<polygon fill="none" stroke="black" points="219,-250 219,-273 277,-273 277,-250 219,-250"/>
|
54
|
+
<text text-anchor="start" x="228.931" y="-255.9" font-family="Helvetica,sans-Serif" font-size="14.00">80/tcp</text>
|
55
|
+
<polygon fill="none" stroke="black" points="219,-226 219,-248 277,-248 277,-226 219,-226"/>
|
56
|
+
<text text-anchor="start" x="222.051" y="-231" font-family="Helvetica,sans-Serif" font-size="14.00">Web002</text>
|
57
|
+
</a>
|
58
|
+
</g>
|
59
|
+
</g>
|
60
|
+
<!-- 3->5 -->
|
61
|
+
<g id="edge5" class="edge"><title>3->5:p80tcp</title>
|
62
|
+
<path fill="none" stroke="blue" d="M200.859,-316.436C201.955,-299.18 204.428,-278.608 209.974,-268.401"/>
|
63
|
+
<polygon fill="blue" stroke="blue" points="212.364,-270.972 218,-262 208,-265.499 212.364,-270.972"/>
|
64
|
+
</g>
|
65
|
+
<!-- 6 -->
|
66
|
+
<g id="node4" class="node"><title>6</title>
|
67
|
+
<g id="a_node4"><a xlink:title="AppLB">
|
68
|
+
<path fill="azure" stroke="black" stroke-dasharray="1,5" d="M218.25,-171.5C218.25,-171.5 131.75,-171.5 131.75,-171.5 125.75,-171.5 119.75,-165.5 119.75,-159.5 119.75,-159.5 119.75,-124.5 119.75,-124.5 119.75,-118.5 125.75,-112.5 131.75,-112.5 131.75,-112.5 218.25,-112.5 218.25,-112.5 224.25,-112.5 230.25,-118.5 230.25,-124.5 230.25,-124.5 230.25,-159.5 230.25,-159.5 230.25,-165.5 224.25,-171.5 218.25,-171.5"/>
|
69
|
+
<polygon fill="none" stroke="black" points="130,-142 130,-165 175,-165 175,-142 130,-142"/>
|
70
|
+
<text text-anchor="start" x="133.431" y="-147.9" font-family="Helvetica,sans-Serif" font-size="14.00">80/tcp</text>
|
71
|
+
<polygon fill="none" stroke="black" points="177,-142 177,-165 221,-165 221,-142 177,-142"/>
|
72
|
+
<text text-anchor="start" x="179.931" y="-147.9" font-family="Helvetica,sans-Serif" font-size="14.00">25/tcp</text>
|
73
|
+
<polygon fill="none" stroke="black" points="130,-118 130,-140 221,-140 221,-118 130,-118"/>
|
74
|
+
<text text-anchor="start" x="154.483" y="-123" font-family="Helvetica,sans-Serif" font-size="14.00">AppLB</text>
|
75
|
+
</a>
|
76
|
+
</g>
|
77
|
+
</g>
|
78
|
+
<!-- 4->6 -->
|
79
|
+
<g id="edge6" class="edge"><title>4->6:p80tcp</title>
|
80
|
+
<path fill="none" stroke="blue" d="M152,-220.191C152,-207.217 152,-191.442 152,-176.321"/>
|
81
|
+
<polygon fill="blue" stroke="blue" points="155.5,-176 152,-166 148.5,-176 155.5,-176"/>
|
82
|
+
</g>
|
83
|
+
<!-- 5->6 -->
|
84
|
+
<g id="edge7" class="edge"><title>5->6:p80tcp</title>
|
85
|
+
<path fill="none" stroke="blue" d="M212.526,-220.441C192.443,-204.036 169.215,-184.601 158.292,-173.792"/>
|
86
|
+
<polygon fill="blue" stroke="blue" points="161.006,-171.581 152,-166 155.559,-175.979 161.006,-171.581"/>
|
87
|
+
</g>
|
88
|
+
<!-- 7 -->
|
89
|
+
<g id="node5" class="node"><title>7</title>
|
90
|
+
<g id="a_node5"><a xlink:title="App001">
|
91
|
+
<polygon fill="none" stroke="black" points="182.25,-75.5 71.75,-75.5 71.75,-16.5 182.25,-16.5 182.25,-75.5"/>
|
92
|
+
<polygon fill="none" stroke="black" points="82,-46 82,-69 127,-69 127,-46 82,-46"/>
|
93
|
+
<text text-anchor="start" x="85.4312" y="-51.9" font-family="Helvetica,sans-Serif" font-size="14.00">80/tcp</text>
|
94
|
+
<polygon fill="none" stroke="black" points="129,-46 129,-69 173,-69 173,-46 129,-46"/>
|
95
|
+
<text text-anchor="start" x="131.931" y="-51.9" font-family="Helvetica,sans-Serif" font-size="14.00">25/tcp</text>
|
96
|
+
<polygon fill="none" stroke="black" points="82,-22 82,-44 173,-44 173,-22 82,-22"/>
|
97
|
+
<text text-anchor="start" x="103.366" y="-27" font-family="Helvetica,sans-Serif" font-size="14.00">App001</text>
|
98
|
+
</a>
|
99
|
+
</g>
|
100
|
+
</g>
|
101
|
+
<!-- 6->7 -->
|
102
|
+
<g id="edge8" class="edge"><title>6->7:p25tcp</title>
|
103
|
+
<path fill="none" stroke="green" d="M160.623,-112.37C156.71,-102.677 153.148,-91.4558 151.693,-80.2467"/>
|
104
|
+
<polygon fill="green" stroke="green" points="155.167,-79.7409 151,-70 148.183,-80.2135 155.167,-79.7409"/>
|
105
|
+
</g>
|
106
|
+
<!-- 6->7 -->
|
107
|
+
<g id="edge9" class="edge"><title>6->7:p80tcp</title>
|
108
|
+
<path fill="none" stroke="blue" d="M126.856,-112.424C116.765,-103.478 108.243,-92.5876 105.197,-79.976"/>
|
109
|
+
<polygon fill="blue" stroke="blue" points="108.667,-79.5118 104,-70 101.716,-80.3458 108.667,-79.5118"/>
|
110
|
+
</g>
|
111
|
+
<!-- 8 -->
|
112
|
+
<g id="node6" class="node"><title>8</title>
|
113
|
+
<g id="a_node6"><a xlink:title="App002">
|
114
|
+
<polygon fill="none" stroke="black" points="311.25,-75.5 200.75,-75.5 200.75,-16.5 311.25,-16.5 311.25,-75.5"/>
|
115
|
+
<polygon fill="none" stroke="black" points="211,-46 211,-69 256,-69 256,-46 211,-46"/>
|
116
|
+
<text text-anchor="start" x="214.431" y="-51.9" font-family="Helvetica,sans-Serif" font-size="14.00">80/tcp</text>
|
117
|
+
<polygon fill="none" stroke="black" points="258,-46 258,-69 302,-69 302,-46 258,-46"/>
|
118
|
+
<text text-anchor="start" x="260.931" y="-51.9" font-family="Helvetica,sans-Serif" font-size="14.00">25/tcp</text>
|
119
|
+
<polygon fill="none" stroke="black" points="211,-22 211,-44 302,-44 302,-22 211,-22"/>
|
120
|
+
<text text-anchor="start" x="232.366" y="-27" font-family="Helvetica,sans-Serif" font-size="14.00">App002</text>
|
121
|
+
</a>
|
122
|
+
</g>
|
123
|
+
</g>
|
124
|
+
<!-- 6->8 -->
|
125
|
+
<g id="edge10" class="edge"><title>6->8:p25tcp</title>
|
126
|
+
<path fill="none" stroke="green" d="M230.267,-125.47C252.302,-116.05 273.686,-101.572 278.832,-80.0328"/>
|
127
|
+
<polygon fill="green" stroke="green" points="282.32,-80.3376 280,-70 275.367,-79.5283 282.32,-80.3376"/>
|
128
|
+
</g>
|
129
|
+
<!-- 6->8 -->
|
130
|
+
<g id="edge11" class="edge"><title>6->8:p80tcp</title>
|
131
|
+
<path fill="none" stroke="blue" d="M181.385,-112.436C186.051,-94.4294 192.996,-72.8124 201.291,-63.1294"/>
|
132
|
+
<polygon fill="blue" stroke="blue" points="203.16,-66.0907 210,-58 199.607,-60.059 203.16,-66.0907"/>
|
133
|
+
</g>
|
134
|
+
<!-- 1 -->
|
135
|
+
<g id="node7" class="node"><title>1</title>
|
136
|
+
<g id="a_node7"><a xlink:title="Browser">
|
137
|
+
<polygon fill="none" stroke="black" points="237,-452 161,-452 161,-416 237,-416 237,-452"/>
|
138
|
+
<text text-anchor="start" x="173.328" y="-427.9" font-family="Helvetica,sans-Serif" font-size="14.00">Browser</text>
|
139
|
+
</a>
|
140
|
+
</g>
|
141
|
+
</g>
|
142
|
+
<!-- 1->3 -->
|
143
|
+
<g id="edge1" class="edge"><title>1->3:p80tcp</title>
|
144
|
+
<path fill="none" stroke="blue" d="M187.672,-415.857C182.088,-406.095 176.087,-393.216 173.879,-380.063"/>
|
145
|
+
<polygon fill="blue" stroke="blue" points="177.357,-379.657 173,-370 170.384,-380.267 177.357,-379.657"/>
|
146
|
+
</g>
|
147
|
+
<!-- 1->3 -->
|
148
|
+
<g id="edge2" class="edge"><title>1->3:p443tcp</title>
|
149
|
+
<path fill="none" stroke="black" d="M209.893,-415.8C215.261,-406.018 221.032,-393.129 223.155,-380.02"/>
|
150
|
+
<polygon fill="black" stroke="black" points="226.647,-380.259 224,-370 219.672,-379.67 226.647,-380.259"/>
|
151
|
+
</g>
|
152
|
+
<!-- 2 -->
|
153
|
+
<g id="node8" class="node"><title>2</title>
|
154
|
+
<g id="a_node8"><a xlink:title="Mail Server">
|
155
|
+
<polygon fill="none" stroke="black" points="94.25,-268 -0.25,-268 -0.25,-232 94.25,-232 94.25,-268"/>
|
156
|
+
<text text-anchor="start" x="12.1035" y="-243.9" font-family="Helvetica,sans-Serif" font-size="14.00">Mail Server</text>
|
157
|
+
</a>
|
158
|
+
</g>
|
159
|
+
</g>
|
160
|
+
<!-- 2->6 -->
|
161
|
+
<g id="edge3" class="edge"><title>2->6:p25tcp</title>
|
162
|
+
<path fill="none" stroke="green" d="M70.1237,-231.94C79.4894,-225.355 90.5446,-217.983 101,-212 139.498,-189.969 190.301,-210.355 198.004,-175.979"/>
|
163
|
+
<polygon fill="green" stroke="green" points="201.489,-176.298 199,-166 194.524,-175.603 201.489,-176.298"/>
|
164
|
+
</g>
|
165
|
+
</g>
|
166
|
+
</svg>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'network_drawer'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module NetworkDrawer
|
5
|
+
# Cli for NetworkDrawer
|
6
|
+
class Cli < Thor
|
7
|
+
desc 'draw SOURCE', 'draw network diagram with SOURCE file'
|
8
|
+
option(:style, aliases: :s,
|
9
|
+
banner: 'STYLE_FILE_IN_JSON')
|
10
|
+
option(:format, aliases: :f,
|
11
|
+
banner: 'OUTPUT_FILE_FORMAT such as svg, png',
|
12
|
+
default: :svg)
|
13
|
+
def draw(source_file)
|
14
|
+
src = Source.read(source_file)
|
15
|
+
op = { style: Style.read(options[:style]) }
|
16
|
+
op.merge!(format: options[:format])
|
17
|
+
dest_file = source_file.gsub(File.extname(source_file), '')
|
18
|
+
Diagram.draw(src, dest_file, op)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'gviz'
|
2
|
+
|
3
|
+
module NetworkDrawer
|
4
|
+
# Replesent of source file
|
5
|
+
class Diagram
|
6
|
+
TOP_LAYER = :networkdrawertop
|
7
|
+
DEFAULT_OPTIONS = {}
|
8
|
+
DEFAULT_STYLE = { fontname: 'Helvetica' }
|
9
|
+
DEFAULT_NODE_STYLE = { fontname: 'Helvetica', shape: 'box' }
|
10
|
+
DEFAULT_LINE_STYLE = {}
|
11
|
+
|
12
|
+
def self.draw(source, dest_file, options = {})
|
13
|
+
dia = new(source, dest_file, options)
|
14
|
+
dia.draw
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(source, dest_file, options = {})
|
18
|
+
@source = source
|
19
|
+
@dest_file = dest_file
|
20
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
21
|
+
@title = @options[:title] ? @options[:title] :
|
22
|
+
File.basename(@dest_file, '.*')
|
23
|
+
@nodes = {}
|
24
|
+
@layers = {}
|
25
|
+
@style = options[:style]
|
26
|
+
@gv = Gviz.new(@title)
|
27
|
+
end
|
28
|
+
|
29
|
+
def draw
|
30
|
+
@gv.global(rankdir: 'TB')
|
31
|
+
@gv.global(DEFAULT_STYLE)
|
32
|
+
create_nodes
|
33
|
+
create_connections
|
34
|
+
|
35
|
+
@gv.save @dest_file, @options[:format]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def create_nodes
|
41
|
+
built_nodes = build_nodes(TOP_LAYER => @source)
|
42
|
+
built_nodes[TOP_LAYER].each_value do |t|
|
43
|
+
@gv.global DEFAULT_STYLE
|
44
|
+
node_style = { label: t[:label], tooltip: t[:name], URL: t[:url] }
|
45
|
+
node_style =
|
46
|
+
override_style(:node, node_style, t[:type])
|
47
|
+
@gv.node(t[:id], node_style)
|
48
|
+
end
|
49
|
+
|
50
|
+
built_nodes[:layers].each_pair do |n, l|
|
51
|
+
layer_name = n
|
52
|
+
id = "#{@layers.size + 1}".to_sym
|
53
|
+
@layers.merge!(layer_name => id)
|
54
|
+
l.each_value do |v|
|
55
|
+
node_style = { label: v[:label], tooltip: v[:name], URL: v[:url] }
|
56
|
+
node_style =
|
57
|
+
override_style(:node, node_style, v[:type])
|
58
|
+
@gv.subgraph "cluster#{id}" do
|
59
|
+
global label: layer_name
|
60
|
+
global DEFAULT_STYLE
|
61
|
+
node(v[:id], node_style)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_nodes(layer)
|
68
|
+
layer_name = layer.keys.first
|
69
|
+
nodes = layer[layer_name][:nodes]
|
70
|
+
built_nodes = {}
|
71
|
+
nodes.each_with_index do |s, i|
|
72
|
+
id = "#{@nodes.size + 1}".to_sym
|
73
|
+
name = s.keys.first
|
74
|
+
ports = s[name][:ports] ? s[name][:ports] : []
|
75
|
+
url = s[name][:url] ? s[name][:url] : nil
|
76
|
+
label = build_node_label(name: name, ports: ports)
|
77
|
+
type = s[name][:type] ? s[name][:type].to_sym : nil
|
78
|
+
node = {
|
79
|
+
id: id, name: name, label: label,
|
80
|
+
ports: ports, type: type, url: url
|
81
|
+
}
|
82
|
+
built_nodes.merge!(name => node)
|
83
|
+
@nodes.merge!(name => node)
|
84
|
+
end if nodes
|
85
|
+
|
86
|
+
layers = layer[layer_name][:layers]
|
87
|
+
built_layers = {}
|
88
|
+
layers.each_pair do |k, v|
|
89
|
+
built_layers.merge!(build_nodes(k => v))
|
90
|
+
end if layers
|
91
|
+
built_nodes = { layer_name => built_nodes, layers: built_layers }
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_node_label(opt = {})
|
95
|
+
if opt[:ports].empty?
|
96
|
+
label = "<tr border='1'><td>#{opt[:name]}</td></tr>"
|
97
|
+
else
|
98
|
+
label = "<tr border='1'>"
|
99
|
+
opt[:ports].each_with_index do |p, j|
|
100
|
+
label << "<td border='1' port=\"p#{p.gsub('/', '')}\">#{p}</td>"
|
101
|
+
end
|
102
|
+
label << '</tr>'
|
103
|
+
label << "<tr border='1'><td border='1' colspan=\"#{opt[:ports].size}\">#{opt[:name]}</td></tr>"
|
104
|
+
end
|
105
|
+
"<table border='0'>#{label}</table>"
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_connections
|
109
|
+
return if @source[:connections].nil?
|
110
|
+
seq = 0
|
111
|
+
@source[:connections].each do |c|
|
112
|
+
from_name, from_port = c[:from].to_s.split(':')
|
113
|
+
to_name, to_port = c[:to].to_s.split(':')
|
114
|
+
from_id = @nodes[from_name.to_sym][:id]
|
115
|
+
to_id = @nodes[to_name.to_sym][:id]
|
116
|
+
|
117
|
+
from = from_port ? "#{from_id}:p#{from_port}" : from_id
|
118
|
+
to = to_port ? "#{to_id}:p#{to_port}" : to_id
|
119
|
+
line_style = override_style(:line, {}, :"#{c[:type]}")
|
120
|
+
|
121
|
+
@gv.edge "#{from}_#{to}_#{seq}".gsub('/', '').to_sym, line_style
|
122
|
+
seq += 1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def override_style(type, origin, style_type)
|
127
|
+
|
128
|
+
default =
|
129
|
+
case type
|
130
|
+
when :line
|
131
|
+
DEFAULT_LINE_STYLE
|
132
|
+
when :node
|
133
|
+
DEFAULT_NODE_STYLE
|
134
|
+
else
|
135
|
+
DEFAULT_STYLE
|
136
|
+
end
|
137
|
+
origin = {} unless origin
|
138
|
+
return default.merge(origin) unless style_type
|
139
|
+
style = @style[:types][style_type] if @style[:types]
|
140
|
+
style ||= {}
|
141
|
+
default.merge(origin).merge(style)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'network_drawer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'network_drawer'
|
8
|
+
spec.version = NetworkDrawer::VERSION
|
9
|
+
spec.authors = ['Hiroshi Ota']
|
10
|
+
spec.email = ['otahi.pub@gmail.com']
|
11
|
+
spec.summary = 'Network diagram drawer with json'
|
12
|
+
spec.description = 'Network diagram drawer with json'
|
13
|
+
spec.homepage = 'https://github.com/otahi/network_drawer'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_dependency 'gviz', '~> 0.3.4'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rubocop', '0.24.1'
|
25
|
+
spec.add_development_dependency 'coveralls', '~> 0.7'
|
26
|
+
spec.add_development_dependency 'byebug', '~> 3.4.0'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: network_drawer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hiroshi Ota
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gviz
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.24.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.24.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coveralls
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.7'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.4.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.4.0
|
97
|
+
description: Network diagram drawer with json
|
98
|
+
email:
|
99
|
+
- otahi.pub@gmail.com
|
100
|
+
executables:
|
101
|
+
- network_drawer
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- bin/network_drawer
|
111
|
+
- examples/simple.dot
|
112
|
+
- examples/simple.json
|
113
|
+
- examples/simple.png
|
114
|
+
- examples/simple.svg
|
115
|
+
- examples/simple_style.json
|
116
|
+
- lib/network_drawer.rb
|
117
|
+
- lib/network_drawer/cli.rb
|
118
|
+
- lib/network_drawer/diagram.rb
|
119
|
+
- lib/network_drawer/source.rb
|
120
|
+
- lib/network_drawer/style.rb
|
121
|
+
- lib/network_drawer/version.rb
|
122
|
+
- network_drawer.gemspec
|
123
|
+
homepage: https://github.com/otahi/network_drawer
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.2.2
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Network diagram drawer with json
|
147
|
+
test_files: []
|