migr8 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,148 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'oktest'
4
+ require 'migr8'
5
+ require 'stringio'
6
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'helpers')
7
+
8
+
9
+ Oktest.scope do
10
+
11
+
12
+ topic Migr8::Application do
13
+
14
+ klass = Migr8::Application
15
+
16
+
17
+ topic '.run()' do
18
+
19
+ fixture :app do
20
+ Migr8::Application.new
21
+ end
22
+
23
+ spec "[!dcggy] sets Migr8::DEBUG=true when '-d' or '--debug' specified." do |app|
24
+ [
25
+ ["-D"],
26
+ ["--debug"],
27
+ ].each do |args|
28
+ at_exit { Migr8.DEBUG = false }
29
+ sout, serr = Dummy.new.stdouterr do
30
+ Migr8::DEBUG = false
31
+ ok {Migr8::DEBUG} == false
32
+ status = app.run(args)
33
+ ok {Migr8::DEBUG} == true
34
+ end
35
+ end
36
+ end
37
+
38
+ spec "[!ktlay] prints help message and exit when '-h' or '--help' specified." do |app|
39
+ [
40
+ ["-h", "foo"],
41
+ ["--help", "foo"],
42
+ ].each do |args|
43
+ sout, serr = Dummy.new.stdouterr do
44
+ status = app.run(args)
45
+ ok {status} == 0
46
+ ok {args} == ["foo"]
47
+ end
48
+ expected = <<END
49
+ #{File.basename($0)} -- database schema version management tool
50
+
51
+ Usage: #{File.basename($0)} [global-options] [action [options] [...]]
52
+ -h, --help : show help
53
+ -v, --version : show version
54
+ -D, --debug : not remove sql file ('migr8/tmp.sql') for debug
55
+
56
+ Actions: (default: status)
57
+ readme : !!READ ME AT FIRST!!
58
+ help [action] : show help message of action, or list action names
59
+ init : create necessary files and a table
60
+ hist : list history of versions
61
+ new : create new migration file and open it by $MIGR8_EDITOR
62
+ show [version] : show migration file with expanding variables
63
+ edit [version] : open migration file by $MIGR8_EDITOR
64
+ status : show status
65
+ up : apply next migration
66
+ down : unapply current migration
67
+ redo : do migration down, and up it again
68
+ apply version ... : apply specified migrations
69
+ unapply version ... : unapply specified migrations
70
+ delete version ... : delete unapplied migration file
71
+
72
+ (ATTENTION!! Run '#{File.basename($0)} readme' at first if you don't know #{File.basename($0)} well.)
73
+
74
+ END
75
+ ok {sout} == expected
76
+ ok {serr} == ""
77
+ end
78
+ end
79
+
80
+ spec "[!n0ubh] prints version string and exit when '-v' or '--version' specified." do |app|
81
+ [
82
+ ["-v", "foo", "bar"],
83
+ ["--version", "foo", "bar"],
84
+ ].each do |args|
85
+ sout, serr = Dummy.new.stdouterr do
86
+ status = app.run(args)
87
+ ok {status} == 0
88
+ ok {args} == ["foo", "bar"]
89
+ end
90
+ expected = "#{Migr8::RELEASE}\n"
91
+ ok {sout} == expected
92
+ ok {serr} == ""
93
+ end
94
+ end
95
+
96
+ spec "[!saisg] returns 0 as status code when succeeded." do |app|
97
+ [
98
+ #["foo", "bar"],
99
+ [],
100
+ ].each do |args|
101
+ sout, serr = Dummy.new.stdouterr do
102
+ status = app.run(args)
103
+ ok {status} == 0
104
+ ok {args} == [] # ["foo", "bar"]
105
+ end
106
+ ok {sout}.NOT == ""
107
+ ok {serr} == ""
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+
114
+ topic '.main()' do
115
+
116
+ spec "[!cy0yo] uses ARGV when args is not passed." do
117
+ bkup = ARGV.dup
118
+ ARGV[0..-1] = ["-h", "-v", "foo", "bar"]
119
+ Dummy.new.stdouterr do
120
+ klass.main()
121
+ ok {ARGV} == ["foo", "bar"]
122
+ end
123
+ ARGV[0..-1] = bkup
124
+ end
125
+
126
+ spec "[!t0udo] returns status code (0: ok, 1: error)." do
127
+ Dummy.new.stdouterr do
128
+ status = klass.main(["-hv"])
129
+ ok {status} == 0
130
+ status = klass.main(["-hx"])
131
+ ok {status} == 1
132
+ end
133
+ end
134
+
135
+ spec "[!maomq] command-option error is cached and not raised." do
136
+ Dummy.new.stdouterr do
137
+ pr = proc { klass.main(["-hx"]) }
138
+ ok {pr}.NOT.raise?(Migr8::Util::CommandOptionError)
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+
145
+ end
146
+
147
+
148
+ end
@@ -0,0 +1,261 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'oktest'
4
+ require 'migr8'
5
+ require 'stringio'
6
+
7
+
8
+ Oktest.scope do
9
+
10
+
11
+ topic Migr8::Migration do
12
+
13
+ klass = Migr8::Migration
14
+
15
+
16
+ topic '#initalize()' do
17
+
18
+ spec "[!y4dy3] takes version, author, and desc arguments." do
19
+ mig = klass.new('abcd1234', 'user1', 'desc1')
20
+ ok {mig.version} == 'abcd1234'
21
+ ok {mig.author} == 'user1'
22
+ ok {mig.desc} == 'desc1'
23
+ end
24
+
25
+ end
26
+
27
+
28
+ topic '#applied?' do
29
+
30
+ spec "[!ebzct] returns false when @applied_at is nil, else true." do
31
+ mig = klass.new()
32
+ ok {mig.applied?} == false
33
+ mig.applied_at = '2013-01-01 12:34:56'
34
+ ok {mig.applied?} == true
35
+ end
36
+
37
+ end
38
+
39
+
40
+ topic '#up_script' do
41
+
42
+ spec "[!200k7] returns @up_script if it is set." do
43
+ mig = klass.new()
44
+ mig.up_script = "xxx"
45
+ ok {mig.up_script} == "xxx"
46
+ end
47
+
48
+ spec "[!cfp34] returns nil when 'up' is not set." do
49
+ mig = klass.new()
50
+ mig.up = nil
51
+ ok {mig.up_script} == nil
52
+ end
53
+
54
+ spec "[!6gaxb] returns 'up' string expanding vars in it." do
55
+ original = <<END
56
+ create table ${table} (
57
+ id serial primary key;
58
+ name varchar(255) not null;
59
+ );
60
+ create index ${table}_${column}_idx on ${table}(${column});
61
+ END
62
+ expanded = <<END
63
+ create table sample1 (
64
+ id serial primary key;
65
+ name varchar(255) not null;
66
+ );
67
+ create index sample1_name_idx on sample1(name);
68
+ END
69
+ mig = klass.new
70
+ mig.up = original
71
+ mig.vars = {'table'=>'sample1', 'column'=>'name'}
72
+ ok {mig.up_script} == expanded
73
+ end
74
+
75
+ spec "[!jeomg] renders 'up' script as eRuby template." do
76
+ original = <<END
77
+ insert into ${table}(${column}) values
78
+ <% comma = " " %>
79
+ <% for name in %w[Haruhi Mikuru Yuki] %>
80
+ <%= comma %>('<%= name %>')
81
+ <% comma = ", " %>
82
+ <% end %>
83
+ ;
84
+ END
85
+ expanded = <<END
86
+ insert into users(name) values
87
+ ('Haruhi')
88
+ , ('Mikuru')
89
+ , ('Yuki')
90
+ ;
91
+ END
92
+ mig = klass.new
93
+ mig.up = original
94
+ mig.vars = {'table'=>'users', 'column'=>'name'}
95
+ ok {mig.up_script} == expanded
96
+ end
97
+
98
+ end
99
+
100
+
101
+ topic '#down_script' do
102
+
103
+ spec "[!27n2l] returns @down_script if it is set." do
104
+ mig = klass.new
105
+ mig.down_script = "xxx"
106
+ ok {mig.down_script} == "xxx"
107
+ end
108
+
109
+ spec "[!e45s1] returns nil when 'down' is not set." do
110
+ mig = klass.new
111
+ mig.down = nil
112
+ ok {mig.down_script} == nil
113
+ end
114
+
115
+ spec "[!0q3nq] returns 'down' string expanding vars in it." do
116
+ original = <<END
117
+ drop index ${table}_${column}_idx;
118
+ drop table ${table};
119
+ END
120
+ expanded = <<END
121
+ drop index sample1_name_idx;
122
+ drop table sample1;
123
+ END
124
+ mig = klass.new
125
+ mig.down = original
126
+ mig.vars = {'table'=>'sample1', 'column'=>'name'};
127
+ ok {mig.down_script} == expanded
128
+ end
129
+
130
+ spec "[!kpwut] renders 'up' script as eRuby template." do
131
+ original = <<END
132
+ <% for name in %w[Haruhi Mikuru Yuki] %>
133
+ delete from ${table} where ${column} = '<%= name %>';
134
+ <% end %>
135
+ END
136
+ expanded = <<END
137
+ delete from users where name = 'Haruhi';
138
+ delete from users where name = 'Mikuru';
139
+ delete from users where name = 'Yuki';
140
+ END
141
+ mig = klass.new
142
+ mig.down = original
143
+ mig.vars = {'table'=>'users', 'column'=>'name'}
144
+ ok {mig.down_script} == expanded
145
+ end
146
+
147
+ end
148
+
149
+
150
+ topic '#_render()' do
151
+
152
+ spec "[!1w3ov] renders string with 'vars' as context variables." do
153
+ mig = klass.new
154
+ mig.vars = {'table'=>'users', 'columns'=>['id', 'name']}
155
+ src = "@table: <%=@table.inspect%>; @columns: <%=@columns.inspect%>;"
156
+ output = mig.__send__(:_render, src)
157
+ ok {output} == '@table: "users"; @columns: ["id", "name"];'
158
+ end
159
+
160
+ end
161
+
162
+
163
+ topic '#applied_at_or()' do
164
+
165
+ spec "[!zazux] returns default arugment when not applied." do
166
+ mig = klass.new
167
+ ok {mig.applied?} == false
168
+ ok {mig.applied_at_or('(not applied)')} == '(not applied)'
169
+ end
170
+
171
+ spec "[!fxb4y] returns @applied_at without msec." do
172
+ mig = klass.new
173
+ mig.applied_at = '2013-01-01 12:34:56.789'
174
+ ok {mig.applied?} == true
175
+ ok {mig.applied_at_or('(not applied)')} == '2013-01-01 12:34:56'
176
+ end
177
+
178
+ end
179
+
180
+
181
+ topic '#filepath' do
182
+
183
+ spec "[!l9t5k] returns nil when version is not set." do
184
+ mig = klass.new
185
+ mig.version = nil
186
+ ok {mig.filepath} == nil
187
+ end
188
+
189
+ spec "[!p0d9q] returns filepath of migration file." do
190
+ mig = klass.new
191
+ mig.version = 'abcd1234'
192
+ ok {mig.filepath} == 'migr8/migrations/abcd1234.yaml'
193
+ end
194
+
195
+ end
196
+
197
+
198
+ topic '.load_from()' do
199
+
200
+ fixture :mig_filepath do
201
+ content = <<END
202
+ # -*- coding: utf-8 -*-
203
+ version: wxyz7890
204
+ author: haruhi
205
+ desc: test migration '#1'
206
+ vars:
207
+ - table: members
208
+ - column: name
209
+ - index: ${table}_${column}_idx
210
+
211
+ up: |
212
+ cteate table ${table}(
213
+ id serial primary key,
214
+ name varchar(255) not null
215
+ );
216
+ create index ${index} on ${table}(${name});
217
+
218
+ down: |
219
+ drop table ${table};
220
+ END
221
+ fpath = "tmp.#{rand().to_s[2..5]}.yaml"
222
+ File.open(fpath, 'w') {|f| f.write(content) }
223
+ at_end { File.unlink(fpath) }
224
+ fpath
225
+ end
226
+
227
+ spec "[!fbea5] loads data from file and returns migration object." do |mig_filepath|
228
+ mig = klass.load_from(mig_filepath)
229
+ ok {mig}.is_a?(Migr8::Migration)
230
+ ok {mig.version} == 'wxyz7890'
231
+ ok {mig.author} == 'haruhi'
232
+ ok {mig.desc} == 'test migration \'#1\''
233
+ end
234
+
235
+ spec "[!sv21s] expands values of 'vars'." do |mig_filepath|
236
+ mig = klass.load_from(mig_filepath)
237
+ ok {mig.vars} == {'table'=>'members', 'column'=>'name',
238
+ 'index'=>'members_name_idx'}
239
+ end
240
+
241
+ spec "[!32ns3] not expand both 'up' and 'down'." do |mig_filepath|
242
+ mig = klass.load_from(mig_filepath)
243
+ ok {mig.up} == <<END
244
+ cteate table ${table}(
245
+ id serial primary key,
246
+ name varchar(255) not null
247
+ );
248
+ create index ${index} on ${table}(${name});
249
+ END
250
+ ok {mig.down} == <<END
251
+ drop table ${table};
252
+ END
253
+ end
254
+
255
+ end
256
+
257
+
258
+ end
259
+
260
+
261
+ end
@@ -0,0 +1,873 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'oktest'
4
+ require 'migr8'
5
+
6
+
7
+ Oktest.scope do
8
+
9
+
10
+ topic Migr8::Util::CommandOptionDefinition do
11
+
12
+ klass = Migr8::Util::CommandOptionDefinition
13
+ errclass = Migr8::Util::CommandOptionDefinitionError
14
+
15
+
16
+ topic '.new()' do
17
+
18
+ spec "parses definition string of short and long options without arg." do
19
+ [
20
+ "-h, --help: show help",
21
+ "-h,--help: show help",
22
+ " -h, --help : show help ",
23
+ ].each do |defstr|
24
+ x = klass.new(defstr)
25
+ ok {x.short} == 'h'
26
+ ok {x.long} == 'help'
27
+ ok {x.name} == 'help'
28
+ ok {x.arg} == nil
29
+ ok {x.desc} == 'show help'
30
+ ok {x.arg_required} == false
31
+ end
32
+ end
33
+
34
+ spec "parses definition string of short and long options with required arg." do
35
+ [
36
+ "-a, --action=name: action name.",
37
+ "-a,--action=name: action name.",
38
+ " -a, --action=name : action name. ",
39
+ ].each do |defstr|
40
+ x = klass.new(defstr)
41
+ ok {x.short} == 'a'
42
+ ok {x.long} == 'action'
43
+ ok {x.name} == 'action'
44
+ ok {x.arg} == 'name'
45
+ ok {x.desc} == 'action name.'
46
+ ok {x.arg_required} == true
47
+ end
48
+ end
49
+
50
+ spec "parses definition string of short and long options with optional arg." do
51
+ [
52
+ "-i, --indent[=N]: indent depth (default 2).",
53
+ "-i,--indent[=N]: indent depth (default 2).",
54
+ " -i, --indent[=N] : indent depth (default 2). ",
55
+ ].each do |defstr|
56
+ x = klass.new(defstr)
57
+ ok {x.short} == 'i'
58
+ ok {x.long} == 'indent'
59
+ ok {x.name} == 'indent'
60
+ ok {x.arg} == 'N'
61
+ ok {x.desc} == 'indent depth (default 2).'
62
+ ok {x.arg_required} == nil
63
+ end
64
+ end
65
+
66
+ spec "parses definition string of short-only options without arg." do
67
+ [
68
+ "-q: be quiet",
69
+ " -q : be quiet ",
70
+ ].each do |defstr|
71
+ x = klass.new(defstr)
72
+ ok {x.short} == 'q'
73
+ ok {x.long} == nil
74
+ ok {x.name} == 'q'
75
+ ok {x.arg} == nil
76
+ ok {x.desc} == 'be quiet'
77
+ ok {x.arg_required} == false
78
+ end
79
+ end
80
+
81
+ spec "parses definition string of short-only options with required arg." do
82
+ [
83
+ "-a name: action name.",
84
+ " -a name : action name. ",
85
+ ].each do |defstr|
86
+ x = klass.new(defstr)
87
+ ok {x.short} == 'a'
88
+ ok {x.long} == nil
89
+ ok {x.name} == 'a'
90
+ ok {x.arg} == 'name'
91
+ ok {x.desc} == 'action name.'
92
+ ok {x.arg_required} == true
93
+ end
94
+ end
95
+
96
+ spec "parses definition string of short-only options with optional arg." do
97
+ [
98
+ "-i[N]: indent depth (default 2).",
99
+ " -i[N] : indent depth (default 2). ",
100
+ ].each do |defstr|
101
+ x = klass.new(defstr)
102
+ ok {x.short} == 'i'
103
+ ok {x.long} == nil
104
+ ok {x.name} == 'i'
105
+ ok {x.arg} == 'N'
106
+ ok {x.desc} == 'indent depth (default 2).'
107
+ ok {x.arg_required} == nil
108
+ end
109
+ end
110
+
111
+ spec "parses definition string of long-only options without arg." do
112
+ [
113
+ "--verbose: be verbose",
114
+ " --verbose : be verbose ",
115
+ ].each do |defstr|
116
+ x = klass.new(defstr)
117
+ ok {x.short} == nil
118
+ ok {x.long} == 'verbose'
119
+ ok {x.name} == 'verbose'
120
+ ok {x.arg} == nil
121
+ ok {x.desc} == 'be verbose'
122
+ ok {x.arg_required} == false
123
+ end
124
+ end
125
+
126
+ spec "parses definition string of long-only options with required arg." do
127
+ [
128
+ "--action=name: action name.",
129
+ " --action=name : action name. ",
130
+ ].each do |defstr|
131
+ x = klass.new(defstr)
132
+ ok {x.short} == nil
133
+ ok {x.long} == 'action'
134
+ ok {x.name} == 'action'
135
+ ok {x.arg} == 'name'
136
+ ok {x.desc} == 'action name.'
137
+ ok {x.arg_required} == true
138
+ end
139
+ end
140
+
141
+ spec "parses definition string of long-only options with optional arg." do
142
+ [
143
+ "--indent[=N]: indent depth (default 2).",
144
+ " --indent[=N] : indent depth (default 2). ",
145
+ ].each do |defstr|
146
+ x = klass.new(defstr)
147
+ ok {x.short} == nil
148
+ ok {x.long} == 'indent'
149
+ ok {x.name} == 'indent'
150
+ ok {x.arg} == 'N'
151
+ ok {x.desc} == 'indent depth (default 2).'
152
+ ok {x.arg_required} == nil
153
+ end
154
+ end
155
+
156
+ spec "detects '#name' notation to override option name." do
157
+ #
158
+ [
159
+ "-h #usage : show usage.",
160
+ ].each do |defstr|
161
+ x = klass.new(defstr)
162
+ ok {x.short} == 'h'
163
+ ok {x.long} == nil
164
+ ok {x.name} == 'usage'
165
+ ok {x.arg} == nil
166
+ ok {x.desc} == 'show usage.'
167
+ ok {x.arg_required} == false
168
+ end
169
+ #
170
+ [
171
+ "-h, --help #usage : show usage.",
172
+ ].each do |defstr|
173
+ x = klass.new(defstr)
174
+ ok {x.short} == 'h'
175
+ ok {x.long} == 'help'
176
+ ok {x.name} == 'usage'
177
+ ok {x.arg} == nil
178
+ ok {x.desc} == 'show usage.'
179
+ ok {x.arg_required} == false
180
+ end
181
+ #
182
+ [
183
+ "--help #usage : show usage.",
184
+ ].each do |defstr|
185
+ x = klass.new(defstr)
186
+ ok {x.short} == nil
187
+ ok {x.long} == 'help'
188
+ ok {x.name} == 'usage'
189
+ ok {x.arg} == nil
190
+ ok {x.desc} == 'show usage.'
191
+ ok {x.arg_required} == false
192
+ end
193
+ #
194
+ [
195
+ "-a name #command : action name.",
196
+ ].each do |defstr|
197
+ x = klass.new(defstr)
198
+ ok {x.short} == 'a'
199
+ ok {x.long} == nil
200
+ ok {x.name} == 'command'
201
+ ok {x.arg} == 'name'
202
+ ok {x.desc} == 'action name.'
203
+ ok {x.arg_required} == true
204
+ end
205
+ #
206
+ [
207
+ "--action=name #command : action name.",
208
+ ].each do |defstr|
209
+ x = klass.new(defstr)
210
+ ok {x.short} == nil
211
+ ok {x.long} == 'action'
212
+ ok {x.name} == 'command'
213
+ ok {x.arg} == 'name'
214
+ ok {x.desc} == 'action name.'
215
+ ok {x.arg_required} == true
216
+ end
217
+ #
218
+ [
219
+ "-i, --indent[=N] #width : indent width (default 2).",
220
+ ].each do |defstr|
221
+ x = klass.new(defstr)
222
+ ok {x.short} == 'i'
223
+ ok {x.long} == 'indent'
224
+ ok {x.name} == 'width'
225
+ ok {x.arg} == 'N'
226
+ ok {x.desc} == 'indent width (default 2).'
227
+ ok {x.arg_required} == nil
228
+ end
229
+ #
230
+ [
231
+ "-i[N] #width : indent width (default 2).",
232
+ ].each do |defstr|
233
+ x = klass.new(defstr)
234
+ ok {x.short} == 'i'
235
+ ok {x.long} == nil
236
+ ok {x.name} == 'width'
237
+ ok {x.arg} == 'N'
238
+ ok {x.desc} == 'indent width (default 2).'
239
+ ok {x.arg_required} == nil
240
+ end
241
+ #
242
+ [
243
+ "--indent[=N] #width : indent width (default 2).",
244
+ ].each do |defstr|
245
+ x = klass.new(defstr)
246
+ ok {x.short} == nil
247
+ ok {x.long} == 'indent'
248
+ ok {x.name} == 'width'
249
+ ok {x.arg} == 'N'
250
+ ok {x.desc} == 'indent width (default 2).'
251
+ ok {x.arg_required} == nil
252
+ end
253
+ end
254
+
255
+ spec "raises error when failed to parse definition string." do
256
+ pr = proc { klass.new("-h, --help:show help") }
257
+ ok {pr}.raise?(errclass, "'-h, --help:show help': invalid definition.")
258
+ end
259
+
260
+ end
261
+
262
+
263
+ topic '#usage()' do
264
+
265
+ spec "[!xd9do] returns option usage with specified width." do
266
+ optdef = klass.new("-h, --help: show help")
267
+ ok {optdef.usage(15)} == "-h, --help : show help"
268
+ ok {optdef.usage()} == "-h, --help : show help"
269
+ optdef = klass.new("-h: show help")
270
+ ok {optdef.usage()} == "-h : show help"
271
+ optdef = klass.new("--help: show help")
272
+ ok {optdef.usage()} == "--help : show help"
273
+ #
274
+ optdef = klass.new("-a, --action=name: action name")
275
+ ok {optdef.usage(15)} == "-a, --action=name: action name"
276
+ ok {optdef.usage()} == "-a, --action=name : action name"
277
+ optdef = klass.new("-a name: action name")
278
+ ok {optdef.usage()} == "-a name : action name"
279
+ optdef = klass.new("--action=name: action name")
280
+ ok {optdef.usage()} == "--action=name : action name"
281
+ #
282
+ optdef = klass.new("-i, --indent[=N]: indent width")
283
+ ok {optdef.usage(15)} == "-i, --indent[=N]: indent width"
284
+ ok {optdef.usage()} == "-i, --indent[=N] : indent width"
285
+ optdef = klass.new("-i[N]: indent width")
286
+ ok {optdef.usage()} == "-i[N] : indent width"
287
+ optdef = klass.new("--indent[=N]: indent width")
288
+ ok {optdef.usage()} == "--indent[=N] : indent width"
289
+ end
290
+
291
+ end
292
+
293
+ end
294
+
295
+
296
+ topic Migr8::Util::CommandOptionParser do
297
+
298
+ klass = Migr8::Util::CommandOptionParser
299
+ errclass = Migr8::Util::CommandOptionError
300
+
301
+
302
+ topic '#add()' do
303
+
304
+ spec "[!tm89j] parses definition string and adds optdef object." do
305
+ parser = klass.new
306
+ parser.add("-h, --help: show help")
307
+ parser.add("-a, --action=name: action name.")
308
+ ok {parser.optdefs.length} == 2
309
+ ok {parser.optdefs[0].short} == 'h'
310
+ ok {parser.optdefs[0].long} == 'help'
311
+ ok {parser.optdefs[0].arg} == nil
312
+ ok {parser.optdefs[1].short} == 'a'
313
+ ok {parser.optdefs[1].long} == 'action'
314
+ ok {parser.optdefs[1].arg} == 'name'
315
+ end
316
+
317
+ spec "[!00kvl] returns self." do
318
+ parser = klass.new
319
+ ret = parser.add("-h, --help: show help")
320
+ ok {ret}.same?(parser)
321
+ end
322
+
323
+ end
324
+
325
+
326
+ topic '#parse()' do
327
+
328
+ fixture :parser do
329
+ parser = klass.new
330
+ parser.add("-h, --help : show help")
331
+ parser.add("-V #version : show version")
332
+ parser.add("-a, --action=name : action name")
333
+ parser.add("-i, --indent[=N] : indent width (default N=2)")
334
+ parser
335
+ end
336
+
337
+ spec "returns options parsed." do |parser|
338
+ args = "-hVi4 -a print foo bar".split(' ')
339
+ options = parser.parse(args)
340
+ ok {options} == {'help'=>true, 'version'=>true, 'indent'=>4, 'action'=>'print'}
341
+ ok {args} == ["foo", "bar"]
342
+ end
343
+
344
+ spec "parses short options." do |parser|
345
+ # short options
346
+ args = "-hVi4 -a print foo bar".split(' ')
347
+ options = parser.parse(args)
348
+ ok {options} == {'help'=>true, 'version'=>true, 'indent'=>4, 'action'=>'print'}
349
+ ok {args} == ["foo", "bar"]
350
+ #
351
+ args = "-hi foo bar".split(' ')
352
+ options = parser.parse(args)
353
+ ok {options} == {'help'=>true, 'indent'=>true}
354
+ ok {args} == ["foo", "bar"]
355
+ end
356
+
357
+ spec "parses long options." do |parser|
358
+ # long options
359
+ args = "--help --action=print --indent=4 foo bar".split(' ')
360
+ options = parser.parse(args)
361
+ ok {options} == {'help'=>true, 'indent'=>4, 'action'=>'print'}
362
+ ok {args} == ["foo", "bar"]
363
+ #
364
+ args = "--indent foo bar".split(' ')
365
+ options = parser.parse(args)
366
+ ok {options} == {'indent'=>true}
367
+ ok {args} == ["foo", "bar"]
368
+ end
369
+
370
+ spec "[!2jo9d] stops to parse options when '--' found." do |parser|
371
+ args = ["-h", "--", "-V", "--action=print", "foo", "bar"]
372
+ options = parser.parse(args)
373
+ ok {options} == {'help'=>true}
374
+ ok {args} == ["-V", "--action=print", "foo", "bar"]
375
+ end
376
+
377
+ spec "[!7pa2x] raises error when invalid long option." do |parser|
378
+ pr1 = proc { parser.parse(["--help?", "aaa"]) }
379
+ ok {pr1}.raise?(errclass, "--help?: invalid option format.")
380
+ pr2 = proc { parser.parse(["---help", "aaa"]) }
381
+ ok {pr2}.raise?(errclass, "---help: invalid option format.")
382
+ end
383
+
384
+ spec "[!sj0cv] raises error when unknown long option." do |parser|
385
+ pr = proc { parser.parse(["--foobar", "aaa"]) }
386
+ ok {pr}.raise?(errclass, "--foobar: unknown option.")
387
+ end
388
+
389
+ spec "[!a7qxw] raises error when argument required but not provided." do |parser|
390
+ pr = proc { parser.parse(["--action", "foo", "bar"]) }
391
+ ok {pr}.raise?(errclass, "--action: argument required.")
392
+ end
393
+
394
+ spec "[!8eu9s] raises error when option takes no argument but provided." do |parser|
395
+ pr = proc { parser.parse(["--help=true", "foo", "bar"]) }
396
+ ok {pr}.raise?(errclass, "--help=true: unexpected argument.")
397
+ end
398
+
399
+ case_when "[!1l2dn] when argname is 'N'..." do
400
+
401
+ spec "[!cfjp3] raises error when argval is not an integer." do |parser|
402
+ parser.add("-n, --num=N: number")
403
+ pr = proc { parser.parse(["--num=314"]) }
404
+ ok {pr}.NOT.raise?(Exception)
405
+ pr = proc { parser.parse(["--num=3.14"]) }
406
+ ok {pr}.raise?(errclass, "--num=3.14: integer expected.")
407
+ #
408
+ pr = proc { parser.parse(["--indent=4"]) }
409
+ ok {pr}.NOT.raise?(Exception)
410
+ pr = proc { parser.parse(["--indent=4i"]) }
411
+ ok {pr}.raise?(errclass, "--indent=4i: integer expected.")
412
+ end
413
+
414
+ spec "[!18p1g] raises error when argval <= 0." do |parser|
415
+ parser.add("-n, --num=N: number")
416
+ opts = parser.parse(["--num=9"])
417
+ ok {opts['num']} == 9
418
+ #
419
+ pr = proc { parser.parse(["--num=-9"]) }
420
+ ok {pr}.raise?(errclass, "--num=-9: positive value expected.")
421
+ pr = proc { parser.parse(["--num=0"]) }
422
+ ok {pr}.raise?(errclass, "--num=0: positive value expected.")
423
+ end
424
+
425
+ end
426
+
427
+ spec "[!dtbdd] uses option name instead of long name when option name specified." do |parser|
428
+ pr = proc { parser.parse(["--help=true", "foo", "bar"]) }
429
+ ok {pr}.raise?(errclass, "--help=true: unexpected argument.")
430
+ end
431
+
432
+ spec "[!7mp75] sets true as value when argument is not provided." do |parser|
433
+ args = ["--help", "foo", "bar"]
434
+ options = parser.parse(args)
435
+ ok {options} == {'help'=>true}
436
+ ok {args} == ["foo", "bar"]
437
+ end
438
+
439
+ spec "[!8aaj0] raises error when unknown short option provided." do |parser|
440
+ args = ["-hxV", "foo"]
441
+ pr = proc { parser.parse(args) }
442
+ ok {pr}.raise?(errclass, "-x: unknown option.")
443
+ end
444
+
445
+ case_when "[!mnwxw] when short option takes no argument..." do
446
+
447
+ spec "[!8atm1] sets true as value." do |parser|
448
+ args = ["-hV", "foo", "bar"]
449
+ options = parser.parse(args)
450
+ ok {options} == {'help'=>true, 'version'=>true}
451
+ ok {args} == ["foo", "bar"]
452
+ end
453
+
454
+ end
455
+
456
+ case_when "[!l5mee] when short option takes required argument..." do
457
+
458
+ spec "[!crvxx] uses following string as argument." do |parser|
459
+ [
460
+ ["-aprint", "foo", "bar"],
461
+ ["-a", "print", "foo", "bar"],
462
+ ].each do |args|
463
+ options = parser.parse(args)
464
+ ok {options} == {'action'=>'print'}
465
+ ok {args} == ["foo", "bar"]
466
+ end
467
+ end
468
+
469
+ spec "[!7t6l3] raises error when no argument provided." do |parser|
470
+ pr = proc { parser.parse(["-a"]) }
471
+ ok {pr}.raise?(errclass, "-a: argument required.")
472
+ end
473
+
474
+ case_when "[!h3gt8] when argname is 'N'..." do
475
+
476
+ spec "[!yzr2p] argument must be an integer." do |parser|
477
+ parser.add("-n, --num=N: number.")
478
+ pr = proc { parser.parse(["-n", "314", "hoo"]) }
479
+ ok {pr}.NOT.raise?(Exception)
480
+ #
481
+ pr = proc { opts = parser.parse(["-n", "3.14", "hoo"]) }
482
+ ok {pr}.raise?(errclass, "-n 3.14: integer expected.")
483
+ end
484
+
485
+ spec "[!mcwu7] argument must be positive value." do |parser|
486
+ parser.add("-n, --num=N: number.")
487
+ opts = parser.parse(["-n", "9"])
488
+ ok {opts['num']} == 9
489
+ #
490
+ pr = proc { parser.parse(["-n", "-9"]) }
491
+ ok {pr}.raise?(errclass, "-n -9: positive value expected.")
492
+ pr = proc { parser.parse(["-n", "0"]) }
493
+ ok {pr}.raise?(errclass, "-n 0: positive value expected.")
494
+ end
495
+
496
+ end
497
+
498
+ end
499
+
500
+ case_when "[!pl97z] when short option takes optional argument..." do
501
+
502
+ spec "[!4k3zy] uses following string as argument if provided." do |parser|
503
+ args = ["-hi4", "foo", "bar"]
504
+ options = parser.parse(args)
505
+ ok {options} == {'help'=>true, 'indent'=>4}
506
+ ok {args} == ["foo", "bar"]
507
+ end
508
+
509
+ spec "[!9k2ip] uses true as argument value if not provided." do |parser|
510
+ args = ["-hi", "foo", "bar"]
511
+ options = parser.parse(args)
512
+ ok {options} == {'help'=>true, 'indent'=>true}
513
+ ok {args} == ["foo", "bar"]
514
+ end
515
+
516
+ case_when "[!lk761] when argname is 'N'..." do
517
+
518
+ spec "[!6oy04] argument must be an integer." do |parser|
519
+ parser.add("-n, --num[=N]: number.")
520
+ pr = proc { parser.parse(["-n314", "hoo"]) }
521
+ ok {pr}.NOT.raise?(Exception)
522
+ #
523
+ pr = proc { parser.parse(["-n3.14", "hoo"]) }
524
+ ok {pr}.raise?(errclass, "-n3.14: integer expected.")
525
+ end
526
+
527
+ spec "[!nc3av] argument must be positive value." do |parser|
528
+ parser.add("-n, --num[=N]: number")
529
+ opts = parser.parse(["-n9"])
530
+ ok {opts['num']} == 9
531
+ #
532
+ pr = proc { opts = parser.parse(["-n-9"]) }
533
+ ok {pr}.raise?(errclass, "-n-9: positive value expected.")
534
+ pr = proc { opts = parser.parse(["-n0"]) }
535
+ ok {pr}.raise?(errclass, "-n0: positive value expected.")
536
+ end
537
+
538
+ end
539
+
540
+ end
541
+
542
+ spec "[!35eof] returns parsed options." do |parser|
543
+ [
544
+ ["-hVi4", "--action=print", "foo", "bar"],
545
+ ["--indent=4", "-hVa", "print", "foo", "bar"],
546
+ ].each do |args|
547
+ options = parser.parse(args)
548
+ ok {options} == {'help'=>true, 'version'=>true, 'indent'=>4, 'action'=>'print'}
549
+ ok {args} == ["foo", "bar"]
550
+ end
551
+ end
552
+
553
+ end
554
+
555
+
556
+ topic '#usage()' do
557
+
558
+ spec "[!w9v9c] returns usage string of all options." do
559
+ parser = klass.new
560
+ parser.add("-h, --help : show help")
561
+ parser.add("-V #version : show version")
562
+ parser.add("-a, --action=name : action name")
563
+ parser.add("-i, --indent[=N] : indent width (default N=2)")
564
+ #
565
+ expected = <<'END'
566
+ -h, --help : show help
567
+ -V : show version
568
+ -a, --action=name : action name
569
+ -i, --indent[=N] : indent width (default N=2)
570
+ END
571
+ ok {parser.usage()} == expected
572
+ #
573
+ expected = <<'END'
574
+ -h, --help : show help
575
+ -V : show version
576
+ -a, --action=name: action name
577
+ -i, --indent[=N]: indent width (default N=2)
578
+ END
579
+ ok {parser.usage(16)} == expected
580
+ end
581
+
582
+ spec "[!i0uvr] adds indent when specified." do
583
+ parser = klass.new
584
+ parser.add("-h, --help : show help")
585
+ parser.add("--quiet : be quiet")
586
+ #
587
+ expected = <<'END'
588
+ -h, --help : show help
589
+ --quiet : be quiet
590
+ END
591
+ ok {parser.usage(nil, ' ')} == expected
592
+ end
593
+
594
+ spec "[!lbjai] skips options when desc is empty." do
595
+ parser = klass.new
596
+ parser.add("-h, --help : show help")
597
+ parser.add("-D, --debug[=N] : ")
598
+ parser.add("--quiet : be quiet")
599
+ #
600
+ expected = <<'END'
601
+ -h, --help : show help
602
+ --quiet : be quiet
603
+ END
604
+ ok {parser.usage()} == expected
605
+ end
606
+
607
+
608
+ end
609
+
610
+
611
+ end
612
+
613
+
614
+ topic Migr8::Util::Template do
615
+
616
+ tmpl_class = Migr8::Util::Template
617
+
618
+
619
+ topic '#initialize()' do
620
+
621
+ spec "[!6z4kp] converts input string into ruby code." do
622
+ input = <<'END'
623
+ <p>
624
+ <% x = 10 %>
625
+ <%= x %>
626
+ <%== x %>
627
+ </p>
628
+ END
629
+ expected = <<'END'
630
+ _buf = ''; _buf << %q`<p>
631
+ `; x = 10
632
+ ; _buf << %q` `; _buf << (escape( x )).to_s; _buf << %q`
633
+ `; _buf << %q` `; _buf << ( x ).to_s; _buf << %q`
634
+ `; _buf << %q`</p>
635
+ `;
636
+ _buf.to_s
637
+ END
638
+ tmpl = tmpl_class.new(input)
639
+ ok {tmpl.src} == expected
640
+ end
641
+
642
+ end
643
+
644
+ topic '#convert()' do
645
+
646
+ spec "[!118pw] converts template string into ruby code." do
647
+ input = <<'END'
648
+ <div>
649
+ <% for i in 1..3 do %>
650
+ <%== i %>
651
+ <% end %>
652
+ </div>
653
+ END
654
+ expected = <<'END'
655
+ _buf = ''; _buf << %q`<div>
656
+ `; for i in 1..3 do
657
+ ; _buf << %q` `; _buf << ( i ).to_s; _buf << %q`
658
+ `; end
659
+ ; _buf << %q`</div>
660
+ `;
661
+ _buf.to_s
662
+ END
663
+ tmpl = tmpl_class.new()
664
+ actual = tmpl.convert(input)
665
+ ok {actual} == expected
666
+ end
667
+
668
+ spec "[!7ht59] escapes '`' and '\\' characters." do
669
+ input = <<'END'
670
+ output = `ls`
671
+ newline = "\n"
672
+ END
673
+ expected = <<'END'
674
+ _buf = ''; _buf << %q`output = \`ls\`
675
+ newline = "\\n"
676
+ `;
677
+ _buf.to_s
678
+ END
679
+ tmpl = tmpl_class.new()
680
+ actual = tmpl.convert(input)
681
+ ok {actual} == expected
682
+ end
683
+
684
+ spec "[!u93y5] wraps expression by 'escape()' when <%= %>." do
685
+ input = "val=<%= val %>"
686
+ expected = <<'END'
687
+ _buf = ''; _buf << %q`val=`; _buf << (escape( val )).to_s;
688
+ _buf.to_s
689
+ END
690
+ tmpl = tmpl_class.new()
691
+ ok {tmpl.convert(input)} == expected
692
+ end
693
+
694
+ spec "[!auj95] leave expression as it is when <%== %>." do
695
+ input = "val=<%== val %>"
696
+ expected = <<'END'
697
+ _buf = ''; _buf << %q`val=`; _buf << ( val ).to_s;
698
+ _buf.to_s
699
+ END
700
+ tmpl = tmpl_class.new()
701
+ ok {tmpl.convert(input)} == expected
702
+ end
703
+
704
+ spec "[!b10ns] generates ruby code correctly even when no embedded code." do
705
+ input = <<'END'
706
+ create table (users) (
707
+ id serial primary key,
708
+ name varchar(255) not null
709
+ );
710
+ END
711
+ expected = <<'END'
712
+ _buf = ''; _buf << %q`create table (users) (
713
+ id serial primary key,
714
+ name varchar(255) not null
715
+ );
716
+ `;
717
+ _buf.to_s
718
+ END
719
+ tmpl = tmpl_class.new()
720
+ actual = tmpl.convert(input)
721
+ ok {actual} == expected
722
+ end
723
+
724
+ end
725
+
726
+
727
+ topic '#render()' do
728
+
729
+ spec "[!48pfc] returns rendered string." do
730
+ input = <<'END'
731
+ <p>
732
+ <% for item in ['Haruhi', 'Mikuru', 'Yuki'] %>
733
+ <%= item %>
734
+ <% end %>
735
+ </p>
736
+ END
737
+ expected = <<'END'
738
+ <p>
739
+ Haruhi
740
+ Mikuru
741
+ Yuki
742
+ </p>
743
+ END
744
+ tmpl = tmpl_class.new(input)
745
+ actual = tmpl.render()
746
+ ok {actual} == expected
747
+ end
748
+
749
+ spec "[!umsfx] takes hash object as context variables." do
750
+ input = <<'END'
751
+ <p>
752
+ <% for item in @members %>
753
+ <%= item %>
754
+ <% end %>
755
+ </p>
756
+ END
757
+ expected = <<'END'
758
+ <p>
759
+ Haruhi
760
+ Mikuru
761
+ Yuki
762
+ </p>
763
+ END
764
+ context = {
765
+ :members=>['Haruhi', 'Mikuru', 'Yuki'],
766
+ }
767
+ tmpl = tmpl_class.new(input)
768
+ actual = tmpl.render(context)
769
+ ok {actual} == expected
770
+ end
771
+
772
+ spec "[!p0po0] context argument can be null." do
773
+ input = "Hello"
774
+ context = nil
775
+ tmpl = tmpl_class.new(input)
776
+ actual = nil
777
+ pr = proc { actual = tmpl.render(nil) }
778
+ ok {pr}.NOT.raise?()
779
+ ok {actual} == "Hello"
780
+ end
781
+
782
+ spec "[!1i0v8] escapes \"'\" into \"''\" when '<%= %>', and not when '<%== %>'." do
783
+ input = <<'END'
784
+ <% for item in @items %>
785
+ <%= item %>
786
+ <%== item %>
787
+ <% end %>
788
+ END
789
+ expected = <<'END'
790
+ Rock''n Roll
791
+ Rock'n Roll
792
+ xx''yy''''zz''''''
793
+ xx'yy''zz'''
794
+ END
795
+ items = ["Rock'n Roll", "xx'yy''zz'''"]
796
+ tmpl = tmpl_class.new(input)
797
+ ok {tmpl.render({:items=>items})} == expected
798
+ end
799
+
800
+ end
801
+
802
+
803
+ end
804
+
805
+
806
+ topic Migr8::Util::TemplateContext do
807
+ klass = Migr8::Util::TemplateContext
808
+
809
+
810
+ topic '#initialize()' do
811
+
812
+ spec "[!p69q1] takes vars and sets them into instance variables." do
813
+ ctx = klass.new({'title'=>'hello', 'members'=>%w[Haruhi Mikuru Yuki]})
814
+ ok {ctx.instance_variable_get('@title')} == 'hello'
815
+ ok {ctx.instance_variable_get('@members')} == ['Haruhi', 'Mikuru', 'Yuki']
816
+ end
817
+
818
+ spec "[!p853f] do nothing when vars is nil." do
819
+ pr = proc { klass.new(nil) }
820
+ ok {pr}.NOT.raise?(Exception)
821
+ end
822
+
823
+ end
824
+
825
+
826
+ topic '#escape()' do
827
+
828
+ spec "[!f3yy9] escapes \"'\" into \"''\" for default." do
829
+ $MIGR8_DBMS = nil
830
+ ctx = klass.new()
831
+ ok {ctx.escape("lock'n roll!")} == "lock''n roll!"
832
+ ok {ctx.escape("aa'bb''cc'''")} == "aa''bb''''cc''''''"
833
+ end
834
+
835
+ spec "[!to5kz] converts any value into string." do
836
+ $MIGR8_DBMS = nil
837
+ ctx = klass.new()
838
+ ok {ctx.escape(1)} == "1"
839
+ ok {ctx.escape(nil)} == ""
840
+ ok {ctx.escape(true)} == "true"
841
+ ok {ctx.escape(false)} == "false"
842
+ end
843
+
844
+ spec "[!6v5yq] escapes "'" into "\\'" when on MySQL dbms." do
845
+ at_exit { $MIGR8_DBMS = nil }
846
+ #
847
+ $MIGR8_DBMS = Migr8::DBMS::MySQL.new('mysql -q -u user1 example1')
848
+ ctx = klass.new()
849
+ ok {ctx.escape("aa'bb''cc'''")} == "aa\\'bb\\'\\'cc\\'\\'\\'"
850
+ ok {ctx.escape(123)} == "123"
851
+ ok {ctx.escape(nil)} == ""
852
+ #
853
+ $MIGR8_DBMS = Migr8::DBMS::SQLite3.new('sqlite3 example1.db')
854
+ ctx = klass.new()
855
+ ok {ctx.escape("aa'bb''cc'''")} == "aa''bb''''cc''''''"
856
+ ok {ctx.escape(123)} == "123"
857
+ ok {ctx.escape(nil)} == ""
858
+ #
859
+ $MIGR8_DBMS = Migr8::DBMS::PostgreSQL.new('psql -q -U user1 example1')
860
+ ctx = klass.new()
861
+ ok {ctx.escape("aa'bb''cc'''")} == "aa''bb''''cc''''''"
862
+ ok {ctx.escape(123)} == "123"
863
+ ok {ctx.escape(nil)} == ""
864
+ end
865
+
866
+
867
+ end
868
+
869
+
870
+ end
871
+
872
+
873
+ end