migr8 0.4.0

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.
@@ -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