benry-cli 0.1.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.
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