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 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
+