ymldot 0.0.1
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.
- data/README +102 -0
- data/bin/ymldot +27 -0
- data/lib/ymldot.rb +187 -0
- data/sample/sample.dot +13 -0
- data/sample/sample.png +0 -0
- data/sample/sample.yml +30 -0
- data/sample/sample_jp.dot +13 -0
- data/sample/sample_jp.png +0 -0
- data/sample/sample_jp.yml +34 -0
- metadata +61 -0
data/README
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
= ymldot
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
Can describe ERD in a form of ".yml".
|
6
|
+
ERD method use IDEF1X.
|
7
|
+
".yml" file to ".dot" file. ".dot" is a language used in "graphviz".
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
(1) if not install graphviz, type following.
|
11
|
+
* $ apt-get install graphviz
|
12
|
+
(2) get ymldot.
|
13
|
+
* $ gem install ymldot
|
14
|
+
|
15
|
+
== Option
|
16
|
+
please see ymldot --help
|
17
|
+
|
18
|
+
== Yml format
|
19
|
+
|
20
|
+
* config
|
21
|
+
* font
|
22
|
+
* Please set the font name that You want to use.
|
23
|
+
* tables
|
24
|
+
* name
|
25
|
+
* table name
|
26
|
+
* columns
|
27
|
+
* enumerate column names
|
28
|
+
* foreginkeys
|
29
|
+
* has_many
|
30
|
+
* has_one
|
31
|
+
* has_many_and_belongs_to
|
32
|
+
* same meaning the relation of ActiveRecord.
|
33
|
+
* enumerate ref table name.
|
34
|
+
* polymorphic
|
35
|
+
* name
|
36
|
+
* type
|
37
|
+
* tables
|
38
|
+
* target table name.
|
39
|
+
|
40
|
+
* category
|
41
|
+
* label
|
42
|
+
* category name set. and set frame.
|
43
|
+
* table
|
44
|
+
* nested table.
|
45
|
+
|
46
|
+
== Sample
|
47
|
+
* in file. "sample/sample.yml"
|
48
|
+
|
49
|
+
tables:
|
50
|
+
- name: customer
|
51
|
+
columns:
|
52
|
+
- name
|
53
|
+
- phone_number
|
54
|
+
foreignkeys:
|
55
|
+
has_many:
|
56
|
+
- order
|
57
|
+
- name: order
|
58
|
+
columns:
|
59
|
+
- order_num
|
60
|
+
- name: product
|
61
|
+
columns:
|
62
|
+
- name
|
63
|
+
- amount
|
64
|
+
- tax
|
65
|
+
- product_div
|
66
|
+
foreignkeys:
|
67
|
+
has_many:
|
68
|
+
- order
|
69
|
+
- name: category
|
70
|
+
columns:
|
71
|
+
- name
|
72
|
+
foreignkeys:
|
73
|
+
has_many:
|
74
|
+
- product
|
75
|
+
|
76
|
+
* output dot file. "sample/sample.dot"
|
77
|
+
|
78
|
+
digraph sample {
|
79
|
+
graph[overlap=false, splines=true]
|
80
|
+
|
81
|
+
"category" [shape=record, label="{category|name\l}"]
|
82
|
+
"customer" [shape=record, label="{customer|name\lphone_number\l}"]
|
83
|
+
"order" [shape=record, label="{order|customerID(FK)\lproductID(FK)\lorder_num\l}"]
|
84
|
+
"product" [shape=record, label="{product|categoryID(FK)\lname\lamount\ltax\lproduct_div\l}"]
|
85
|
+
|
86
|
+
"category" -> "product" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
87
|
+
"customer" -> "order" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
88
|
+
"product" -> "order" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
89
|
+
|
90
|
+
}
|
91
|
+
|
92
|
+
* please type
|
93
|
+
$ dot -Tpng sample.dot -o sample.png
|
94
|
+
|
95
|
+
== Copyright
|
96
|
+
|
97
|
+
* Author:: nari <authorNari@gmail.com>
|
98
|
+
* Copyright:: Copyright (c) 2008 nari
|
99
|
+
* License:: Ruby's
|
100
|
+
|
101
|
+
== Link
|
102
|
+
* ((<ymldot-project|URL:http://rubyforge.org/projects/ymldot>))
|
data/bin/ymldot
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/ruby -wKU
|
2
|
+
|
3
|
+
if File.symlink?(__FILE__)
|
4
|
+
$: << File.join(File.dirname(File.readlink(__FILE__)), '../lib')
|
5
|
+
else
|
6
|
+
$: << File.join(File.dirname(__FILE__), '../lib')
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'ymldot'
|
10
|
+
require 'optparse'
|
11
|
+
|
12
|
+
Version = "0.0.1"
|
13
|
+
is_output = false
|
14
|
+
opt = OptionParser.new
|
15
|
+
opt.version = Version
|
16
|
+
opt.release = "0.0.1"
|
17
|
+
opt.on('-o', 'output file to current directory. name is "#{input_filename}.dot"') {|v| is_output = true }
|
18
|
+
opt.parse!(ARGV)
|
19
|
+
|
20
|
+
if is_output
|
21
|
+
y = Ymldot.new(ARGV[0])
|
22
|
+
open("./#{y.file_name}.dot", "w") do |f|
|
23
|
+
f.write(y.dot_generate)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
puts Ymldot.new(ARGV[0]).dot_generate
|
27
|
+
end
|
data/lib/ymldot.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
class Entity
|
5
|
+
def initialize(name, dependent, columns = [])
|
6
|
+
@name = name
|
7
|
+
@dependent = dependent
|
8
|
+
@columns = columns
|
9
|
+
@foreignkeys = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def dependent?
|
13
|
+
@dependent
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_dot
|
17
|
+
res = ""
|
18
|
+
res << %Q!"#{@name}" [#{@dependent? 'shape=Mrecord, ' : 'shape=record, '}label=\"{#{@name}|!
|
19
|
+
@foreignkeys.each{|f| res << "#{f}\\l"}
|
20
|
+
@columns.each{|c| res << "#{c}\\l"}
|
21
|
+
res << '}"]'
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :name, :dependent, :columns, :foreignkeys
|
25
|
+
end
|
26
|
+
|
27
|
+
class Tables
|
28
|
+
def initialize(table_node, label=nil, category=false)
|
29
|
+
@label = label
|
30
|
+
@category = category
|
31
|
+
@table_node = table_node
|
32
|
+
@entity_dict = {}
|
33
|
+
table_node.each {|t| eval_entity(t) }
|
34
|
+
end
|
35
|
+
attr_accessor :entity_dict, :label, :table_node, :category
|
36
|
+
|
37
|
+
def entity_dict_to_dot
|
38
|
+
res = ""
|
39
|
+
s = []
|
40
|
+
@entity_dict.each_value{|e| s << e }
|
41
|
+
if @category
|
42
|
+
res << "label=#{@label}\n" if @label
|
43
|
+
res << "style=invis\n" unless @label
|
44
|
+
end
|
45
|
+
s.sort{|a, b| a.name <=> b.name}.each{|e| res << e.to_dot << "\n"}
|
46
|
+
res
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def eval_entity(table)
|
51
|
+
columns = table["columns"]; columns ||= []
|
52
|
+
@entity_dict[table["name"]] ||= Entity.new(table["name"], table["dependent"], columns)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
class Ymldot
|
59
|
+
attr_accessor :file_name
|
60
|
+
|
61
|
+
def initialize(filepath)
|
62
|
+
open(filepath) do |f|
|
63
|
+
str = f.read
|
64
|
+
@node = YAML.load(str)
|
65
|
+
end
|
66
|
+
@file_name = $1 if filepath[/(\w+).yml\z/]
|
67
|
+
@entity_dict = {}
|
68
|
+
@category = []
|
69
|
+
@one_relations = []
|
70
|
+
@many_relations = []
|
71
|
+
eval_yml
|
72
|
+
end
|
73
|
+
|
74
|
+
def dot_generate
|
75
|
+
code = ""
|
76
|
+
code += <<"EOS"
|
77
|
+
digraph #{@file_name} {
|
78
|
+
#{add_2_tab(config_to_dot)}
|
79
|
+
#{add_2_tab(entity_dict_to_dot)}
|
80
|
+
#{add_2_tab(relations_to_dot)}
|
81
|
+
}
|
82
|
+
EOS
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def add_2_tab(str)
|
87
|
+
res = ""
|
88
|
+
str.each_line{|s| res << " " << s}
|
89
|
+
res
|
90
|
+
end
|
91
|
+
|
92
|
+
def config_to_dot
|
93
|
+
res = ""
|
94
|
+
res << "graph[overlap=false, splines=true]\n"
|
95
|
+
res << %Q!node [fontname="#{@config["font"]}"]! if @config["font"]
|
96
|
+
res
|
97
|
+
end
|
98
|
+
|
99
|
+
def entity_dict_to_dot
|
100
|
+
res = ""
|
101
|
+
unless @category.empty?
|
102
|
+
@category.each_with_index do |c, i|
|
103
|
+
unless c.category
|
104
|
+
res << c.entity_dict_to_dot
|
105
|
+
else
|
106
|
+
res << "subgraph cluster#{i} {\n"
|
107
|
+
res << add_2_tab(c.entity_dict_to_dot)
|
108
|
+
res << "}\n"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
res
|
113
|
+
end
|
114
|
+
|
115
|
+
def relations_to_dot
|
116
|
+
res = ""
|
117
|
+
@one_relations.sort{|a, b| a[:self].name <=> b[:self].name}.each do |r|
|
118
|
+
res << %Q!"#{r[:self].name}" -> "#{r[:have].name}" [arrowtail=none arrowhead=dot headlabel="1" taillabel="1"]! << "\n"
|
119
|
+
end
|
120
|
+
@many_relations.sort{|a, b| a[:self].name <=> b[:self].name}.each do |r|
|
121
|
+
res << %Q!"#{r[:self].name}" -> "#{r[:have].name}" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]! << "\n"
|
122
|
+
end
|
123
|
+
res
|
124
|
+
end
|
125
|
+
|
126
|
+
def eval_yml
|
127
|
+
@config = @node["config"]? @node["config"] : {}
|
128
|
+
@category << Tables.new(@node["tables"]) if @node["tables"] and !@node["tables"].empty?
|
129
|
+
@node["category"].each{|c| @category << Tables.new(c["tables"], c["label"], true) } if @node["category"]
|
130
|
+
@category.each{|c| c.entity_dict.each_pair{|k, v| @entity_dict[k] = v}}
|
131
|
+
@category.each{|c| c.table_node.each{|e| eval_relation(e) } }
|
132
|
+
end
|
133
|
+
|
134
|
+
def eval_relation(table)
|
135
|
+
foreignkeys = table["foreignkeys"]
|
136
|
+
tname = table["name"]
|
137
|
+
return unless foreignkeys
|
138
|
+
eval_relation_has_many(foreignkeys["has_many"], tname) if foreignkeys["has_many"]
|
139
|
+
eval_relation_has_one(foreignkeys["has_one"], tname) if foreignkeys["has_one"]
|
140
|
+
eval_relation_hmabt(foreignkeys["has_many_and_belongs_to"], tname) if foreignkeys["has_many_and_belongs_to"]
|
141
|
+
eval_relation_polymorphic(foreignkeys["polymorphic"], tname) if foreignkeys["polymorphic"]
|
142
|
+
end
|
143
|
+
|
144
|
+
def eval_relation_has_many(keys, tname)
|
145
|
+
keys.each do |rel|
|
146
|
+
@entity_dict[rel].foreignkeys << "#{tname}ID(FK)"
|
147
|
+
@many_relations << {:self => @entity_dict[tname], :have => @entity_dict[rel]}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def eval_relation_has_one(keys, tname)
|
152
|
+
keys.each do |rel|
|
153
|
+
@entity_dict[rel].foreignkeys << "#{tname}ID(FK)"
|
154
|
+
@one_relations << {:self => @entity_dict[tname], :have => @entity_dict[rel]}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def eval_relation_hmabt(keys, tname)
|
159
|
+
keys.each do |rel|
|
160
|
+
join_tname = "#{rel}_#{tname}"
|
161
|
+
return if @entity_dict.has_key? join_tname
|
162
|
+
join_tname = "#{tname}_#{rel}"
|
163
|
+
@entity_dict[join_tname] ||= Entity.new(join_tname, true)
|
164
|
+
@entity_dict[join_tname].foreignkeys << "#{tname}ID(FK)"
|
165
|
+
@entity_dict[join_tname].foreignkeys << "#{rel}ID(FK)"
|
166
|
+
|
167
|
+
# make new category
|
168
|
+
keys = []
|
169
|
+
keys << "#{tname}ID(FK)"
|
170
|
+
keys << "#{rel}ID(FK)"
|
171
|
+
@category << Tables.new([{"name" => join_tname, "columns" => keys}])
|
172
|
+
|
173
|
+
@many_relations << {:self => @entity_dict[tname], :have => @entity_dict[join_tname]}
|
174
|
+
@many_relations << {:self => @entity_dict[rel], :have => @entity_dict[join_tname]}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def eval_relation_polymorphic(keys, tname)
|
179
|
+
keys.each do |rel|
|
180
|
+
@entity_dict[tname].foreignkeys << "#{rel["name"]}ID(FK)" if key = rel["name"]
|
181
|
+
@entity_dict[tname].columns.unshift "#{rel["type"]}(type)" if key = rel["type"]
|
182
|
+
rel["tables"].each do |t|
|
183
|
+
@one_relations << {:self => @entity_dict[t], :have => @entity_dict[tname] }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/sample/sample.dot
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
digraph sample {
|
2
|
+
graph[overlap=false, splines=true]
|
3
|
+
|
4
|
+
"category" [shape=record, label="{category|name\l}"]
|
5
|
+
"customer" [shape=record, label="{customer|name\lphone_number\l}"]
|
6
|
+
"order" [shape=record, label="{order|customerID(FK)\lproductID(FK)\lorder_num\l}"]
|
7
|
+
"product" [shape=record, label="{product|categoryID(FK)\lname\lamount\ltax\lproduct_div\l}"]
|
8
|
+
|
9
|
+
"category" -> "product" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
10
|
+
"customer" -> "order" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
11
|
+
"product" -> "order" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
12
|
+
|
13
|
+
}
|
data/sample/sample.png
ADDED
Binary file
|
data/sample/sample.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# reference http://codezine.jp/article/detail/154?p=1
|
2
|
+
tables:
|
3
|
+
- name: customer
|
4
|
+
columns:
|
5
|
+
- name
|
6
|
+
- phone_number
|
7
|
+
foreignkeys:
|
8
|
+
has_many:
|
9
|
+
- order
|
10
|
+
|
11
|
+
- name: order
|
12
|
+
columns:
|
13
|
+
- order_num
|
14
|
+
|
15
|
+
- name: product
|
16
|
+
columns:
|
17
|
+
- name
|
18
|
+
- amount
|
19
|
+
- tax
|
20
|
+
- product_div
|
21
|
+
foreignkeys:
|
22
|
+
has_many:
|
23
|
+
- order
|
24
|
+
|
25
|
+
- name: category
|
26
|
+
columns:
|
27
|
+
- name
|
28
|
+
foreignkeys:
|
29
|
+
has_many:
|
30
|
+
- product
|
@@ -0,0 +1,13 @@
|
|
1
|
+
digraph sample_jp {
|
2
|
+
graph[overlap=false, splines=true]
|
3
|
+
node [fontname="MSUIGOTHIC.ttf"]
|
4
|
+
"商品" [shape=record, label="{商品|商品カテゴリID(FK)\l名前\l金額\l税\l商品区分\l}"]
|
5
|
+
"商品カテゴリ" [shape=record, label="{商品カテゴリ|商品カテゴリ名\l}"]
|
6
|
+
"注文" [shape=record, label="{注文|顧客ID(FK)\l商品ID(FK)\l注文数\l}"]
|
7
|
+
"顧客" [shape=record, label="{顧客|名前\l電話番号\l}"]
|
8
|
+
|
9
|
+
"商品" -> "注文" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
10
|
+
"商品カテゴリ" -> "商品" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
11
|
+
"顧客" -> "注文" [arrowtail=none arrowhead=dot headlabel="n" taillabel="1"]
|
12
|
+
|
13
|
+
}
|
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# reference http://codezine.jp/article/detail/154?p=1
|
2
|
+
config:
|
3
|
+
font: MSUIGOTHIC.ttf
|
4
|
+
|
5
|
+
tables:
|
6
|
+
- name: 顧客
|
7
|
+
columns:
|
8
|
+
- 名前
|
9
|
+
- 電話番号
|
10
|
+
foreignkeys:
|
11
|
+
has_many:
|
12
|
+
- 注文
|
13
|
+
|
14
|
+
- name: 注文
|
15
|
+
columns:
|
16
|
+
- 注文数
|
17
|
+
|
18
|
+
- name: 商品
|
19
|
+
columns:
|
20
|
+
- 名前
|
21
|
+
- 金額
|
22
|
+
- 税
|
23
|
+
- 商品区分
|
24
|
+
foreignkeys:
|
25
|
+
has_many:
|
26
|
+
- 注文
|
27
|
+
|
28
|
+
- name: 商品カテゴリ
|
29
|
+
columns:
|
30
|
+
- 商品カテゴリ名
|
31
|
+
foreignkeys:
|
32
|
+
has_many:
|
33
|
+
- 商品
|
34
|
+
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ymldot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nari
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-09-10 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: authornari@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- bin/ymldot
|
27
|
+
- lib/ymldot.rb
|
28
|
+
- sample/sample.dot
|
29
|
+
- sample/sample.png
|
30
|
+
- sample/sample.yml
|
31
|
+
- sample/sample_jp.dot
|
32
|
+
- sample/sample_jp.png
|
33
|
+
- sample/sample_jp.yml
|
34
|
+
has_rdoc: false
|
35
|
+
homepage: http://coderepos.org/share/browser/lang/ruby/ymldot
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project: ymldot
|
56
|
+
rubygems_version: 1.1.1
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: Can describe ERD in a from of ".yml"
|
60
|
+
test_files: []
|
61
|
+
|