benry-cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +428 -0
  3. data/Rakefile +108 -0
  4. data/benry-cli.gemspec +30 -0
  5. data/lib/benry/cli.rb +595 -0
  6. data/test/cli_test.rb +1025 -0
  7. metadata +79 -0
@@ -0,0 +1,1025 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'minitest/spec'
4
+ require 'minitest/autorun'
5
+ require 'minitest/ok'
6
+
7
+ require 'benry/cli'
8
+
9
+
10
+
11
+ class HelloAction < Benry::CLI::Action
12
+ end
13
+
14
+
15
+ class HelloSubAction < HelloAction
16
+ end
17
+
18
+
19
+ class GitAction < Benry::CLI::Action
20
+
21
+ self.prefix = 'git'
22
+
23
+ @action.('switch', 'switch git branch')
24
+ @option.('-v, --verbose', "verbose mode")
25
+ def do_git_switch(branch, verbose: false)
26
+ puts "git checkout #{branch}"
27
+ end
28
+
29
+ @action.('fork', 'create and switch to new branch')
30
+ @option.('-v, --verbose', "verbose mode")
31
+ def do_git_fork(branch, verbose: false)
32
+ puts "git checkout -b #{branch}"
33
+ end
34
+
35
+ @action.('join', 'merge branch with --no-ff')
36
+ def do_git_join(branch)
37
+ puts "git merge --no-ff #{branch}"
38
+ end
39
+
40
+ end
41
+
42
+
43
+
44
+ describe Benry::CLI::OptionSchema do
45
+
46
+
47
+ describe '.parse()' do
48
+
49
+ def _arg_should_be_nothing(x)
50
+ ok {x.argname} == nil
51
+ ok {x.arg_required?} == false
52
+ ok {x.arg_optional?} == false
53
+ ok {x.arg_nothing?} == true
54
+ end
55
+
56
+ def _arg_should_be_required(x, argname)
57
+ ok {x.argname} == argname
58
+ ok {x.arg_required?} == true
59
+ ok {x.arg_optional?} == false
60
+ ok {x.arg_nothing?} == false
61
+ end
62
+
63
+ def _arg_should_be_optional(x, argname)
64
+ ok {x.argname} == argname
65
+ ok {x.arg_required?} == false
66
+ ok {x.arg_optional?} == true
67
+ ok {x.arg_nothing?} == false
68
+ end
69
+
70
+ it "[!fdh36] can parse '-v, --version' (short + long)." do
71
+ x = Benry::CLI::OptionSchema.parse("-v, --version", "print version")
72
+ ok {x.short} == "v"
73
+ ok {x.long} == "version"
74
+ ok {x.desc} == "print version"
75
+ _arg_should_be_nothing(x)
76
+ end
77
+
78
+ it "[!jkmee] can parse '-v' (short)" do
79
+ x = Benry::CLI::OptionSchema.parse("-v", "print version")
80
+ ok {x.short} == "v"
81
+ ok {x.long} == nil
82
+ ok {x.desc} == "print version"
83
+ _arg_should_be_nothing(x)
84
+ end
85
+
86
+ it "[!uc2en] can parse '--version' (long)." do
87
+ x = Benry::CLI::OptionSchema.parse("--version", "print version")
88
+ ok {x.short} == nil
89
+ ok {x.long} == "version"
90
+ ok {x.desc} == "print version"
91
+ _arg_should_be_nothing(x)
92
+ end
93
+
94
+ it "[!sy157] can parse '-f, --file=FILE' (short + long + required-arg)." do
95
+ x = Benry::CLI::OptionSchema.parse("-f, --file=FILENAME", "config file")
96
+ ok {x.short} == "f"
97
+ ok {x.long} == "file"
98
+ ok {x.desc} == "config file"
99
+ _arg_should_be_required(x, "FILENAME")
100
+ end
101
+
102
+ it "[!wrjqa] can parse '-f FILE' (short + required-arg)." do
103
+ x = Benry::CLI::OptionSchema.parse("-f FILENAME", "config file")
104
+ ok {x.short} == "f"
105
+ ok {x.long} == nil
106
+ ok {x.desc} == "config file"
107
+ _arg_should_be_required(x, "FILENAME")
108
+ end
109
+
110
+ it "[!ip99s] can parse '--file=FILE' (long + required-arg)." do
111
+ x = Benry::CLI::OptionSchema.parse("--file=FILENAME", "config file")
112
+ ok {x.short} == nil
113
+ ok {x.long} == "file"
114
+ ok {x.desc} == "config file"
115
+ _arg_should_be_required(x, "FILENAME")
116
+ end
117
+
118
+ it "[!9pmv8] can parse '-i, --indent[=N]' (short + long + optional-arg)." do
119
+ x = Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "indent (default 2)")
120
+ ok {x.short} == "i"
121
+ ok {x.long} == "indent"
122
+ ok {x.desc} == "indent (default 2)"
123
+ _arg_should_be_optional(x, "N")
124
+ end
125
+
126
+ it "[!ooo42] can parse '-i[N]' (short + optional-arg)." do
127
+ x = Benry::CLI::OptionSchema.parse("-i[N]", "indent (default 2)")
128
+ ok {x.short} == "i"
129
+ ok {x.long} == nil
130
+ ok {x.desc} == "indent (default 2)"
131
+ _arg_should_be_optional(x, "N")
132
+ end
133
+
134
+ it "[!o93c7] can parse '--indent[=N]' (long + optional-arg)." do
135
+ x = Benry::CLI::OptionSchema.parse("--indent[=N]", "indent (default 2)")
136
+ ok {x.short} == nil
137
+ ok {x.long} == "indent"
138
+ ok {x.desc} == "indent (default 2)"
139
+ _arg_should_be_optional(x, "N")
140
+ end
141
+
142
+ it "[!gzuhx] can parse string with extra spaces." do
143
+ x = Benry::CLI::OptionSchema.parse(" -v, --version ", "print version")
144
+ ok {x.short} == "v"
145
+ ok {x.long} == "version"
146
+ ok {x.argname} == nil
147
+ x = Benry::CLI::OptionSchema.parse(" -f, --file=FILENAME ", "config file")
148
+ ok {x.short} == "f"
149
+ ok {x.long} == "file"
150
+ ok {x.argname} == "FILENAME"
151
+ x = Benry::CLI::OptionSchema.parse(" -i, --indent[=N] ", "indent (default 2)")
152
+ ok {x.short} == "i"
153
+ ok {x.long} == "indent"
154
+ ok {x.argname} == "N"
155
+ end
156
+
157
+ it "[!cy1ux] regards canonical name of '-f NAME #file' as 'file'." do
158
+ x = Benry::CLI::OptionSchema.parse("-v #version", "print version")
159
+ ok {x.short} == "v"
160
+ ok {x.long} == nil
161
+ ok {x.name} == "version"
162
+ ok {x.argname} == nil
163
+ x = Benry::CLI::OptionSchema.parse("-f FILENAME #file", "config file")
164
+ ok {x.short} == "f"
165
+ ok {x.long} == nil
166
+ ok {x.name} == "file"
167
+ ok {x.argname} == "FILENAME"
168
+ x = Benry::CLI::OptionSchema.parse("-i[N] #indent", "indent (default 2)")
169
+ ok {x.short} == "i"
170
+ ok {x.long} == nil
171
+ ok {x.name} == "indent"
172
+ ok {x.argname} == "N"
173
+ end
174
+
175
+ it "[!6f4xx] uses long name or short name as option name when option name is not specfied." do
176
+ x = Benry::CLI::OptionSchema.parse("-v, --ver #version", "")
177
+ ok {x.name} == "version"
178
+ ok {x.short} == "v"
179
+ ok {x.long} == "ver"
180
+ x = Benry::CLI::OptionSchema.parse("-v #version", "")
181
+ ok {x.name} == "version"
182
+ ok {x.short} == "v"
183
+ ok {x.long} == nil
184
+ x = Benry::CLI::OptionSchema.parse("--ver #version", "")
185
+ ok {x.name} == "version"
186
+ ok {x.short} == nil
187
+ ok {x.long} == "ver"
188
+ #
189
+ x = Benry::CLI::OptionSchema.parse("-v, --ver", "")
190
+ ok {x.name} == "ver"
191
+ ok {x.short} == "v"
192
+ ok {x.long} == "ver"
193
+ x = Benry::CLI::OptionSchema.parse("-v", "")
194
+ ok {x.name} == "v"
195
+ ok {x.short} == "v"
196
+ ok {x.long} == nil
197
+ x = Benry::CLI::OptionSchema.parse("--ver", "")
198
+ ok {x.name} == "ver"
199
+ ok {x.short} == nil
200
+ ok {x.long} == "ver"
201
+ end
202
+
203
+ it "[!1769n] raises error when invalid format." do
204
+ pr = proc { Benry::CLI::OptionSchema.parse("-f, --file FILENAME", "config file") }
205
+ ok {pr}.raise?(Benry::CLI::OptionDefinitionError,
206
+ "'-f, --file FILENAME': failed to parse option definition.")
207
+ end
208
+
209
+ it "[!j2wgf] raises error when '-i [N]' specified." do
210
+ pr = proc { Benry::CLI::OptionSchema.parse("-i [N]", "indent (default 2)") }
211
+ ok {pr}.raise?(Benry::CLI::OptionDefinitionError,
212
+ "'-i [N]': failed to parse option definition due to extra space before '[' (should be '-i[N]').")
213
+ end
214
+
215
+ end
216
+
217
+
218
+ describe '#option_string()' do
219
+
220
+ it "[!pdaz3] builds option definition string." do
221
+ cls = Benry::CLI::OptionSchema
222
+ #
223
+ ok {cls.parse("-v, --version", "") .option_string} == "-v, --version"
224
+ ok {cls.parse("-v", "") .option_string} == "-v"
225
+ ok {cls.parse("--version", "") .option_string} == " --version"
226
+ #
227
+ ok {cls.parse("-f, --file=FILE1", "") .option_string} == "-f, --file=FILE1"
228
+ ok {cls.parse("-f FILE1", "") .option_string} == "-f FILE1"
229
+ ok {cls.parse("--file=FILE1", "") .option_string} == " --file=FILE1"
230
+ #
231
+ ok {cls.parse("-i, --indent[=N]", "") .option_string} == "-i, --indent[=N]"
232
+ ok {cls.parse("-i[=N]", "") .option_string} == "-i[=N]"
233
+ ok {cls.parse("--indent[=N]", "") .option_string} == " --indent[=N]"
234
+ end
235
+
236
+ end
237
+
238
+
239
+ end
240
+
241
+
242
+
243
+ describe Benry::CLI::OptionParser do
244
+
245
+ def _option_schemas
246
+ return [
247
+ Benry::CLI::OptionSchema.parse("-v, --version", "print version"),
248
+ Benry::CLI::OptionSchema.parse("-f, --file=FILE", "config file"),
249
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "indent (default 2)"),
250
+ ]
251
+ end
252
+
253
+ def _option_parser
254
+ arr = _option_schemas()
255
+ return Benry::CLI::OptionParser.new(arr)
256
+ end
257
+
258
+
259
+ describe '#initialize()' do
260
+
261
+ it "[!bflls] takes array of option schema." do
262
+ arr = _option_schemas()
263
+ parser = Benry::CLI::OptionParser.new(arr)
264
+ parser.instance_exec(self) do |_|
265
+ _.ok {@option_schemas}.is_a?(Array)
266
+ _.ok {@option_schemas.length} == 3
267
+ _.ok {@option_schemas[0].long} == "version"
268
+ _.ok {@option_schemas[1].long} == "file"
269
+ _.ok {@option_schemas[2].long} == "indent"
270
+ end
271
+ end
272
+
273
+ end
274
+
275
+
276
+ describe '#otpion()' do
277
+
278
+ it "[!s59ly] accepts option definition string and description." do
279
+ parser = Benry::CLI::OptionParser.new
280
+ parser.option('-v, --verbose', "set verbose mode")
281
+ parser.option('-f, --file=NAME', "config file")
282
+ parser.option('-i, --indent[=n]', "indentation (default 2)") {|val| val.to_i }
283
+ args = ["-vffoo.txt", "-i2", "x", "y"]
284
+ options = parser.parse(args)
285
+ ok {options} == {"verbose"=>true, "file"=>"foo.txt", "indent"=>2}
286
+ ok {args} == ["x", "y"]
287
+ end
288
+
289
+ it "[!2gfnh] recognizes first argument as option name if it is a symbol." do
290
+ parser = Benry::CLI::OptionParser.new
291
+ parser.option(:verbose, '-v ', "set verbose mode")
292
+ parser.option(:file , '-f NAME', "config file")
293
+ parser.option(:indent , '-i[=n] ', "indentation (default 2)")
294
+ args = ["-vffoo.txt", "-i", "x", "y"]
295
+ options = parser.parse(args)
296
+ ok {options} == {"verbose"=>true, "file"=>"foo.txt", "indent"=>true}
297
+ ok {args} == ["x", "y"]
298
+ end
299
+
300
+ it "[!fv5g4] return self in order to chain method call." do
301
+ parser = Benry::CLI::OptionParser.new
302
+ ret = parser.option('-v, --verbose', "set verbose mode")
303
+ ok {ret}.same?(parser)
304
+ end
305
+
306
+ end
307
+
308
+
309
+ describe '#each_option_string()' do
310
+
311
+ it "[!luro4] yields each option string and description." do
312
+ parser = Benry::CLI::OptionParser.new(_option_schemas())
313
+ #
314
+ arr = []
315
+ parser.each_option_string do |optstr, desc|
316
+ arr << [optstr, desc]
317
+ end
318
+ ok {arr} == [
319
+ ['-v, --version' , "print version"],
320
+ ['-f, --file=FILE' , "config file"],
321
+ ['-i, --indent[=N]' , "indent (default 2)"],
322
+ ]
323
+ end
324
+
325
+ end
326
+
327
+
328
+ describe '#each_option_schema()' do
329
+
330
+ it "[!ycgdm] yields each option schema." do
331
+ parser = Benry::CLI::OptionParser.new(_option_schemas())
332
+ #
333
+ arr = []
334
+ parser.each_option_schema do |schema|
335
+ arr << [schema.option_string, schema.desc]
336
+ end
337
+ ok {arr} == [
338
+ ['-v, --version' , "print version"],
339
+ ['-f, --file=FILE' , "config file"],
340
+ ['-i, --indent[=N]' , "indent (default 2)"],
341
+ ]
342
+ end
343
+
344
+ end
345
+
346
+
347
+ describe '#parse()' do
348
+
349
+ it "[!5jfhv] returns command-line options as hash object." do
350
+ p = _option_parser()
351
+ args = "-vffile.txt foo bar".split()
352
+ ok {p.parse(args)} == {"version"=>true, "file"=>"file.txt"}
353
+ args = "--file=foo.txt --version --indent=2".split()
354
+ ok {p.parse(args)} == {"version"=>true, "file"=>"foo.txt", "indent"=>"2"}
355
+ end
356
+
357
+ it "[!06iq3] removes command-line options from args." do
358
+ p = _option_parser()
359
+ args = "-vffile.txt foo bar".split()
360
+ p.parse(args)
361
+ ok {args} == ["foo", "bar"]
362
+ args = "--file=foo.txt --version --indent=2 1 2".split()
363
+ p.parse(args)
364
+ ok {args} == ["1", "2"]
365
+ end
366
+
367
+ it "[!j2fda] stops command-line parsing when '-' found in args." do
368
+ p = _option_parser()
369
+ args = "-v - -f file.txt foo bar".split()
370
+ options = p.parse(args)
371
+ ok {options} == {'version'=>true}
372
+ ok {args} == ["-", "-f", "file.txt", "foo", "bar"]
373
+ end
374
+
375
+ it "[!w5dpy] can parse long options." do
376
+ p = _option_parser()
377
+ args = "--version".split()
378
+ ok {p.parse(args)} == {"version"=>true}
379
+ args = "--file=foo.txt".split()
380
+ ok {p.parse(args)} == {"file"=>"foo.txt"}
381
+ args = "--indent".split()
382
+ ok {p.parse(args)} == {"indent"=>true}
383
+ args = "--indent=99".split()
384
+ ok {p.parse(args)} == {"indent"=>"99"}
385
+ end
386
+
387
+ it "[!mov8e] can parse short options." do
388
+ p = _option_parser()
389
+ args = "-v".split()
390
+ ok {p.parse(args)} == {"version"=>true}
391
+ args = "-f foo.txt".split()
392
+ ok {p.parse(args)} == {"file"=>"foo.txt"}
393
+ args = "-i foo bar".split()
394
+ ok {p.parse(args)} == {"indent"=>true}
395
+ args = "-i99 foo bar".split()
396
+ ok {p.parse(args)} == {"indent"=>"99"}
397
+ end
398
+
399
+ it "[!31h46] stops parsing when '--' appears in args." do
400
+ p = _option_parser()
401
+ args = "-v -- -ffile.txt foo bar".split()
402
+ ok {p.parse(args)} == {"version"=>true}
403
+ ok {args} == ["-ffile.txt", "foo", "bar"]
404
+ end
405
+
406
+ it "[!w67gl] raises error when long option is unknown." do
407
+ p = _option_parser()
408
+ pr = proc { p.parse("-v --verbose".split()) }
409
+ ok {pr}.raise?(Benry::CLI::OptionError, "--verbose: unknown option.")
410
+ pr = proc { p.parse("-v --quiet=yes".split()) }
411
+ ok {pr}.raise?(Benry::CLI::OptionError, "--quiet: unknown option.")
412
+ end
413
+
414
+ it "[!kyd1j] raises error when required argument of long option is missing." do
415
+ p = _option_parser()
416
+ pr = proc { p.parse("-v --file".split()) }
417
+ ok {pr}.raise?(Benry::CLI::OptionError, "--file: argument required.")
418
+ end
419
+
420
+ it "[!wuyrh] uses true as default value of optional argument of long option." do
421
+ p = _option_parser()
422
+ ok {p.parse("-v --indent".split())} == {"indent"=>true, "version"=>true}
423
+ end
424
+
425
+ it "[!91b2j] raises error when long option takes no argument but specified." do
426
+ p = _option_parser()
427
+ pr = proc { p.parse("-v --version=1.1".split()) }
428
+ ok {pr}.raise?(Benry::CLI::OptionError, "--version=1.1: unexpected argument.")
429
+ end
430
+
431
+ it "[!9td8b] invokes callback with long option value if callback exists." do
432
+ p = _option_parser()
433
+ ok {p.parse(["--indent=99"])} == {"indent"=>"99"}
434
+ #
435
+ arr = [
436
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "") {|value| value.to_i }
437
+ ]
438
+ p2 = Benry::CLI::OptionParser.new(arr)
439
+ ok {p2.parse(["--indent=99"])} == {"indent"=>99}
440
+ end
441
+
442
+ it "[!1hak2] invokes callback with long option values as 2nd argument." do
443
+ arr = [
444
+ Benry::CLI::OptionSchema.parse("--include=path", "") {|val, values|
445
+ (values['include'] || []) << val
446
+ }
447
+ ]
448
+ p = Benry::CLI::OptionParser.new(arr)
449
+ args = ["--include=/tmp", "--include=/var/tmp", "--include=/usr/tmp"]
450
+ ok {p.parse(args)} == {"include"=>["/tmp", "/var/tmp", "/usr/tmp"]}
451
+ end
452
+
453
+ it "[!nkqln] regards RuntimeError callback raised as long option error." do
454
+ arr = [
455
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "") {|val|
456
+ val =~ /\A\d+\z/ or raise "positive integer expected."
457
+ val.to_i
458
+ }
459
+ ]
460
+ p2 = Benry::CLI::OptionParser.new(arr)
461
+ pr = proc { p2.parse(["--indent=9.9"]) }
462
+ ok {pr}.raise?(Benry::CLI::OptionError, "--indent=9.9: positive integer expected.")
463
+ end
464
+
465
+ it "[!wr58v] raises error when unknown short option specified." do
466
+ p = _option_parser()
467
+ pr = proc { p.parse("-vx".split()) }
468
+ ok {pr}.raise?(Benry::CLI::OptionError, "-x: unknown option.")
469
+ end
470
+
471
+ it "[!jzdcr] raises error when requried argument of short option is missing." do
472
+ p = _option_parser()
473
+ pr = proc { p.parse("-vf".split()) }
474
+ ok {pr}.raise?(Benry::CLI::OptionError, "-f: argument required.")
475
+ end
476
+
477
+ it "[!hnki9] uses true as default value of optional argument of short option." do
478
+ p = _option_parser()
479
+ ok {p.parse("-i".split())} == {"indent"=>true}
480
+ end
481
+
482
+ it "[!8gj65] uses true as value of short option which takes no argument." do
483
+ p = _option_parser()
484
+ ok {p.parse("-v".split())} == {"version"=>true}
485
+ end
486
+
487
+ it "[!l6gss] invokes callback with short option value if exists." do
488
+ p = _option_parser()
489
+ ok {p.parse(["-i99"])} == {"indent"=>"99"}
490
+ #
491
+ arr = [
492
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "") {|value| value.to_i }
493
+ ]
494
+ p2 = Benry::CLI::OptionParser.new(arr)
495
+ ok {p2.parse(["-i99"])} == {"indent"=>99}
496
+ end
497
+
498
+ it "[!g4pld] invokes callback with short option values as 2nd argument." do
499
+ arr = [
500
+ Benry::CLI::OptionSchema.parse("-I path #include", "") {|val, values|
501
+ (values['include'] || []) << val
502
+ }
503
+ ]
504
+ p = Benry::CLI::OptionParser.new(arr)
505
+ args = ['-I', '/tmp', '-I', '/var/tmp', '-I/usr/tmp']
506
+ ok {p.parse(args)} == {'include'=>['/tmp', '/var/tmp', '/usr/tmp']}
507
+ end
508
+
509
+ it "[!d4mgr] regards RuntimeError callback raised as short option error." do
510
+ arr = [
511
+ Benry::CLI::OptionSchema.parse("-L, --level=N", "") {|val|
512
+ val =~ /\A\d+\z/ or raise "positive integer expected."
513
+ val.to_i
514
+ }
515
+ ]
516
+ p1 = Benry::CLI::OptionParser.new(arr)
517
+ pr = proc { p1.parse(["-L9.9"]) }
518
+ ok {pr}.raise?(Benry::CLI::OptionError, "-L 9.9: positive integer expected.")
519
+ #
520
+ arr = [
521
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "") {|val|
522
+ val =~ /\A\d+\z/ or raise "positive integer expected."
523
+ val.to_i
524
+ }
525
+ ]
526
+ p2 = Benry::CLI::OptionParser.new(arr)
527
+ pr = proc { p2.parse(["-i9.9"]) }
528
+ ok {pr}.raise?(Benry::CLI::OptionError, "-i9.9: positive integer expected.")
529
+ end
530
+
531
+ end
532
+
533
+
534
+ end
535
+
536
+
537
+
538
+ describe Benry::CLI::Action do
539
+
540
+
541
+ describe '.inherited()' do
542
+
543
+ it "[!al5pr] provides @action and @option for subclass." do
544
+ HelloAction.instance_exec(self) do |_|
545
+ _.ok {@action} != nil
546
+ _.ok {@action}.is_a?(Proc)
547
+ _.ok {@option} != nil
548
+ _.ok {@option}.is_a?(Proc)
549
+ end
550
+ end
551
+
552
+ it "[!ymtsg] allows block argument to @option." do
553
+ cls = Class.new(Benry::CLI::Action) do
554
+ @action.(:hello, "print hello")
555
+ @option.('-L, --level=N', 'level') {|val| val.to_i }
556
+ def hello(level: 1)
557
+ "level=#{level.inspect}"
558
+ end
559
+ end
560
+ cls.instance_exec(self) do |_|
561
+ arr = @__mappings[0]
562
+ _.ok {arr[0]} == :hello
563
+ _.ok {arr[1]} == "print hello"
564
+ _.ok {arr[2][1].short} == 'L'
565
+ _.ok {arr[2][1].long} == 'level'
566
+ _.ok {arr[2][1].callback}.is_a?(Proc)
567
+ _.ok {arr[2][1].callback.call("123")} == 123
568
+ end
569
+ end
570
+
571
+ it "[!v76cf] can take symbol as kwarg name." do
572
+ cls = Class.new(Benry::CLI::Action) do
573
+ @action.(:hello, "print hello")
574
+ @option.(:level, '-L N', 'level number') {|val| val.to_i }
575
+ def hello(level: 1)
576
+ "level=#{level.inspect}"
577
+ end
578
+ end
579
+ cls.instance_exec(self) do |_|
580
+ arr = @__mappings[0]
581
+ _.ok {arr[0]} == :hello
582
+ _.ok {arr[1]} == "print hello"
583
+ _.ok {arr[2][1].name} == 'level'
584
+ _.ok {arr[2][1].short} == 'L'
585
+ _.ok {arr[2][1].long} == nil
586
+ _.ok {arr[2][1].callback}.is_a?(Proc)
587
+ _.ok {arr[2][1].callback.call("123")} == 123
588
+ end
589
+ end
590
+
591
+ it "[!di9na] raises error when @option.() called without @action.()." do
592
+ pr = proc do
593
+ Class.new(Benry::CLI::Action) do
594
+ @option.('-v, --verbose', "verbose mode")
595
+ def hello(verbose)
596
+ end
597
+ end
598
+ end
599
+ ok {pr}.raise?(Benry::CLI::OptionDefinitionError,
600
+ '@option.("-v, --verbose"): @action.() should be called prior to @option.().')
601
+ end
602
+
603
+ it "[!4otr6] registers subclass." do
604
+ ok {Benry::CLI::Action::SUBCLASSES}.include?(HelloAction)
605
+ ok {Benry::CLI::Action::SUBCLASSES}.include?(HelloSubAction)
606
+ end
607
+
608
+ end
609
+
610
+
611
+ describe '.method_added()' do
612
+
613
+ it "[!syzvc] registers action with method." do
614
+ cls = Class.new(Benry::CLI::Action) do
615
+ @action.("hello", "print hello message")
616
+ @option.('-n, --name=NAME', "user name")
617
+ def do_hello(name: "World")
618
+ puts "Hello, #{name}!"
619
+ end
620
+ end
621
+ cls.instance_exec(self) do |_|
622
+ _.ok {@__mappings} == [
623
+ [
624
+ 'hello',
625
+ 'print hello message',
626
+ [Benry::CLI::OptionSchema.new('help', 'h', 'help', nil, nil, 'print help message'),
627
+ Benry::CLI::OptionSchema.new('name', 'n', 'name', 'NAME', :required, 'user name')],
628
+ :do_hello,
629
+ ],
630
+ ]
631
+ end
632
+ end
633
+
634
+ it "[!m7y8p] clears current action definition." do
635
+ _ = self
636
+ cls = Class.new(Benry::CLI::Action) do
637
+ _.ok {@__defining} == nil
638
+ @action.("hello", "print hello message")
639
+ @option.('-n, --name=NAME', "user name")
640
+ _.ok {@__defining} != nil
641
+ def do_hello
642
+ end
643
+ _.ok {@__defining} == nil
644
+ end
645
+ end
646
+
647
+ end
648
+
649
+
650
+ end
651
+
652
+
653
+
654
+ describe Benry::CLI::ActionInfo do
655
+
656
+
657
+ describe '#help_message()' do
658
+
659
+ def _new_info()
660
+ schemas = [
661
+ Benry::CLI::OptionSchema.parse("-v, --verbose", "verbose mode"),
662
+ Benry::CLI::OptionSchema.parse("-f, --file=NAME", "file name"),
663
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "indent (default 2)"),
664
+ ]
665
+ cls = Class.new(Benry::CLI::Action) do
666
+ def do_git_switch(aa, bb, cc=nil, dd=nil, *args, verbose: false, file: nil, indent: 2)
667
+ end
668
+ end
669
+ return Benry::CLI::ActionInfo.new('git:switch', 'switch', 'switch git branch',
670
+ schemas, cls, :do_git_switch)
671
+ end
672
+
673
+ it "[!hjq5l] builds help message." do
674
+ expected = <<END
675
+ switch git branch
676
+
677
+ Usage:
678
+ script-name git:switch [<options>] <aa> <bb> [<cc>] [<dd>] [<args>...]
679
+
680
+ Options:
681
+ -v, --verbose : verbose mode
682
+ -f, --file=NAME : file name
683
+ -i, --indent[=N] : indent (default 2)
684
+ END
685
+ info = _new_info()
686
+ ok {info.help_message('script-name')} == expected
687
+ end
688
+
689
+ it "[!7qmnz] replaces '_' in arg names with '-'." do
690
+ schemas = []
691
+ cls = Class.new(Benry::CLI::Action) do
692
+ def do_something(tmp_file_name, tmp_file_dir='/tmp')
693
+ end
694
+ end
695
+ action_info = Benry::CLI::ActionInfo.new('my:hom', 'hom', 'do something',
696
+ schemas, cls, :do_something)
697
+ s = action_info.help_message("script")
698
+ ok {s} =~ "Usage:\n script my:hom [<options>] <tmp-file-name> [<tmp-file-dir>]"
699
+ end
700
+
701
+ it "[!s6p09] converts arg name 'file_or_dir' into 'file|dir'." do
702
+ schemas = []
703
+ cls = Class.new(Benry::CLI::Action) do
704
+ def do_something(file_or_directory, name_or_id=nil)
705
+ end
706
+ end
707
+ action_info = Benry::CLI::ActionInfo.new('my:hom', 'hom', 'do something',
708
+ schemas, cls, :do_something)
709
+ s = action_info.help_message("script")
710
+ ok {s} =~ "Usage:\n script my:hom [<options>] <file|directory> [<name|id>]"
711
+ end
712
+
713
+ it "[!6m50d] don't show non-described options." do
714
+ schemas = [
715
+ Benry::CLI::OptionSchema.parse("-v, --verbose", "verbose mode"),
716
+ Benry::CLI::OptionSchema.parse("-f, --file=NAME", nil), # hidden option
717
+ Benry::CLI::OptionSchema.parse("-i, --indent[=N]", "indent (default 2)"),
718
+ ]
719
+ cls = Class.new(Benry::CLI::Action) do
720
+ def do_something(verbose: false, file: nil, indent: nil)
721
+ end
722
+ end
723
+ action_info = Benry::CLI::ActionInfo.new('foo', 'foo', 'Do something',
724
+ schemas, cls, :do_something)
725
+ s = action_info.help_message("script")
726
+ ok {s} == <<END
727
+ Do something
728
+
729
+ Usage:
730
+ script foo [<options>]
731
+
732
+ Options:
733
+ -v, --verbose : verbose mode
734
+ -i, --indent[=N] : indent (default 2)
735
+ END
736
+ ok {s}.NOT =~ /--file/
737
+ end
738
+
739
+ end
740
+
741
+
742
+ end
743
+
744
+
745
+ describe Benry::CLI::Application do
746
+
747
+
748
+ describe '.inherited()' do
749
+
750
+ it "[!b09pv] provides @global_option in subclass." do
751
+ cls = Class.new(Benry::CLI::Application)
752
+ cls.instance_exec(self) do |_|
753
+ _.ok {@global_option} != nil
754
+ _.ok {@global_option}.is_a?(Proc)
755
+ end
756
+ end
757
+
758
+ it "[!8swia] global option '-h' and '--help' are enabled by default." do
759
+ cls = Class.new(Benry::CLI::Application)
760
+ cls.instance_exec(self) do |_|
761
+ _.ok {@_global_option_schemas[0].short} == 'h'
762
+ _.ok {@_global_option_schemas[0].long} == 'help'
763
+ end
764
+ end
765
+
766
+ it "[!vh08n] global option '--version' is enabled by defaut." do
767
+ cls = Class.new(Benry::CLI::Application)
768
+ cls.instance_exec(self) do |_|
769
+ _.ok {@_global_option_schemas[1].short} == nil
770
+ _.ok {@_global_option_schemas[1].long} == 'version'
771
+ end
772
+ end
773
+
774
+ end
775
+
776
+
777
+ describe '#accept()' do
778
+
779
+ it "[!ue26k] builds action dictionary." do
780
+ app = Benry::CLI::Application.new
781
+ d = app.__send__(:accept, [GitAction])
782
+ ok {d}.is_a?(Hash)
783
+ ok {d.keys().sort} == ['git:fork', 'git:join', 'git:switch']
784
+ ok {d['git:fork']} == Benry::CLI::ActionInfo.new(
785
+ 'git:fork', 'fork', 'create and switch to new branch',
786
+ [Benry::CLI::OptionSchema.parse('-h, --help', 'print help message'),
787
+ Benry::CLI::OptionSchema.parse('-v, --verbose', 'verbose mode')],
788
+ GitAction, :do_git_fork
789
+ )
790
+ ok {d['git:switch']} == Benry::CLI::ActionInfo.new(
791
+ 'git:switch', 'switch', 'switch git branch',
792
+ [Benry::CLI::OptionSchema.parse('-h, --help', 'print help message'),
793
+ Benry::CLI::OptionSchema.parse('-v, --verbose', 'verbose mode')],
794
+ GitAction, :do_git_switch
795
+ )
796
+ end
797
+
798
+ it "[!x6rh1] registers action name replacing '_' with '-'." do
799
+ cls = Class.new(Benry::CLI::Action) do
800
+ @action.(:bla_bla_bla, "bla bla bla")
801
+ def do_xx()
802
+ return "OK"
803
+ end
804
+ end
805
+ app = Benry::CLI::Application.new
806
+ d = app.__send__(:accept, [cls])
807
+ ok {d}.is_a?(Hash)
808
+ ok {d.keys().sort} == ['bla-bla-bla']
809
+ #
810
+ app = Benry::CLI::Application.new(nil, action_classes: [cls])
811
+ output1 = app.run('--help')
812
+ ok {output1} =~ /Actions:\n bla-bla-bla +: bla bla bla\n/
813
+ output2 = app.run('bla-bla-bla')
814
+ ok {output2} == "OK"
815
+ end
816
+
817
+ end
818
+
819
+
820
+ describe '#run()' do
821
+
822
+ def _argtest_action_class
823
+ return Class.new(Benry::CLI::Action) do
824
+ @action.(:hello1, "hello1")
825
+ @option.('-f, --file=NAME', 'filename')
826
+ @option.('-i, --indent[=N]', 'indent (default 2)')
827
+ def do_hello1(aa, bb, cc=nil, dd=nil, file: nil, indent: 2)
828
+ return ("aa=#{aa.inspect}, bb=#{bb.inspect}, cc=#{cc.inspect}, dd=#{dd.inspect}"+\
829
+ ", file=#{file.inspect}, indent=#{indent.inspect}")
830
+ end
831
+ #
832
+ @action.(:hello2, "hello2")
833
+ @option.('-f, --file=NAME', 'filename')
834
+ @option.('-i, --indent[=N]', 'indent (default 2)')
835
+ def do_hello2(aa, bb, cc=nil, dd=nil, *args, file: nil, indent: 2)
836
+ return ("aa=#{aa.inspect}, bb=#{bb.inspect}, cc=#{cc.inspect}, dd=#{dd.inspect}"+\
837
+ ", args=#{args.inspect}, file=#{file.inspect}, indent=#{indent.inspect}")
838
+ end
839
+ #
840
+ @action.(:hello3, "hello3")
841
+ @option.('-L, --debug-log-level=N', 'log level')
842
+ def do_hello3(debug_log_level: 1)
843
+ return "debug_log_level=#{debug_log_level.inspect}"
844
+ end
845
+ end
846
+ end
847
+
848
+ it "[!b8isy] returns help message when global option '-h' or '--help' is specified." do
849
+ expected = <<END
850
+ Usage:
851
+ cli_test.rb [<options>] <action> [<args>...]
852
+
853
+ Options:
854
+ -h, --help : print help message
855
+ --version : print version
856
+
857
+ Actions:
858
+ git:fork : create and switch to new branch
859
+ git:join : merge branch with --no-ff
860
+ git:switch : switch git branch
861
+
862
+ (Run `cli_test.rb help <action>' to show help message of each action.)
863
+ END
864
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
865
+ output = app.run('--help')
866
+ ok {output} == expected
867
+ output = app.run('-h')
868
+ ok {output} == expected
869
+ end
870
+
871
+ it "[!4irzw] returns version string when global option '--version' is specified." do
872
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
873
+ output = app.run('--version')
874
+ ok {output} == "0.0"
875
+ #
876
+ app = Benry::CLI::Application.new(version: '1.2.3', action_classes: [GitAction])
877
+ output = app.run('--version')
878
+ ok {output} == "1.2.3"
879
+ end
880
+
881
+ it "[!p5pr6] returns global help message when action is 'help'." do
882
+ expected = <<END
883
+ Usage:
884
+ cli_test.rb [<options>] <action> [<args>...]
885
+
886
+ Options:
887
+ -h, --help : print help message
888
+ --version : print version
889
+
890
+ Actions:
891
+ git:fork : create and switch to new branch
892
+ git:join : merge branch with --no-ff
893
+ git:switch : switch git branch
894
+
895
+ (Run `cli_test.rb help <action>' to show help message of each action.)
896
+ END
897
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
898
+ output = app.run('help')
899
+ ok {output} == expected
900
+ end
901
+
902
+ it "[!3hyvi] returns help message of action when action is 'help' with action name." do
903
+ expected = <<END
904
+ create and switch to new branch
905
+
906
+ Usage:
907
+ cli_test.rb git:fork [<options>] <branch>
908
+
909
+ Options:
910
+ -h, --help : print help message
911
+ -v, --verbose : verbose mode
912
+ END
913
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
914
+ output = app.run('help', 'git:fork')
915
+ ok {output} == expected
916
+ end
917
+
918
+ it "[!mb92l] raises error when action name is unknown." do
919
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
920
+ pr = proc { app.run('fork') }
921
+ ok {pr}.raise?(Benry::CLI::OptionError, "fork: unknown action.")
922
+ end
923
+
924
+ it "[!13m3q] returns help message if '-h' or '--help' specified to action." do
925
+ expected = <<END
926
+ create and switch to new branch
927
+
928
+ Usage:
929
+ cli_test.rb git:fork [<options>] <branch>
930
+
931
+ Options:
932
+ -h, --help : print help message
933
+ -v, --verbose : verbose mode
934
+ END
935
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
936
+ output = app.run('git:fork', '--help')
937
+ ok {output} == expected
938
+ end
939
+
940
+ it "[!yhry7] raises error when required argument is missing." do
941
+ cls = _argtest_action_class()
942
+ app = Benry::CLI::Application.new(action_classes: [cls])
943
+ pr = proc { app.run('hello1') }
944
+ ok {pr}.raise?(Benry::CLI::OptionError,
945
+ "too few arguments (at least 2 args expected).\n" +\
946
+ "(run `cli_test.rb help hello1' for details.)")
947
+ pr = proc { app.run('hello1', "x") }
948
+ ok {pr}.raise?(Benry::CLI::OptionError,
949
+ "too few arguments (at least 2 args expected).\n" +\
950
+ "(run `cli_test.rb help hello1' for details.)")
951
+ pr = proc { app.run('hello1', "x", "y") }
952
+ ok {pr}.NOT.raise?(Exception)
953
+ end
954
+
955
+ it "[!h5522] raises error when too much arguments specified." do
956
+ cls = _argtest_action_class()
957
+ app = Benry::CLI::Application.new(action_classes: [cls])
958
+ pr = proc { app.run('hello1', "x1", "x2", "x3", "x4", "x5") }
959
+ ok {pr}.raise?(Benry::CLI::OptionError,
960
+ "too many arguments (at most 4 args expected).\n" +\
961
+ "(run `cli_test.rb help hello1' for details.)")
962
+ end
963
+
964
+ it "[!hq8b0] not raise error when many argument specified but method has *args." do
965
+ cls = _argtest_action_class()
966
+ app = Benry::CLI::Application.new(action_classes: [cls])
967
+ pr = proc { app.run('hello2', "x1", "x2", "x3", "x4", "x5", "x6") }
968
+ ok {pr}.NOT.raise?(Exception)
969
+ end
970
+
971
+ it "[!qwd9x] passes command arguments and options as method arguments and options." do
972
+ cls = _argtest_action_class()
973
+ app = Benry::CLI::Application.new(action_classes: [cls])
974
+ #
975
+ output = app.run('hello1', "-ffoo.txt", "x1", "x2")
976
+ ok {output} == 'aa="x1", bb="x2", cc=nil, dd=nil, file="foo.txt", indent=2'
977
+ output = app.run('hello1', "-i", "x1", "x2", "x3")
978
+ ok {output} == 'aa="x1", bb="x2", cc="x3", dd=nil, file=nil, indent=true'
979
+ #
980
+ output = app.run('hello2', "-ffoo.txt", "x1", "x2", "x3", "x4", "x5", "x6")
981
+ ok {output} == 'aa="x1", bb="x2", cc="x3", dd="x4", args=["x5", "x6"], file="foo.txt", indent=2'
982
+ end
983
+
984
+ it "[!rph9y] converts 'foo-bar' option name into :foo_bar keyword." do
985
+ cls = _argtest_action_class()
986
+ app = Benry::CLI::Application.new(action_classes: [cls])
987
+ pr = proc { app.run('hello3', "-L30") }
988
+ ok {pr}.NOT.raise?(Exception)
989
+ output = pr.call()
990
+ ok {output} == 'debug_log_level="30"'
991
+ end
992
+
993
+ end
994
+
995
+
996
+ describe '#help_message()' do
997
+
998
+ it "[!1zpv4] adds command desc if it is specified at initializer." do
999
+ app = Benry::CLI::Application.new("my git wrapper", action_classes: [GitAction])
1000
+ s = app.help_message('mygit')
1001
+ ok {s} =~ /\Amy git wrapper\n\nUsage:/
1002
+ #
1003
+ app = Benry::CLI::Application.new(action_classes: [GitAction])
1004
+ s = app.help_message('mygit')
1005
+ ok {s} =~ /\AUsage:/
1006
+ end
1007
+
1008
+ it "[!m3mry] skips action name when description is not provided." do
1009
+ cls = Class.new(Benry::CLI::Action) do
1010
+ @action.(:test1, nil)
1011
+ def do_test1; end
1012
+ @action.(:test2, "")
1013
+ def do_test2; end
1014
+ end
1015
+ app = Benry::CLI::Application.new(action_classes: [cls])
1016
+ s = app.help_message('script')
1017
+ ok {s} !~ /test1/
1018
+ ok {s} =~ /test2/
1019
+ ok {s} =~ /^Actions:\n test2 +: +\n/
1020
+ end
1021
+
1022
+ end
1023
+
1024
+
1025
+ end