kwatable 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.
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 %>