benry-cmdopt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,922 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'minitest/spec'
4
+ require 'minitest/autorun'
5
+ require 'minitest/ok'
6
+
7
+ require 'benry/cmdopt'
8
+
9
+
10
+ def new_sample_schema()
11
+ sc = Benry::Cmdopt::Schema.new
12
+ sc.add(:help , "-h, --help" , "show help message.")
13
+ sc.add(:version, "--version" , "print version")
14
+ sc.add(:file , "-f, --file=<FILE>" , "filename")
15
+ sc.add(:indent , "-i, --indent[=<WIDTH>]", "enable indent", type: Integer)
16
+ sc.add(:mode , "-m, --mode=<MODE>" , "mode", enum: ['verbose', 'quiet'])
17
+ sc.add(:libpath, "-I, --path=<PATH>" , "library path (multiple ok)") do |optdef, key, val|
18
+ File.directory?(val) or raise "directory not exist."
19
+ arr = optdef[key] || []
20
+ arr << val
21
+ arr
22
+ end
23
+ sc
24
+ end
25
+
26
+
27
+
28
+ class Benry::Cmdopt::Schema::Test < MiniTest::Test
29
+
30
+
31
+ describe "#parse()" do
32
+
33
+ before do
34
+ @schema = Benry::Cmdopt::Schema.new
35
+ end
36
+
37
+ it "[!qw0ac] parses command option definition string." do
38
+ sc = @schema
39
+ tuple = sc.__send__(:parse_optdef, "-h, --help")
40
+ ok {tuple} == ['h', 'help', nil, false]
41
+ tuple = sc.__send__(:parse_optdef, "-h")
42
+ ok {tuple} == ['h', nil, nil, false]
43
+ tuple = sc.__send__(:parse_optdef, "--help")
44
+ ok {tuple} == [nil, 'help', nil, false]
45
+ end
46
+
47
+ it "[!ae733] parses command option definition which has a required param." do
48
+ sc = @schema
49
+ tuple = sc.__send__(:parse_optdef, "-f, --file=<FILE>")
50
+ ok {tuple} == ['f', 'file', '<FILE>', false]
51
+ tuple = sc.__send__(:parse_optdef, "-f <FILE>")
52
+ ok {tuple} == ['f', nil, '<FILE>', false]
53
+ tuple = sc.__send__(:parse_optdef, "--file=<FILE>")
54
+ ok {tuple} == [nil, 'file', '<FILE>', false]
55
+ end
56
+
57
+ it "[!4h05c] parses command option definition which has an optional param." do
58
+ sc = @schema
59
+ tuple = sc.__send__(:parse_optdef, "-i, --indent[=<WIDTH>]")
60
+ ok {tuple} == ['i', 'indent', '<WIDTH>', true]
61
+ tuple = sc.__send__(:parse_optdef, "-i[<WIDTH>]")
62
+ ok {tuple} == ['i', nil, '<WIDTH>', true]
63
+ tuple = sc.__send__(:parse_optdef, "--indent[=<WIDTH>]")
64
+ ok {tuple} == [nil, 'indent', '<WIDTH>', true]
65
+ end
66
+
67
+ it "[!b7jo3] raises SchemaError when command option definition is invalid." do
68
+ sc = @schema
69
+ pr = proc {
70
+ tuple = sc.__send__(:parse_optdef, "-i, --indent <WIDTH>")
71
+ }
72
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
73
+ "-i, --indent <WIDTH>: invalid option definition (use '=--indent' instead of ' --indent').")
74
+ end
75
+
76
+ end
77
+
78
+
79
+ describe "#add()" do
80
+
81
+ before do
82
+ @schema = Benry::Cmdopt::Schema.new
83
+ end
84
+
85
+ it "[!7hi2d] takes command option definition string." do
86
+ sc = @schema
87
+ sc.add(:indent, "-i, --indent=<WIDTH>", "print help")
88
+ items = sc.instance_eval { @items }
89
+ ok {items.length} == 1
90
+ ok {items[0]}.is_a?(Benry::Cmdopt::SchemaItem)
91
+ ok {items[0].key} == :indent
92
+ ok {items[0].short} == 'i'
93
+ ok {items[0].long} == 'indent'
94
+ ok {items[0].param} == '<WIDTH>'
95
+ ok {items[0].optional} == false
96
+ ok {items[0].type} == nil
97
+ ok {items[0].pattern} == nil
98
+ ok {items[0].enum} == nil
99
+ ok {items[0].callback} == nil
100
+ end
101
+
102
+ it "[!p9924] option key is omittable only when long option specified." do
103
+ sc = @schema
104
+ sc.add(nil, "-m, --max-num=<N>", nil)
105
+ items = sc.instance_eval { @items }
106
+ ok {items[0].key} == :max_num
107
+ end
108
+
109
+ it "[!jtp7z] raises SchemaError when key is nil and no long option." do
110
+ sc = @schema
111
+ pr = proc { sc.add(nil, "-i <N>", nil) }
112
+ msg = "add(nil, \"-i <N>\"): long option required when option key (1st arg) not specified."
113
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError, msg)
114
+ end
115
+
116
+ it "[!yht0v] keeps command option definitions." do
117
+ sc = @schema
118
+ sc.add(:indent, "-i, --indent[=<WIDTH>]", "indent width",
119
+ type: Integer, pattern: /\A\d+\z/, enum: ['2', '4', '8']) {|v| v.to_i }
120
+ items = sc.instance_eval { @items }
121
+ ok {items.length} == 1
122
+ ok {items[0]}.is_a?(Benry::Cmdopt::SchemaItem)
123
+ ok {items[0].key} == :indent
124
+ ok {items[0].short} == 'i'
125
+ ok {items[0].long} == 'indent'
126
+ ok {items[0].param} == '<WIDTH>'
127
+ ok {items[0].optional} == true
128
+ ok {items[0].type} == Integer
129
+ ok {items[0].pattern} == /\A\d+\z/
130
+ ok {items[0].enum} == ['2', '4', '8']
131
+ ok {items[0].callback}.is_a?(Proc)
132
+ ok {items[0].callback.arity} == 1
133
+ end
134
+
135
+ it "[!rhhji] raises SchemaError when key is not a Symbol." do
136
+ sc = @schema
137
+ pr = proc {
138
+ sc.add("-i, --indent[=<WIDTH>]", "indent width", nil)
139
+ }
140
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
141
+ 'add("-i, --indent[=<WIDTH>]"): 1st arg should be a Symbol as an option key.')
142
+ end
143
+
144
+ it "[!vq6eq] raises SchemaError when help message is missing." do
145
+ sc = @schema
146
+ pr = proc {
147
+ begin
148
+ sc.add(:indent, "-i, --indent[=<WIDTH>]", type: Array) # Ruby 2
149
+ rescue ArgumentError
150
+ sc.add(:indent, "-i, --indent[=<WIDTH>]", {type: Array}) # Ruby 3
151
+ end
152
+ }
153
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
154
+ 'add(:indent, "-i, --indent[=<WIDTH>]"): help message required as 3rd argument.')
155
+ end
156
+
157
+ it "[!7xmr5] raises SchemaError when type is not registered." do
158
+ sc = @schema
159
+ pr = proc {
160
+ sc.add(:indent, "-i, --indent[=<WIDTH>]", "indent width", type: Array)
161
+ }
162
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
163
+ "Array: unregistered type.")
164
+ end
165
+
166
+ it "[!s2aaj] raises SchemaError when option has no params but type specified." do
167
+ sc = @schema
168
+ pr = proc {
169
+ sc.add(:indent, "-i, --indent", "indent width", type: Integer)
170
+ }
171
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
172
+ "Integer: type specified in spite of option has no params.")
173
+ end
174
+
175
+ it "[!bi2fh] raises SchemaError when pattern is not a regexp." do
176
+ sc = @schema
177
+ pr = proc {
178
+ sc.add(:indent, "-x, --indent[=<WIDTH>]", "indent width", pattern: '\A\d+\z')
179
+ }
180
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
181
+ '"\\\\A\\\\d+\\\\z": regexp expected.')
182
+ end
183
+
184
+ it "[!01fmt] raises SchmeaError when option has no params but pattern specified." do
185
+ sc = @schema
186
+ pr = proc {
187
+ sc.add(:indent, "-i, --indent", "indent width", pattern: /\A\d+\z/)
188
+ }
189
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
190
+ '/\A\d+\z/: pattern specified in spite of option has no params.')
191
+ end
192
+
193
+ it "[!melyd] raises SchmeaError when enum is not a Array nor Set." do
194
+ sc = @schema
195
+ sc.add(:indent, "-i <N>", "indent width", enum: ["2", "4", "8"])
196
+ sc.add(:indent, "-i <N>", "indent width", enum: Set.new(["2", "4", "8"]))
197
+ pr = proc {
198
+ sc.add(:indent, "-i <N>", "indent width", enum: "2,4,8")
199
+ }
200
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
201
+ '"2,4,8": array or set expected.')
202
+ end
203
+
204
+ it "[!xqed8] raises SchemaError when enum specified for no param option." do
205
+ sc = @schema
206
+ pr = proc {
207
+ sc.add(:indent, "-i", "enable indent", enum: [2, 4, 8])
208
+ }
209
+ ok {pr}.raise?(Benry::Cmdopt::SchemaError,
210
+ "[2, 4, 8]: enum specified in spite of option has no params.")
211
+ end
212
+
213
+ end
214
+
215
+
216
+ describe '#build_option_help()' do
217
+
218
+ before do
219
+ sc = Benry::Cmdopt::Schema.new
220
+ sc.add(:help , "-h, --help" , "show help message.")
221
+ sc.add(:version, " --version" , "print version")
222
+ sc.add(:file , "-f, --file=<FILE>" , "filename")
223
+ sc.add(:indent , "-i, --indent[=<WIDTH>]", "enable indent", type: Integer)
224
+ sc.add(:debug , "-d, --debug" , nil)
225
+ @schema = sc
226
+ end
227
+
228
+ it "[!0aq0i] can take integer as width." do
229
+ help = @schema.build_option_help(41)
230
+ ok {help} == <<END
231
+ -h, --help : show help message.
232
+ --version : print version
233
+ -f, --file=<FILE> : filename
234
+ -i, --indent[=<WIDTH>] : enable indent
235
+ END
236
+ s = help.each_line.first.split(':')[0]
237
+ ok {s.length} == 41+2
238
+ end
239
+
240
+ it "[!pcsah] can take format string." do
241
+ help = @schema.build_option_help("%-42s: %s")
242
+ ok {help} == <<END
243
+ -h, --help : show help message.
244
+ --version : print version
245
+ -f, --file=<FILE> : filename
246
+ -i, --indent[=<WIDTH>] : enable indent
247
+ END
248
+ s = help.each_line.first.split(':')[0]
249
+ ok {s.length} == 42+0
250
+ end
251
+
252
+ it "[!dndpd] detects option width automatically when nothing specified." do
253
+ help = @schema.build_option_help()
254
+ ok {help} == <<END
255
+ -h, --help : show help message.
256
+ --version : print version
257
+ -f, --file=<FILE> : filename
258
+ -i, --indent[=<WIDTH>] : enable indent
259
+ END
260
+ s = help.each_line.to_a.last.split(':')[0]
261
+ ok {s.length} == 25
262
+ end
263
+
264
+ it "[!v7z4x] skips option help if help message is not specified." do
265
+ help = @schema.build_option_help()
266
+ ok {help} !~ /debug/
267
+ end
268
+
269
+ it "[!to1th] includes all option help when `all` is true." do
270
+ help = @schema.build_option_help(nil, all: true)
271
+ ok {help} =~ /debug/
272
+ ok {help} == <<END
273
+ -h, --help : show help message.
274
+ --version : print version
275
+ -f, --file=<FILE> : filename
276
+ -i, --indent[=<WIDTH>] : enable indent
277
+ -d, --debug :
278
+ END
279
+ end
280
+
281
+ it "[!848rm] supports multi-lines help message." do
282
+ sc = Benry::Cmdopt::Schema.new
283
+ sc.add(:mode, "-m, --mode=<MODE>", <<END)
284
+ output mode
285
+ v, verbose: print many output
286
+ q, quiet: print litte output
287
+ c, compact: print summary output
288
+ END
289
+ actual = sc.build_option_help()
290
+ expected = <<END
291
+ -m, --mode=<MODE> : output mode
292
+ v, verbose: print many output
293
+ q, quiet: print litte output
294
+ c, compact: print summary output
295
+ END
296
+ ok {actual} == expected
297
+ end
298
+
299
+ end
300
+
301
+
302
+ describe '#_default_format()' do
303
+
304
+ before do
305
+ sc = Benry::Cmdopt::Schema.new
306
+ sc.add(:help , "-h, --help" , "show help message.")
307
+ sc.add(:version, " --version" , "print version")
308
+ sc.add(:file , "-f, --file=<FILE>" , "filename")
309
+ sc.add(:indent , "-i, --indent[=<WIDTH>]", "enable indent", type: Integer)
310
+ sc.add(:debug , "-d, --debug" , nil)
311
+ @schema = sc
312
+ end
313
+
314
+ it "[!kkh9t] returns format string." do
315
+ ret = @schema.__send__(:_default_format)
316
+ ok {ret} == " %-22s : %s"
317
+ end
318
+
319
+ it "[!hr45y] detects preffered option width." do
320
+ ret = @schema.__send__(:_default_format, 10, 20)
321
+ ok {ret} == " %-20s : %s"
322
+ ret = @schema.__send__(:_default_format, 30, 40)
323
+ ok {ret} == " %-30s : %s"
324
+ ret = @schema.__send__(:_default_format, 10, 40)
325
+ ok {ret} == " %-22s : %s"
326
+ end
327
+
328
+ end
329
+
330
+
331
+ describe '#each_option_help()' do
332
+
333
+ before do
334
+ sc = Benry::Cmdopt::Schema.new
335
+ sc.add(:help, "-h, --help", "show help message")
336
+ sc.add(:version, " --version", "print version")
337
+ @schema = sc
338
+ end
339
+
340
+ it "[!4b911] yields each optin definition str and help message." do
341
+ sc = @schema
342
+ arr = []
343
+ sc.each_option_help do |opt, help|
344
+ arr << [opt, help]
345
+ end
346
+ ok {arr} == [
347
+ ["-h, --help", "show help message"],
348
+ [" --version", "print version"],
349
+ ]
350
+ end
351
+
352
+ it "[!zbxyv] returns self." do
353
+ sc = @schema
354
+ ret = sc.each_option_help { nil }
355
+ ok {ret}.same? sc
356
+ end
357
+
358
+ end
359
+
360
+
361
+ describe '#find_short_option()' do
362
+
363
+ before do
364
+ @schema = new_sample_schema()
365
+ end
366
+
367
+ it "[!b4js1] returns option definition matched to short name." do
368
+ x = @schema.find_short_option('h')
369
+ ok {x.key} == :help
370
+ x = @schema.find_short_option('f')
371
+ ok {x.key} == :file
372
+ x = @schema.find_short_option('i')
373
+ ok {x.key} == :indent
374
+ end
375
+
376
+ it "[!s4d1y] returns nil when nothing found." do
377
+ ok {@schema.find_short_option('v')} == nil
378
+ end
379
+
380
+ end
381
+
382
+
383
+ describe '#find_long_option()' do
384
+
385
+ before do
386
+ @schema = new_sample_schema()
387
+ end
388
+
389
+ it "[!atmf9] returns option definition matched to long name." do
390
+ x = @schema.find_long_option('help')
391
+ ok {x.key} == :help
392
+ x = @schema.find_long_option('file')
393
+ ok {x.key} == :file
394
+ x = @schema.find_long_option('indent')
395
+ ok {x.key} == :indent
396
+ end
397
+
398
+ it "[!6haoo] returns nil when nothing found." do
399
+ ok {@schema.find_long_option('lib')} == nil
400
+ end
401
+
402
+ end
403
+
404
+
405
+ end
406
+
407
+
408
+
409
+ class Benry::Cmdopt::SchemaItem::Test < MiniTest::Test
410
+
411
+
412
+ describe '#validate_and_convert()' do
413
+
414
+ def new_item(key, optstr, short, long, param, help,
415
+ optional: nil, type: nil, pattern: nil, enum: nil, &callback)
416
+ return Benry::Cmdopt::SchemaItem.new(key, optstr, short, long, param, help,
417
+ optional: optional, type: type, pattern: pattern, enum: enum, &callback)
418
+ end
419
+
420
+ it "[!h0s0o] raises RuntimeError when value not matched to pattern." do
421
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", "indent width", pattern: /\A\d+\z/)
422
+ optdict = {}
423
+ pr = proc { x.validate_and_convert("abc", optdict) }
424
+ ok {pr}.raise?(RuntimeError, "pattern unmatched.")
425
+ end
426
+
427
+ it "[!5jrdf] raises RuntimeError when value not in enum." do
428
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", "indent width", enum: ['2', '4', '8'])
429
+ optdict = {}
430
+ pr = proc { x.validate_and_convert("10", optdict) }
431
+ ok {pr}.raise?(RuntimeError, "expected one of 2/4/8.")
432
+ end
433
+
434
+ it "[!j4fuz] calls type-specific callback when type specified." do
435
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", "indent width", type: Integer)
436
+ optdict = {}
437
+ pr = proc { x.validate_and_convert("abc", optdict) }
438
+ ok {pr}.raise?(RuntimeError, "integer expected.")
439
+ end
440
+
441
+ it "[!jn9z3] calls callback when callback specified." do
442
+ called = false
443
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", "indent width") {|va|
444
+ called = true
445
+ }
446
+ optdict = {}
447
+ x.validate_and_convert("abc", optdict)
448
+ ok {called} == true
449
+ end
450
+
451
+ it "[!iqalh] calls callback with different number of args according to arity." do
452
+ args1 = nil
453
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", nil) {|val|
454
+ args1 = val
455
+ }
456
+ optdict = {}
457
+ x.validate_and_convert("123", optdict)
458
+ ok {args1} == "123"
459
+ #
460
+ args2 = nil
461
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", nil) {|optdict, key, val|
462
+ args2 = [optdict, key, val]
463
+ }
464
+ optdict = {}
465
+ x.validate_and_convert("123", optdict)
466
+ ok {args2} == [optdict, :indent, "123"]
467
+ end
468
+
469
+ it "[!x066l] returns new value." do
470
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", nil, type: Integer)
471
+ ok {x.validate_and_convert("123", {})} == 123
472
+ #
473
+ x = new_item(:indent, "", "i", "indent", "<WIDTH>", nil, type: Integer) {|val|
474
+ val * 2
475
+ }
476
+ ok {x.validate_and_convert("123", {})} == 246
477
+ end
478
+
479
+ end
480
+
481
+ end
482
+
483
+
484
+
485
+ class Benry::Cmdopt::Parser::Test < MiniTest::Test
486
+
487
+
488
+ describe '#parse_options()' do
489
+
490
+ before do
491
+ @parser = Benry::Cmdopt::Parser.new(new_sample_schema())
492
+ end
493
+
494
+ it "[!3wmsy] returns command option values as a dict." do
495
+ argv = ["-h", "--version"]
496
+ d = @parser.parse(argv)
497
+ ok {d} == {help: true, version: true}
498
+ end
499
+
500
+ it "[!uh7j8] parses long options." do
501
+ argv = ["--help", "--file=foo.png", "--indent=10"]
502
+ d = @parser.parse(argv)
503
+ ok {d} == {help: true, file: "foo.png", indent: 10}
504
+ end
505
+
506
+ it "[!nwnjc] parses short options." do
507
+ argv = ["-h", "-f", "foo.png", "-i10"]
508
+ d = @parser.parse(argv)
509
+ ok {d} == {help: true, file: "foo.png", indent: 10}
510
+ end
511
+
512
+ it "[!y04um] skips rest options when '--' found in argv." do
513
+ argv = ["-h", "--", "-f", "foo.png", "-i10"]
514
+ d = @parser.parse(argv)
515
+ ok {d} == {help: true}
516
+ ok {argv} == ["-f", "foo.png", "-i10"]
517
+ end
518
+
519
+ it "[!qpuxh] handles only OptionError when block given." do
520
+ errmsg = nil
521
+ errcls = nil
522
+ @parser.parse(["-ix"]) {|err|
523
+ errmsg = err.message
524
+ errcls = err.class
525
+ }
526
+ ok {errmsg} == "-ix: integer expected."
527
+ ok {errcls} == Benry::Cmdopt::OptionError
528
+ #
529
+ sc = Benry::Cmdopt::Schema.new
530
+ sc.add(:file, "--file=<FILE>", "file") do |val|
531
+ File.open(val) {|f| f.read }
532
+ end
533
+ parser = Benry::Cmdopt::Parser.new(sc)
534
+ pr = proc { parser.parse(["--file=/foo/bar/baz.png"]) }
535
+ ok {pr}.raise?(Errno::ENOENT, /No such file or directory/)
536
+ end
537
+
538
+ it "[!dhpw1] returns nil when OptionError handled." do
539
+ ret = @parser.parse(["-dx"]) {|err| 1 }
540
+ ok {ret} == nil
541
+ end
542
+
543
+ end
544
+
545
+
546
+ describe '#parse_long_option()' do
547
+
548
+ before do
549
+ @parser = Benry::Cmdopt::Parser.new(new_sample_schema())
550
+ end
551
+
552
+ it "[!3i994] raises OptionError when invalid long option format." do
553
+ argv = ["--f/o/o"]
554
+ pr = proc { @parser.parse(argv) }
555
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--f/o/o: invalid long option.")
556
+ end
557
+
558
+ it "[!er7h4] raises OptionError when unknown long option." do
559
+ argv = ["--foo"]
560
+ pr = proc { @parser.parse(argv) }
561
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--foo: unknown long option.")
562
+ end
563
+
564
+ it "[!2jd9w] raises OptionError when no arguments specified for arg required long option." do
565
+ argv = ["--file"]
566
+ pr = proc { @parser.parse(argv) }
567
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--file: argument required.")
568
+ end
569
+
570
+ it "[!qyq8n] raises optionError when an argument specified for no arg long option." do
571
+ argv = ["--version=1.0.0"]
572
+ pr = proc { @parser.parse(argv) }
573
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--version=1.0.0: unexpected argument.")
574
+ end
575
+
576
+ it "[!o596x] validates argument value." do
577
+ argv = ["--indent=abc"]
578
+ pr = proc { @parser.parse(argv) }
579
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--indent=abc: integer expected.")
580
+ #
581
+ argv = ["--path=/foo/bar"]
582
+ pr = proc { @parser.parse(argv) }
583
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--path=/foo/bar: directory not exist.")
584
+
585
+ end
586
+
587
+ end
588
+
589
+
590
+ describe '#parse_short_option()' do
591
+
592
+ before do
593
+ @parser = Benry::Cmdopt::Parser.new(new_sample_schema())
594
+ end
595
+
596
+ it "[!4eh49] raises OptionError when unknown short option specified." do
597
+ argv = ["-hxf", "foo.png"]
598
+ pr = proc { @parser.parse(argv) }
599
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "-x: unknown option.")
600
+ end
601
+
602
+ it "[!utdbf] raises OptionError when argument required but not specified." do
603
+ argv = ["-hf"]
604
+ pr = proc { @parser.parse(argv) }
605
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "-f: argument required.")
606
+ end
607
+
608
+ it "[!f63hf] short option arg can be specified without space separator." do
609
+ argv = ["-hfabc.png", "xx"]
610
+ d = @parser.parse(argv)
611
+ ok {d} == {help: true, file: "abc.png"}
612
+ ok {argv} == ["xx"]
613
+ end
614
+
615
+ it "[!yjq6b] optional arg should be specified without space separator." do
616
+ argv = ["-hi123", "xx"]
617
+ d = @parser.parse(argv)
618
+ ok {d} == {help: true, indent: 123}
619
+ ok {argv} == ['xx']
620
+ end
621
+
622
+ it "[!wape4] otpional arg can be omit." do
623
+ argv = ["-hi", "xx"]
624
+ d = @parser.parse(argv)
625
+ ok {d} == {help: true, indent: true}
626
+ ok {argv} == ['xx']
627
+ end
628
+
629
+ it "[!yu0kc] validates short option argument." do
630
+ argv = ["-iaaa"]
631
+ pr = proc { @parser.parse(argv) }
632
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "-iaaa: integer expected.")
633
+ #
634
+ argv = ["-I", "/foo/bar"]
635
+ pr = proc { @parser.parse(argv) }
636
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "-I /foo/bar: directory not exist.")
637
+ end
638
+
639
+ end
640
+
641
+
642
+ describe '#new_options_dict()' do
643
+
644
+ it "[!vm6h0] returns new hash object." do
645
+ parser = Benry::Cmdopt::Parser.new(new_sample_schema())
646
+ ret = parser.__send__(:new_options_dict)
647
+ ok {ret}.is_a?(Hash)
648
+ ok {ret} == {}
649
+ end
650
+
651
+ end
652
+
653
+
654
+ end
655
+
656
+
657
+
658
+ class Benry::Cmdopt::Test < MiniTest::Test
659
+
660
+
661
+ describe 'PARAM_TYPES[Integer]' do
662
+
663
+ before do
664
+ sc = Benry::Cmdopt::Schema.new
665
+ sc.add(:indent, "-i, --indent[=<N>]", "indent width", type: Integer)
666
+ @parser = Benry::Cmdopt::Parser.new(sc)
667
+ end
668
+
669
+ it "[!6t8cs] converts value into integer." do
670
+ d = @parser.parse(['-i20'])
671
+ ok {d[:indent]} == 20
672
+ #
673
+ d = @parser.parse(['--indent=12'])
674
+ ok {d[:indent]} == 12
675
+ end
676
+
677
+ it "[!nzwc9] raises error when failed to convert value into integer." do
678
+ pr = proc { @parser.parse(['-i2.1']) }
679
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "-i2.1: integer expected.")
680
+ #
681
+ pr = proc { @parser.parse(['--indent=2.2']) }
682
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--indent=2.2: integer expected.")
683
+ end
684
+
685
+ end
686
+
687
+
688
+ describe 'PARAM_TYPES[Float]' do
689
+
690
+ before do
691
+ sc = Benry::Cmdopt::Schema.new
692
+ sc.add(:ratio, "-r, --ratio=<RATIO>", "ratio", type: Float)
693
+ @parser = Benry::Cmdopt::Parser.new(sc)
694
+ end
695
+
696
+ it "[!gggy6] converts value into float." do
697
+ d = @parser.parse(['-r', '1.25'])
698
+ ok {d[:ratio]} == 1.25
699
+ #
700
+ d = @parser.parse(['--ratio=1.25'])
701
+ ok {d[:ratio]} == 1.25
702
+ end
703
+
704
+ it "[!t4elj] raises error when faield to convert value into float." do
705
+ pr = proc { @parser.parse(['-r', 'abc']) }
706
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "-r abc: float expected.")
707
+ #
708
+ pr = proc { @parser.parse(['--ratio=abc']) }
709
+ ok {pr}.raise?(Benry::Cmdopt::OptionError, "--ratio=abc: float expected.")
710
+ end
711
+
712
+ end
713
+
714
+
715
+ describe 'PARAM_TYPES[TrueClass]' do
716
+
717
+ before do
718
+ sc = Benry::Cmdopt::Schema.new
719
+ sc.add(:border , "-b, --border[=<on|off>]", "enable border", type: TrueClass)
720
+ @parser = Benry::Cmdopt::Parser.new(sc)
721
+ end
722
+
723
+ it "[!47kx4] converts 'true'/'on'/'yes' into true." do
724
+ d = @parser.parse(["-btrue"])
725
+ ok {d} == {border: true}
726
+ d = @parser.parse(["-bon"])
727
+ ok {d} == {border: true}
728
+ d = @parser.parse(["-byes"])
729
+ ok {d} == {border: true}
730
+ #
731
+ d = @parser.parse(["--border=true"])
732
+ ok {d} == {border: true}
733
+ d = @parser.parse(["--border=on"])
734
+ ok {d} == {border: true}
735
+ d = @parser.parse(["--border=yes"])
736
+ ok {d} == {border: true}
737
+ end
738
+
739
+ it "[!3n810] converts 'false'/'off'/'no' into false." do
740
+ d = @parser.parse(["-bfalse"])
741
+ ok {d} == {border: false}
742
+ d = @parser.parse(["-boff"])
743
+ ok {d} == {border: false}
744
+ d = @parser.parse(["-bno"])
745
+ ok {d} == {border: false}
746
+ #
747
+ d = @parser.parse(["--border=false"])
748
+ ok {d} == {border: false}
749
+ d = @parser.parse(["--border=off"])
750
+ ok {d} == {border: false}
751
+ d = @parser.parse(["--border=no"])
752
+ ok {d} == {border: false}
753
+ end
754
+
755
+ it "[!h8ayh] raises error when failed to convert value into true nor false." do
756
+ pr = proc { @parser.parse(["-bt"]) }
757
+ ok {pr}.raise?(Benry::Cmdopt::OptionError,
758
+ "-bt: boolean expected.")
759
+ #
760
+ pr = proc { @parser.parse(["--border=t"]) }
761
+ ok {pr}.raise?(Benry::Cmdopt::OptionError,
762
+ "--border=t: boolean expected.")
763
+ end
764
+
765
+ end
766
+
767
+
768
+ describe 'PARAM_TYPES[Date]' do
769
+
770
+ before do
771
+ require 'date'
772
+ sc = Benry::Cmdopt::Schema.new
773
+ sc.add(:date, "-d, --date=<YYYY-MM-DD>]", "date", type: Date)
774
+ @parser = Benry::Cmdopt::Parser.new(sc)
775
+ end
776
+
777
+ it "[!sru5j] converts 'YYYY-MM-DD' into date object." do
778
+ d = @parser.parse(['-d', '2000-01-01'])
779
+ ok {d[:date]} == Date.new(2000, 1, 1)
780
+ #
781
+ d = @parser.parse(['--date=2000-1-2'])
782
+ ok {d[:date]} == Date.new(2000, 1, 2)
783
+ end
784
+
785
+ it "[!h9q9y] raises error when failed to convert into date object." do
786
+ pr = proc { @parser.parse(['-d', '2000/01/01']) }
787
+ ok {pr}.raise?(Benry::Cmdopt::OptionError,
788
+ "-d 2000/01/01: invalid date format (ex: '2000-01-01')")
789
+ #
790
+ pr = proc { @parser.parse(['--date=01-01-2000']) }
791
+ ok {pr}.raise?(Benry::Cmdopt::OptionError,
792
+ "--date=01-01-2000: invalid date format (ex: '2000-01-01')")
793
+ end
794
+
795
+ it "[!i4ui8] raises error when specified date not exist." do
796
+ pr = proc { @parser.parse(['-d', '2001-02-29']) }
797
+ ok {pr}.raise?(Benry::Cmdopt::OptionError,
798
+ "-d 2001-02-29: date not exist.")
799
+ #
800
+ pr = proc { @parser.parse(['--date=2001-02-29']) }
801
+ ok {pr}.raise?(Benry::Cmdopt::OptionError,
802
+ "--date=2001-02-29: date not exist.")
803
+ end
804
+
805
+ end
806
+
807
+
808
+ describe '.new()' do
809
+
810
+ it "[!7kkqv] creates Facade object." do
811
+ obj = Benry::Cmdopt.new
812
+ ok {obj}.is_a?(Benry::Cmdopt::Facade)
813
+ end
814
+
815
+ end
816
+
817
+
818
+ end
819
+
820
+
821
+
822
+ class Benry::Cmdopt::Facade::Test < MiniTest::Test
823
+
824
+
825
+ describe '#add()' do
826
+
827
+ it "[!vmb3r] defines command option." do
828
+ cmdopt = Benry::Cmdopt.new()
829
+ cmdopt.add(:help, "-h, --help", "show help message")
830
+ items = cmdopt.instance_eval { @schema.instance_variable_get('@items') }
831
+ ok {items}.is_a?(Array)
832
+ ok {items.length} == 1
833
+ ok {items[0].key} == :help
834
+ ok {items[0].short} == 'h'
835
+ ok {items[0].long} == 'help'
836
+ ok {items[0].help} == 'show help message'
837
+ end
838
+
839
+ end
840
+
841
+
842
+ describe '#build_option_help()' do
843
+
844
+ before do
845
+ @cmdopt = Benry::Cmdopt.new
846
+ @cmdopt.add(:help , "-h, --help" , "show help message")
847
+ @cmdopt.add(:version, " --version" , "print version")
848
+ @cmdopt.add(:file , "-f, --file=<FILE>" , "filename")
849
+ end
850
+
851
+ it "[!dm4p8] returns option help message." do
852
+ help = @cmdopt.build_option_help()
853
+ ok {help} == <<END
854
+ -h, --help : show help message
855
+ --version : print version
856
+ -f, --file=<FILE> : filename
857
+ END
858
+ end
859
+
860
+ end
861
+
862
+
863
+ describe '#each_option_help()' do
864
+
865
+ before do
866
+ @cmdopt = Benry::Cmdopt.new
867
+ @cmdopt.add(:help , "-h, --help" , "show help message")
868
+ @cmdopt.add(:version, " --version" , "print version")
869
+ @cmdopt.add(:file , "-f, --file=<FILE>" , "filename")
870
+ end
871
+
872
+ it "[!bw9qx] yields each option definition string and help message." do
873
+ cmdopt = @cmdopt
874
+ pairs = []
875
+ cmdopt.each_option_help do |opt, help|
876
+ pairs << [opt, help]
877
+ end
878
+ ok {pairs} == [
879
+ ["-h, --help", "show help message"],
880
+ [" --version", "print version"],
881
+ ["-f, --file=<FILE>", "filename"],
882
+ ]
883
+ end
884
+
885
+ end
886
+
887
+
888
+ describe '#parse_options()' do
889
+
890
+ before do
891
+ @cmdopt = Benry::Cmdopt.new()
892
+ @cmdopt.add(:file, "-f, --file=<FILE>", "file") do |val|
893
+ File.open(val) {|f| f.read }
894
+ end
895
+ @cmdopt.add(:file, "-d, --debug[=<LEVEL>]", "debug", type: Integer)
896
+ end
897
+
898
+ it "[!areof] handles only OptionError when block given." do
899
+ errmsg = nil
900
+ errcls = nil
901
+ @cmdopt.parse(["-dx"]) {|err|
902
+ errmsg = err.message
903
+ errcls = err.class
904
+ }
905
+ ok {errmsg} == "-dx: integer expected."
906
+ ok {errcls} == Benry::Cmdopt::OptionError
907
+ #
908
+ pr = proc do
909
+ @cmdopt.parse(["-f", "/foo/bar/baz.png"])
910
+ end
911
+ ok {pr}.raise?(Errno::ENOENT, /No such file or directory/)
912
+ end
913
+
914
+ it "[!peuva] returns nil when OptionError handled." do
915
+ ret = @cmdopt.parse(["-dx"]) {|err| 1 }
916
+ ok {ret} == nil
917
+ end
918
+
919
+ end
920
+
921
+
922
+ end