kwatable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/COPYING +340 -0
  2. data/ChangeLog.txt +27 -0
  3. data/README.txt +81 -0
  4. data/bin/kwatable +20 -0
  5. data/examples/ex1/Makefile +34 -0
  6. data/examples/ex1/example1.yaml +85 -0
  7. data/examples/ex2/Makefile +34 -0
  8. data/examples/ex2/example2.yaml +94 -0
  9. data/kwatable.gemspec +48 -0
  10. data/lib/kwatable.rb +31 -0
  11. data/lib/kwatable/error-msg.rb +37 -0
  12. data/lib/kwatable/kwatable.schema.yaml +133 -0
  13. data/lib/kwatable/main-program.rb +197 -0
  14. data/lib/kwatable/manufactory.rb +213 -0
  15. data/lib/kwatable/templates/ddl-mysql.eruby +169 -0
  16. data/lib/kwatable/templates/ddl-postgresql.eruby +153 -0
  17. data/lib/kwatable/templates/defaults.yaml +87 -0
  18. data/lib/kwatable/templates/dto-java.eruby +204 -0
  19. data/lib/kwatable/templates/dto-ruby.eruby +180 -0
  20. data/setup.rb +1331 -0
  21. data/test/assert-diff.rb +44 -0
  22. data/test/test.rb +202 -0
  23. data/test/test1/test1.ddl-mysql.expected +22 -0
  24. data/test/test1/test1.ddl-postgresql.expected +22 -0
  25. data/test/test1/test1.dto-java.Group.expected +32 -0
  26. data/test/test1/test1.dto-java.User.expected +59 -0
  27. data/test/test1/test1.dto-ruby.Group.expected +21 -0
  28. data/test/test1/test1.dto-ruby.User.expected +36 -0
  29. data/test/test1/test1.yaml +85 -0
  30. data/test/test2/test2.ddl-mysql.expected +49 -0
  31. data/test/test2/test2.ddl-postgresql.expected +49 -0
  32. data/test/test2/test2.dto-java.Address.expected +42 -0
  33. data/test/test2/test2.dto-java.Customer.expected +49 -0
  34. data/test/test2/test2.dto-java.Item.expected +37 -0
  35. data/test/test2/test2.dto-java.SalesOrder.expected +50 -0
  36. data/test/test2/test2.dto-java.SalesOrderLine.expected +56 -0
  37. data/test/test2/test2.dto-ruby.Address.expected +25 -0
  38. data/test/test2/test2.dto-ruby.Customer.expected +32 -0
  39. data/test/test2/test2.dto-ruby.Item.expected +23 -0
  40. data/test/test2/test2.dto-ruby.SalesOrder.expected +32 -0
  41. data/test/test2/test2.dto-ruby.SalesOrderLine.expected +39 -0
  42. data/test/test2/test2.yaml +94 -0
  43. metadata +91 -0
@@ -0,0 +1,197 @@
1
+ ###
2
+ ### copyright(c) 2005 kuwata-lab.com all rights reserved.
3
+ ### $Release: 0.0.1 $
4
+ ### $Rev: 12 $
5
+ ###
6
+
7
+ require 'erb'
8
+
9
+ module Kwatable
10
+
11
+ class CommandOptionError < KwatableError
12
+ end
13
+
14
+ class MainProgram
15
+ def initialize(argv=ARGV)
16
+ @argv = argv
17
+ end
18
+
19
+ def execute()
20
+ options, properties = parse_options(@argv)
21
+
22
+ ## help or version
23
+ if options[?h] || options[?v]
24
+ puts version() if options[?v]
25
+ puts usage() if options[?h]
26
+ return
27
+ end
28
+
29
+ ## load data file
30
+ s = ''
31
+ filenames = @argv
32
+ filenames.each do |filename|
33
+ File.open(filename) do |f|
34
+ f.each_line do |line|
35
+ s << line.gsub(/([^\t]{8})|([^\t]*)\t/n){[$+].pack("A8")} ## expand tab
36
+ end
37
+ end
38
+ end
39
+ yaml = YAML.load(s)
40
+
41
+ ## manufacture
42
+ manufactory = Manufactory.new()
43
+ manufactory.manufacture(yaml)
44
+ $stderr.print yaml.to_yaml if options[?D]
45
+
46
+ ## template filename
47
+ template = options[?f]
48
+ unless template
49
+ return nil if options[?D]
50
+ #* key=:template_required msg="template is not specified."
51
+ raise CommandOptionError.new(Kwatable.msg(:template_required))
52
+ end
53
+
54
+ ## template filepath
55
+ template_filepath = nil
56
+ if test(?f, template)
57
+ template_filepath = template
58
+ elsif options[?I] && t = find_template(template, options[?I])
59
+ template_filepath = t
60
+ else
61
+ template_filepath = find_template(template, Kwatable.template_path)
62
+ end
63
+ unless template_filepath
64
+ #* key=:template_notfound msg="`%s': template file not found."
65
+ raise CommandOptionError.new(Kwatable.msg(:template_notfound) % template)
66
+ end
67
+
68
+ ## apply template
69
+ if !options[?m]
70
+ context = { 'tables' => yaml['tables'], 'properties' => properties, }
71
+ output = apply_template(template_filepath, context)
72
+ return output
73
+ else
74
+ yaml['tables'].each do |table|
75
+ context = { 'table' => table, 'properties' => properties, }
76
+ output = apply_template(template_filepath, context)
77
+ output_filename = context[:output_filename]
78
+ output_filename = "#{options[?d]}/#{output_filename}" if options[?d]
79
+ File.open(output_filename, 'w') { |f| f.write(output) }
80
+ unless options[?s]
81
+ #* key=:file_generated msg="generated: %s"
82
+ $stderr.puts(Kwatable.msg(:file_generated) % output_filename)
83
+ end
84
+ end
85
+ return nil
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def parse_options(argv)
92
+ options = {}
93
+ properties = {}
94
+ while argv[0] && argv[0][0] == ?-
95
+ optstr = argv.shift
96
+ if optstr =~ /\A--([-\w]+)(=.*)?/ ## properties
97
+ key, value = $1, $2
98
+ key = key.gsub(/-/, '_')
99
+ if value
100
+ value.sub!(/\A=/, '')
101
+ else
102
+ value = true
103
+ end
104
+ case value
105
+ when "true", "yes" ; value = true
106
+ when "false", "no" ; value = false
107
+ when "null", "nil" ; value = nil
108
+ when /\A\d+\z/ ; value = value.to_i
109
+ when /\A\d+\.\d+\z/ ; value = value.to_f
110
+ when /\A'.*'\z/ ; value = eval(value)
111
+ when /\A".*"\z/ ; value = eval(value)
112
+ end
113
+ properties[key.intern] = value
114
+ else
115
+ optstr = optstr[1, optstr.length-1]
116
+ while optstr && !optstr.empty?
117
+ optchar = optstr[0]
118
+ optstr = optstr[1, optstr.length-1]
119
+ case optchar
120
+ when ?h, ?v, ?m, ?s, ?D
121
+ options[optchar] = true
122
+ when ?f, ?t
123
+ arg = optstr.empty? ? argv.shift : optstr
124
+ optstr = nil
125
+ unless arg
126
+ #* key=:template_required msg="-%s: template filename required."
127
+ raise CommandOptionError.new(Kwatable.msg(:template_required) % optchar.chr)
128
+ end
129
+ options[?f] = arg
130
+ #options[optchar] = arg
131
+ when ?d
132
+ arg = optstr.empty? ? argv.shift : optstr
133
+ optstr = nil
134
+ unless arg
135
+ #* key=:outdir_required msg="-%s: output directory required."
136
+ raise CommandOptionError.new(Kwatable.msg(:outdir_required) % optchar.chr)
137
+ end
138
+ options[optchar] = arg
139
+ when ?I
140
+ arg = optstr.empty? ? argv.shift : optstr
141
+ optstr = nil
142
+ unless arg
143
+ #* key=:directory_required msg="-%s: directory required."
144
+ raise CommandOptionError.new(Kwatable.msg(:directory_required) % optchar.chr)
145
+ end
146
+ (options[optchar] ||= []).concat(arg.split(/,/))
147
+ else
148
+ #* key=:option_invalid msg="-%s: invalid option."
149
+ raise CommandOptionError.new(Kwatable.msg(:option_invalid) % optchar.chr)
150
+ end
151
+ end # end while
152
+ end # end if
153
+ end # end while
154
+ return options, properties
155
+ end
156
+
157
+ def find_template(template, path_list)
158
+ path_list.each do |path|
159
+ t = "#{path}/#{template}"
160
+ return t if test(?f, t)
161
+ end
162
+ return nil
163
+ end
164
+
165
+ def apply_template(filename, context)
166
+ str = File.open(filename) { |f| f.read() }
167
+ trim_mode = '>' # or '%'
168
+ erb = ERB.new(str, $SAFE, trim_mode)
169
+ result = eval_erb(erb, context)
170
+ return result
171
+ end
172
+
173
+ def eval_erb(__erb, context)
174
+ return __erb.result(binding())
175
+ end
176
+
177
+ def usage()
178
+ command = File::basename($0)
179
+ s = ""
180
+ s << "Usage: #{command} [-hvm] [-I path] [-d dir] -f template datafile [datafile2 ...]\n"
181
+ s << " -h : help\n"
182
+ s << " -v : version\n"
183
+ s << " -I path : template directory path\n"
184
+ s << " -f template : template filename\n"
185
+ s << " -m : multiple output file\n"
186
+ s << " -d dir : output file directory (with '-m')\n"
187
+ s << " -s : silent mode\n"
188
+ return s
189
+ end
190
+
191
+ def version()
192
+ return ("$Release: 0.0.1 $" =~ /[\.\d]+/ && $&)
193
+ end
194
+
195
+ end
196
+
197
+ end
@@ -0,0 +1,213 @@
1
+ ###
2
+ ### copyright(c) 2005 kuwata-lab.com all rights reserved.
3
+ ### $Release: 0.0.1 $
4
+ ### $Rev: 11 $
5
+ ###
6
+
7
+ require 'yaml'
8
+
9
+ module Kwatable
10
+
11
+ class ManufactureError < KwatableError
12
+ end
13
+
14
+
15
+ ##
16
+ ## ex.
17
+ ## manufactory = Kwatable::Manufactory.new
18
+ ## tabledef = manufactory.parse(input) # input is String or File
19
+ ## p tabledef[:columns]
20
+ ## p tabledef[:tables]
21
+ ##
22
+ class Manufactory
23
+
24
+ def parse(input)
25
+ str = ''
26
+ input.each_line do |line|
27
+ str << line.gsub(/([^\t]{8})|([^\t]*)\t/n){[$+].pack("A8")} ## expand tab
28
+ end
29
+ ingredient = YAML.load(str)
30
+ manufacture(ingredient)
31
+ return ingredient
32
+ end
33
+
34
+ def manufacture(ingredient)
35
+ #assert unless ingredient.is_a?(Hash)
36
+
37
+ column_map, patterned_columns = manufacture_columns(ingredient['columns'])
38
+ #assert unless column_map.is_a?(Hash)
39
+ #assert unless patterned_columns.is_a?(Array)
40
+
41
+ table_map = manufacture_tables(ingredient['tables'], column_map, patterned_columns)
42
+ #assert unless table_map.is_a?(Hash)
43
+
44
+ ingredient['column_map'] = column_map # Hash
45
+ ingredient['table_map'] = table_map # Hash
46
+ return ingredient
47
+ end
48
+
49
+ private
50
+
51
+ def manufacture_columns(columns)
52
+ column_map = {}
53
+ patterned_columns = []
54
+ columns.each do |column|
55
+ name = column['name']
56
+ unless name
57
+ #* key=:colname_required msg="column definition doesn't have a name."
58
+ raise ManufactureError.new(Kwatable.msg(:colname_required))
59
+ end
60
+ if name =~ /\A\/(.*)\/\z/
61
+ pattern = $1
62
+ begin
63
+ name = Regexp.compile(pattern)
64
+ rescue RegexpError => ex
65
+ #* key=:regexp_invalid msg="column %s: %s"
66
+ raise ManufactureError.new(Kwatable.msg(:regexp_invalid) % [name, ex.message])
67
+ end
68
+ column['namepattern'] = name
69
+ #column.delete('name')
70
+ patterned_columns << column
71
+ else
72
+ if column_map.key?(name)
73
+ #* key=:coldef_duplicated msg="column definition `%s' is duplicated."
74
+ raise ManufactureError.new(Kwatable.msg(:coldef_duplicated) % name)
75
+ end
76
+ column_map[name] = column
77
+ end
78
+ #name = column['name'].strip
79
+ #namepattern = column['namepattern']
80
+ #unless name || namepattern
81
+ # #* key=:colname_required msg="column definition doesn't have a name nor namepattern."
82
+ # raise ManufactureError.new(Kwatable.msg(:colname_required))
83
+ #end
84
+ #if name
85
+ # if column_map.key?(name)
86
+ # #* key=:coldef_duplicated msg="column definition `%s' is duplicated."
87
+ # raise ManufactureError.new(Kwatable.msg(:coldef_duplicated) % [name])
88
+ # end
89
+ # column_map[name] = column
90
+ #end
91
+ #if namepattern
92
+ # pattern = namepattern.strip
93
+ # pattern = $1 if pattern =~ /^\/(.*)\/$/
94
+ # column['namepattern'] = Regexp.compile(pattern)
95
+ # patterned_columns << column
96
+ #end
97
+ end if columns
98
+ return column_map, patterned_columns
99
+ end
100
+
101
+ def manufacture_tables(tables, column_map, patterned_columns)
102
+ #assert unless tables.is_a?(Array)
103
+ #assert unless column_map.is_a?(Hash)
104
+ #assert unless patterned_columns.is_a?(Array)
105
+
106
+ ## create table_map
107
+ table_map = {}
108
+ tables.each do |table|
109
+ name = table['name']
110
+ unless name
111
+ #* key=:tablename_required msg="table definition doesn't have a name."
112
+ raise ManufactureError.new(Kwatable.msg(:tablename_required))
113
+ end
114
+ if table_map.key?(name)
115
+ #* key=:tabledef_duplicated msg="table definition `%s' is duplicated."
116
+ raise ManufactureError.new(Kwatable.msg(:tabledef_duplicated) % [name])
117
+ end
118
+ table_map[name] = table
119
+ end
120
+
121
+ ## manufacture table columns
122
+ tables.each do |table|
123
+ name_map = {}
124
+ table['columns'].each do |column|
125
+ name = column['name']
126
+ unless name
127
+ #* key=:tablecolumn_required msg="table '%s': column name requried."
128
+ raise ManufactureError.new(Kwatable.msg(:tablecolumn_required % [table['name']]))
129
+ end
130
+ if name_map[name]
131
+ #* key=:tablecolumn_duplicated msg="table '%s': column '%s' is duplicated."
132
+ raise ManufactureError.new(Kwatable.msg(:tablecolumn_duplicated % [table['name'], name]))
133
+ end
134
+ name_map[name] = true
135
+ set_defaults(column, column_map, patterned_columns)
136
+ #alias_keys(column, "primary-key", "primarykey", "identifier")
137
+ #alias_keys(column, "not-null", "notnull", "required")
138
+ alias_key(column, "primary-key", "identifier")
139
+ alias_key(column, "not-null", "required")
140
+ handle_ref(column, table_map) if column['ref']
141
+ handle_values(column) if column['values']
142
+ unless column['type']
143
+ #* key=:tabletype_required msg="table `%s': type of column `%s' is not determined."
144
+ raise ManufactureError.new(Kwatable.msg(:tabletype_required) % [table['name'], column['name']])
145
+ end
146
+ end if table['columns']
147
+ end if tables
148
+
149
+ return table_map
150
+ end
151
+
152
+ def set_defaults(column, column_map, patterned_columns)
153
+ colname = column['name']
154
+ defaults = column_map[colname]
155
+ defaults ||= patterned_columns.find { |col| colname =~ col['namepattern'] }
156
+ defaults.each do |key, val|
157
+ column[key] = val if !column.key?(key) && key != 'namepattern'
158
+ end if defaults
159
+ end
160
+
161
+ def alias_keys(column, key, *old_keys) # not used
162
+ old_keys.each do |old_key|
163
+ column[key] = column[old_key] if !column.key?(key) && column.key?(old_key)
164
+ end if old_keys
165
+ end
166
+
167
+ def alias_key(column, key1, key2)
168
+ if column[key1] && !column.key?(key2)
169
+ column[key2] = column[key1]
170
+ elsif column[key2] && !column.key?(key1)
171
+ column[key1] = column[key2]
172
+ end
173
+ end
174
+
175
+ def handle_values(column)
176
+ return unless column['values']
177
+ width = 0
178
+ column['values'].each do |value|
179
+ len = value.to_s.length
180
+ width = len if len > width
181
+ end
182
+ column['type'] ||= 'string'
183
+ column['width'] ||= width
184
+ end
185
+
186
+ def handle_ref(column, table_map)
187
+ ref = column['ref']
188
+ return unless ref
189
+ ref = ref.strip
190
+ return unless ref =~ /\A(\w+)\.(\w+)\z/ || ref =~ /\A(\w+)\((\w+)\)\z/
191
+ ref_table_name = $1
192
+ ref_column_name = $2
193
+ ref_table = table_map[ref_table_name]
194
+ unless ref_table
195
+ #* key=:reftable_notfound msg="`ref: %s': table not found."
196
+ raise ManufactureError.new(Kwatable.msg(:reftable_notfound) % column['ref'])
197
+ end
198
+ cols = ref_table['columns']
199
+ ref_column = cols ? cols.find { |col| col['name'] == ref_column_name } : nil
200
+ unless ref_column
201
+ #* key=:refcolumn_notfound msg="`ref: %s': column not found in the table."
202
+ raise ManufactureError.new(Kwatable.msg(:refcolumn_notfound) % column['ref'])
203
+ end
204
+ column['ref-table'] = ref_table
205
+ column['ref-column'] = ref_column
206
+ column['ref-name'] ||= column['name'].sub(/_#{ref_column_name}$/, '')
207
+ column['type'] = ref_column['type']
208
+ column['width'] ||= ref_column['width'] if ref_column.key?('width')
209
+ end
210
+
211
+ end
212
+
213
+ end
@@ -0,0 +1,169 @@
1
+ <%
2
+
3
+ ##
4
+ ## kwatable template file for MySQL
5
+ ##
6
+ ## copyright(c) 2005 kuwata-lab.com all rights reserved.
7
+ ## $Release: 0.0.1 $
8
+ ## $Rev: 12 $
9
+ ##
10
+ ## template properties:
11
+ ## (none)
12
+
13
+
14
+ #
15
+ # context variables
16
+ #
17
+ tables = context['tables']
18
+ properties = context['properties']
19
+ raise "don't use '-m' option with 'ddl-mysql.eruby'." unless tables
20
+
21
+
22
+ #
23
+ # MySQL keywords
24
+ #
25
+ keywords = <<-END
26
+ add all alter analyze and as asc asensitive
27
+ before between bigint binary blob both by
28
+ call cascade case change char character check collate column
29
+ condition connection constraint continue convert create cross
30
+ current_date current_time current_timestamp current_user cursor
31
+ database databases day_hour day_microsecond day_minute day_second
32
+ dec decimal declare default delayed delete desc describe
33
+ deterministic distinct distinctrow div double drop dual
34
+ each else elseif enclosed escaped exists exit explain
35
+ false fetch float for force foreign from fulltext
36
+ goto grant group
37
+ having high_priority hour_microsecond hour_minute hour_second
38
+ if ignore in index infile inner inout insensitive insert
39
+ int integer interval into is iterate
40
+ join
41
+ key keys kill
42
+ leading leave left like limit lines load localtime
43
+ localtimestamp lock long longblob longtext loop low_priority
44
+ match mediumblob mediumint mediumtext middleint
45
+ minute_microsecond minute_second mod modifies
46
+ natural not no_write_to_binlog null numeric
47
+ on optimize option optionally or order out outer outfile
48
+ precision primary procedure purge
49
+ read reads real references regexp release rename repeat
50
+ replace require restrict return revoke right rlike
51
+ schema schemas second_microsecond select sensitive
52
+ separator set show smallint soname spatial specific sql
53
+ sqlexception sqlstate sqlwarning sql_big_result
54
+ sql_calc_found_rows sql_small_result ssl starting straight_join
55
+ table terminated then tinyblob tinyint tinytext to
56
+ trailing trigger true
57
+ undo union unique unlock unsigned update usage use using
58
+ utc_date utc_time utc_timestamp
59
+ values varbinary varchar varcharacter varying
60
+ when where while with write
61
+ xor
62
+ year_month
63
+ zerofill
64
+ END
65
+ KEYWORDS = {}
66
+ keywords.split(/\s+/).each { |word| KEYWORDS[word] = true }
67
+
68
+
69
+ #
70
+ # escape keyword
71
+ #
72
+ def _(word)
73
+ return KEYWORDS[word.downcase] ? "`#{word}`" : word
74
+ end
75
+
76
+
77
+ #
78
+ # start output
79
+ #
80
+ %>
81
+ ----------------------------------------------------------------------
82
+ -- DDL for MySQL
83
+ -- generated by kwatable with template 'ddl-mysql.eruby'
84
+ -- at <%= Time.now.to_s %>
85
+
86
+ ----------------------------------------------------------------------
87
+ <%
88
+ #
89
+ # create table statement
90
+ #
91
+ %>
92
+ <% for table in tables %>
93
+
94
+ -- <%= table['desc'] %>
95
+
96
+ create table <%= _(table['name']) %> (
97
+ <%
98
+ n = table['columns'].length
99
+ i = 0
100
+ for column in table['columns']
101
+ i += 1
102
+ flag_last_loop = (i == n)
103
+
104
+ name = column['name']
105
+ type = column['type']
106
+ width = column['width']
107
+
108
+ #
109
+ # column type
110
+ #
111
+ case type
112
+ when 'char' ; type = 'tinyint'
113
+ when 'short' ; type = 'mediumint'
114
+ when 'int' ; type = 'integer'
115
+ when 'inteter' ;
116
+ when 'str' ; type = 'varchar' ; width ||= 255
117
+ when 'string' ; type = 'varchar' ; width ||= 255
118
+ when 'text' ;
119
+ when 'float' ;
120
+ when 'double' ;
121
+ when 'bool' ; type = 'boolean'
122
+ when 'boolean' ;
123
+ when 'date' ;
124
+ when 'timestamp' ;
125
+ when 'money' ; type = 'decimal'
126
+ end
127
+ type += "(#{width})" if width
128
+
129
+ #
130
+ # set type with 'enum(...)' if column has values
131
+ #
132
+ if column['values']
133
+ type = "enum(" + column['values'].collect{|v| "'#{v}'"}.join(", ") + ")"
134
+ width = nil
135
+ end
136
+
137
+ #
138
+ # constraints
139
+ #
140
+ constraints = []
141
+ constraints << 'auto_increment' if column['serial']
142
+ constraints << 'not null' if column['not-null'] && !column['serial'] && !column['primary-key']
143
+ constraints << 'primary key' if column['primary-key']
144
+ constraints << 'unique' if column['unique']
145
+
146
+ #
147
+ # column definition
148
+ #
149
+ name_part = '%-20s' % _(name)
150
+ type_part = '%-20s' % type
151
+ const_part = constraints.join(' ')
152
+ comma = flag_last_loop ? '' : ','
153
+ comment = column['ref'] ? " -- references #{column['ref']}" : ""
154
+ %>
155
+ <%= name_part %> <%= type_part %> <%= const_part %><%= comma %><%= comment %>
156
+
157
+ <%
158
+ end
159
+
160
+ #
161
+ # composite primary key
162
+ #
163
+ %>
164
+ <% if table['primary-keys'] %>
165
+ <% pkeystr = table['primary-keys'].collect { |pkey| _(pkey) }.join(', ') %>
166
+ , primary key (<%= pkeystr %>)
167
+ <% end %>
168
+ );
169
+ <% end %>