benry-cmdopt 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,37 +1,288 @@
1
- Benry::Cmdopt README
2
- ====================
1
+ # Benry-CmdOpt
3
2
 
4
- ($Release: 1.0.0 $)
3
+ ($Release: 2.0.0 $)
5
4
 
6
- Benry::Cmdopt is a command option parser library, like `optparse.rb`.
7
5
 
8
- Compared to `optparse.rb`:
9
6
 
10
- * Easy to use, easy to extend, easy to understand.
11
- * Not add `-h` nor `--help` automatically.
12
- * Not add `-v` nor `--version` automatically.
13
- * Not regard `-x` as short cut of `--xxx`.
14
- (`optparser.rb` regards `-x` as short cut of `--xxx` automatically.)
15
- * Provides very simple feature to build custom help message.
16
- * Separates command option schema class from parser class.
7
+ ## Overview
17
8
 
18
- (Benry::Cmdopt requires Ruby >= 2.3)
9
+ Benry-CmdOpt is a command option parser library, like `optparse.rb`
10
+ (Ruby standard library).
19
11
 
12
+ Compared to `optparse.rb`, Benry-CmdOpt is easy to use, easy to extend,
13
+ and easy to understahnd.
20
14
 
21
- Usage
22
- =====
15
+ * Document: <https://kwatch.github.io/benry-ruby/benry-cmdopt.html>
16
+ * GitHub: <https://github.com/kwatch/benry-ruby/tree/main/benry-cmdopt>
17
+ * Changes: <https://github.com/kwatch/benry-ruby/tree/main/benry-cmdopt/CHANGES.md>
23
18
 
19
+ Benry-CmdOpt requires Ruby >= 2.3.
24
20
 
25
- Define, parse, and print help
26
- -----------------------------
21
+
22
+
23
+ ## Table of Contents
24
+
25
+ <!-- TOC -->
26
+
27
+ * [Overview](#overview)
28
+ * [Why not `optparse.rb`?](#why-not-optparserb)
29
+ * [Install](#install)
30
+ * [Usage](#usage)
31
+ * [Define, Parse, and Print Help](#define-parse-and-print-help)
32
+ * [Command Option Parameter](#command-option-parameter)
33
+ * [Argument Validation](#argument-validation)
34
+ * [Boolean (on/off) Option](#boolean-onoff-option)
35
+ * [Alternative Value](#alternative-value)
36
+ * [Multiple Value Option](#multiple-value-option)
37
+ * [Hidden Option](#hidden-option)
38
+ * [Global Options with Sub-Commands](#global-options-with-sub-commands)
39
+ * [Detailed Description of Option](#detailed-description-of-option)
40
+ * [Option Tag](#option-tag)
41
+ * [Not Supported](#not-supported)
42
+ * [Internal Classes](#internal-classes)
43
+ * [License and Copyright](#license-and-copyright)
44
+
45
+ <!-- /TOC -->
46
+
47
+
48
+
49
+ ## Why not `optparse.rb`?
50
+
51
+ * `optparse.rb` can handle both `--name=val` and `--name val` styles.
52
+ The later style is ambiguous; you may wonder whether `--name` takes
53
+ `val` as argument or `--name` takes no argument (and `val` is command
54
+ argument).
55
+
56
+ Therefore the `--name=val` style is better than the `--name val` style.
57
+
58
+ `optparse.rb` cannot disable `--name val` style.
59
+ `benry/cmdopt.rb` supports only `--name=val` style.
60
+
61
+ * `optparse.rb` regards `-x` and `--x` as a short cut of `--xxx` automatically
62
+ even if you have not defined `-x` option.
63
+ That is, short options which are not defined can be available unexpectedly.
64
+ This feature is hard-coded in `OptionParser#parse_in_order()`
65
+ and hard to be disabled.
66
+
67
+ In contact, `benry/cmdopt.rb` doesn't behave this way.
68
+ `-x` option is available only when `-x` is defined.
69
+ `benry/cmdopt.rb` does nothing superfluous.
70
+
71
+ * `optparse.rb` uses long option name as hash key automatically, but
72
+ it doesn't provide the way to specify hash key for short-only option.
73
+
74
+ `benry/cmdopt.rb` can specify hash key for short-only option.
75
+
76
+ ```ruby
77
+ ### optparse.rb
78
+ require 'optparse'
79
+ parser = OptionParser.new
80
+ parser.on('-v', '--verbose', "verbose mode") # short and long option
81
+ parser.on('-q', "quiet mode") # short-only option
82
+ #
83
+ opts = {}
84
+ parser.parse!(['-v'], into: opts) # short option
85
+ p opts #=> {:verbose=>true} # hash key is long option name
86
+ #
87
+ opts = {}
88
+ parser.parse!(['-q'], into: opts) # short option
89
+ p opts #=> {:q=>true} # hash key is short option name
90
+
91
+ ### benry/cmdopt.rb
92
+ require 'benry/cmdopt'
93
+ cmdopt = Benry::CmdOpt.new
94
+ cmdopt.add(:verbose, '-v, --verbose', "verbose mode") # short and long
95
+ cmdopt.add(:quiet , '-q' , "quiet mode") # short-only
96
+ #
97
+ opts = cmdopt.parse(['-v']) # short option
98
+ p opts #=> {:verbose=>true} # independent hash key of option name
99
+ #
100
+ opts = cmdopt.parse(['-q']) # short option
101
+ p opts #=> {:quiet=>true} # independent hash key of option name
102
+ ```
103
+
104
+ * `optparse.rb` provides severay ways to validate option values, such as
105
+ type class, Regexp as pattern, or Array/Set as enum. But it doesn't
106
+ accept Range object. This means that, for examle, it is not simple to
107
+ validate whether integer or float value is positive or not.
108
+
109
+ In contract, `benry/cmdopt.rb` accepts Range object so it is very simple
110
+ to validate whether integer or float value is positive or not.
111
+
112
+ ```ruby
113
+ ### optparse.rb
114
+ parser = OptionParser.new
115
+ parser.on('-n <N>', "number", Integer, (1..)) #=> NoMethodError
116
+
117
+ ### benry/cmdopt.rb
118
+ require 'benry/cmdopt'
119
+ cmdopt = Benry::CmdOpt.new
120
+ cmdopt.add(:number, "-n <N>", "number", type: Integer, range: (1..)) #=> ok
121
+ ```
122
+
123
+ * `optparse.rb` accepts Array or Set object as enum values. But values
124
+ of enum should be a String in spite that type class specified.
125
+ This seems very strange and not intuitive.
126
+
127
+ `benry/cmdopt.rb` accepts integer values as enum when type class is Integer.
128
+
129
+ ```ruby
130
+ ### optparse.rb
131
+ parser = OptionParser.new
132
+ parser.on('-n <N>', "number", Integer, [1, 2, 3]) # wrong
133
+ parser.on('-n <N>', "number", Integer, ['1','2','3']) # ok (but not intuitive)
134
+
135
+ ### benry/cmdopt.rb
136
+ require 'benry/cmdopt'
137
+ cmdopt = Benry::CmdOpt.new
138
+ cmdopt.add(:number, "-n <N>", "number", type: Integer, enum: [1, 2, 3]) # very intuitive
139
+ ```
140
+
141
+ * `optparse.rb` adds `-h` and `--help` options automatically, and
142
+ terminates current process when `-h` or `--help` specified in command-line.
143
+ It is hard to remove these options.
144
+
145
+ In contract, `benry/cmdopt.rb` does not add these options.
146
+ `benry/cmdopt.rb` does nothing superfluous.
147
+
148
+ ```ruby
149
+ require 'optparse'
150
+ parser = OptionParser.new
151
+ ## it is able to overwrite '-h' and/or '--help',
152
+ ## but how to remove or disable these options?
153
+ opts = {}
154
+ parser.on('-h <host>', "hostname") {|v| opts[:host] = v }
155
+ parser.parse(['--help']) # <== terminates current process!!
156
+ puts 'xxx' #<== not printed because current process alreay terminated
157
+ ```
158
+
159
+ * `optparse.rb` adds `-v` and `--version` options automatically, and
160
+ terminates current process when `-v` or `--version` specified in terminal.
161
+ It is hard to remove these options.
162
+ This behaviour is not desirable because `optparse.rb` is just a library,
163
+ not framework.
164
+
165
+ In contract, `benry/cmdopt.rb` does not add these options.
166
+ `benry/cmdopt.rb` does nothing superfluous.
167
+
168
+ ```ruby
169
+ require 'optparse'
170
+ parser = OptionParser.new
171
+ ## it is able to overwrite '-v' and/or '--version',
172
+ ## but how to remove or disable these options?
173
+ opts = {}
174
+ parser.on('-v', "verbose mode") { opts[:verbose] = true }
175
+ parser.parse(['--version']) # <== terminates current process!!
176
+ puts 'xxx' #<== not printed because current process alreay terminated
177
+ ```
178
+
179
+ * `optparse.rb` generates help message automatically, but it doesn't
180
+ contain `-h`, `--help`, `-v`, nor `--version`.
181
+ These options are available but not shown in help message. Strange.
182
+
183
+ * `optparse.rb` generate help message which contains command usage string
184
+ such as `Usage: <command> [options]`. `optparse.rb` should NOT include
185
+ it in help message because it is just a library, not framework.
186
+ If you want to change '[options]' to '[<options>]', you must manipulate
187
+ help message string by yourself.
188
+
189
+ `benry/cmdopt.rb` doesn't include extra text (such as usage text) into
190
+ help message. `benry/cmdopt.rb` does nothing superfluous.
191
+
192
+ * `optparse.rb` generates help message with too wide option name
193
+ by default. You must specify proper width.
194
+
195
+ `benry/cmdopt.rb` calculates proper width automatically.
196
+ You don't need to specify proper width in many case.
197
+
198
+ ```ruby
199
+ ### optparse.rb
200
+ require 'optparse'
201
+ banner = "Usage: blabla <options>"
202
+ parser = OptionParser.new(banner) # or: OptionParser.new(banner, 25)
203
+ parser.on('-f', '--file=<FILE>', "filename")
204
+ parser.on('-m <MODE>' , "verbose/quiet")
205
+ puts parser.help
206
+ ### output
207
+ # Usage: blabla <options>
208
+ # -f, --file=<FILE> filename
209
+ # -m <MODE> verbose/quiet
210
+
211
+ ### benry/cmdopt.rb
212
+ require 'benry/cmdopt'
213
+ cmdopt = Benry::CmdOpt.new()
214
+ cmdopt.add(:file, '-f, --file=<FILE>', "filename")
215
+ cmdopt.add(:mode, '-m <MODE>' , "verbose/quiet")
216
+ puts "Usage: blabla [<options>]"
217
+ puts cmdopt.to_s()
218
+ ### output (calculated proper width)
219
+ # Usage: blabla [<options>]
220
+ # -f, --file=<FILE> : filename
221
+ # -m <MODE> : verbose/quiet
222
+ ```
223
+
224
+ * `optparse.rb` enforces you to catch `OptionParser::ParseError` exception.
225
+ That is, you must know the error class name.
226
+
227
+ `benry/cmdopt.rb` provides error handler without exception class name.
228
+ You don't need to know the error class name on error handling.
229
+
230
+ ```ruby
231
+ ### optparse.rb
232
+ require 'optparse'
233
+ parser = OptionParser.new
234
+ parser.on('-f', '--file=<FILE>', "filename")
235
+ opts = {}
236
+ begin
237
+ parser.parse!(ARGV, into: opts)
238
+ rescue OptionParser::ParseError => err # specify error class
239
+ $stderr.puts "ERROR: #{err.message}"
240
+ exit 1
241
+ end
242
+
243
+ ### benry/cmdopt.rb
244
+ require 'benry/cmdopt'
245
+ cmdopt = Benry::CmdOpt.new
246
+ cmdopt.add(:file, '-f, --file=<FILE>', "filename")
247
+ opts = cmdopt.parse(ARGV) do |err| # error handling wihtout error class name
248
+ $stderr.puts "ERROR: #{err.message}"
249
+ exit 1
250
+ end
251
+ ```
252
+
253
+ * Source code of `optparse.rb` is very large and complicated, because
254
+ `OptParse` class does everything about command option parsing.
255
+ It is hard to customize or extend `OptionParser` class.
256
+
257
+ In contract, `benry/cmdopt.rb` consists of several classes
258
+ (schema class, parser class, and facade class).
259
+ Therefore it is easy to understand and extend these classes.
260
+
261
+ File `optparse.rb` (in Ruby 3.2) contains 1143 lines (except comments and blanks),
262
+ while `benry/cmdopt.rb` (v2.0) contains 427 lines (except both, too).
263
+
264
+
265
+
266
+ ## Install
267
+
268
+ ```
269
+ $ gem install benry-cmdopt
270
+ ```
271
+
272
+
273
+
274
+ ## Usage
275
+
276
+
277
+ ### Define, Parse, and Print Help
27
278
 
28
279
  ```ruby
29
280
  require 'benry/cmdopt'
30
281
 
31
282
  ## define
32
- cmdopt = Benry::Cmdopt.new
33
- cmdopt.add(:help , "-h, --help" , "print help message")
34
- cmdopt.add(:version, " --version", "print version")
283
+ cmdopt = Benry::CmdOpt.new
284
+ cmdopt.add(:help , '-h, --help' , "print help message")
285
+ cmdopt.add(:version, ' --version', "print version")
35
286
 
36
287
  ## parse with error handling
37
288
  options = cmdopt.parse(ARGV) do |err|
@@ -46,118 +297,375 @@ if options[:help]
46
297
  puts "Usage: foobar [<options>] [<args>...]"
47
298
  puts ""
48
299
  puts "Options:"
49
- puts cmdopt.build_option_help()
50
- ## or
300
+ puts cmdopt.to_s()
301
+ ## or: puts cmdopt.to_s(20) # width
302
+ ## or: puts cmdopt.to_s(" %-20s : %s") # format
303
+ ## or:
51
304
  #format = " %-20s : %s"
52
- #cmdopt.each_option_help {|opt, help| puts format % [opt, help] }
305
+ #cmdopt.each_option_and_desc {|opt, help| puts format % [opt, help] }
53
306
  end
54
307
  ```
55
308
 
309
+ You can set `nil` to option name only if long option specified.
310
+
311
+ ```ruby
312
+ ## both are same
313
+ cmdopt.add(:help, "-h, --help", "print help message")
314
+ cmdopt.add(nil , "-h, --help", "print help message")
315
+ ```
316
+
56
317
 
57
- Command option parameter
58
- ------------------------
318
+ ### Command Option Parameter
59
319
 
60
320
  ```ruby
61
321
  ## required parameter
62
- cmdopt.add(:file, "-f, --file=<FILE>", "filename")
63
- cmdopt.add(:file, " --file=<FILE>", "filename")
64
- cmdopt.add(:file, "-f <FILE>" , "filename")
322
+ cmdopt.add(:file, '-f, --file=<FILE>', "filename") # short & long
323
+ cmdopt.add(:file, ' --file=<FILE>', "filename") # long only
324
+ cmdopt.add(:file, '-f <FILE>' , "filename") # short only
65
325
 
66
326
  ## optional parameter
67
- cmdopt.add(:file, "-f, --file[=<FILE>]", "filename")
68
- cmdopt.add(:file, " --file[=<FILE>]", "filename")
69
- cmdopt.add(:file, "-f[<FILE>]" , "filename")
327
+ cmdopt.add(:indent, '-i, --indent[=<N>]', "indent width") # short & long
328
+ cmdopt.add(:indent, ' --indent[=<N>]', "indent width") # long only
329
+ cmdopt.add(:indent, '-i[<N>]' , "indent width") # short only
70
330
  ```
71
331
 
332
+ Notice that `"--file <FILE>"` style is **not supported for usability reason**.
333
+ Use `"--file=<FILE>"` style instead.
72
334
 
73
- Argument varidation
74
- -------------------
335
+ (From a usability perspective, the former style should not be supported.
336
+ `optparse.rb` is wrong because it supports both styles
337
+ and doesn't provide the way to disable the former style.)
338
+
339
+
340
+ ### Argument Validation
75
341
 
76
342
  ```ruby
77
- ## type
78
- cmdopt.add(:indent , "-i <N>", "indent width", type: Integer)
79
- ## pattern
80
- cmdopt.add(:indent , "-i <N>", "indent width", pattern: /\A\d+\z/)
81
- ## enum
82
- cmdopt.add(:indent , "-i <N>", "indent width", enum: [2, 4, 8])
343
+ ## type (class)
344
+ cmdopt.add(:indent , '-i <N>', "indent width", type: Integer)
345
+ ## pattern (regular expression)
346
+ cmdopt.add(:indent , '-i <N>', "indent width", rexp: /\A\d+\z/)
347
+ ## enum (Array or Set)
348
+ cmdopt.add(:indent , '-i <N>', "indent width", enum: ["2", "4", "8"])
349
+ ## range (endless range such as ``1..`` available)
350
+ cmdopt.add(:indent , '-i <N>', "indent width", range: (0..8))
83
351
  ## callback
84
- cmdopt.add(:indent , "-i <N>", "indent width") {|val|
352
+ cmdopt.add(:indent , '-i <N>', "indent width") {|val|
85
353
  val =~ /\A\d+\z/ or
86
- raise "integer expected." # raise without exception class.
354
+ raise "Integer expected." # raise without exception class.
87
355
  val.to_i # convert argument value.
88
356
  }
89
357
  ```
90
358
 
359
+ (For backward compatibilidy, keyword parameter `pattern:` is available
360
+ which is same as `rexp:`.)
91
361
 
92
- Available types
93
- ---------------
362
+ `type:` keyword argument accepts the following classes.
94
363
 
95
364
  * Integer (`/\A[-+]?\d+\z/`)
96
365
  * Float (`/\A[-+]?(\d+\.\d*\|\.\d+)z/`)
97
366
  * TrueClass (`/\A(true|on|yes|false|off|no)\z/`)
98
367
  * Date (`/\A\d\d\d\d-\d\d?-\d\d?\z/`)
99
368
 
369
+ Notice that Ruby doesn't have Boolean class.
370
+ Benry-CmdOpt uses TrueClass instead.
371
+
372
+ In addition:
373
+
374
+ * Values of `enum:` or `range:` should match to type class specified by `type:`.
375
+ * When `type:` is not specified, then String class will be used instead.
376
+
377
+ ```ruby
378
+ ## ok
379
+ cmdopt.add(:lang, '-l <lang>', "language", enum: ["en", "fr", "it"])
380
+
381
+ ## error: enum values are not Integer
382
+ cmdopt.add(:lang, '-l <lang>', "language", enum: ["en", "fr", "it"], type: Integer)
383
+
384
+ ## ok
385
+ cmdopt.add(:indent, '-i <N>', "indent", range: (0..), type: Integer)
386
+
387
+ ## error: beginning value of range is not a String
388
+ cmdopt.add(:indent, '-i <N>', "indent", range: (0..))
389
+ ```
390
+
391
+
392
+ ### Boolean (on/off) Option
393
+
394
+ Benry-CmdOpt doens't support `--no-xxx` style option for usability reason.
395
+ Use boolean option instead.
396
+
397
+ ex3.rb:
398
+
399
+ ```ruby
400
+ require 'benry/cmdopt'
401
+ cmdopt = Benry::CmdOpt.new()
402
+ cmdopt.add(:foo, "--foo[=on|off]", "foo feature", type: TrueClass) # !!!!
403
+ ## or:
404
+ #cmdopt.add(:foo, "--foo=<on|off>", "foo feature", type: TrueClass)
405
+ options = cmdopt.parse(ARGV)
406
+ p options
407
+ ```
408
+
409
+ Output example:
410
+
411
+ ```terminal
412
+ $ ruby ex3.rb --foo # enable
413
+ {:foo=>true}
414
+ $ ruby ex3.rb --foo=on # enable
415
+ {:foo=>true}
416
+ $ ruby ex3.rb --foo=off # disable
417
+ {:foo=>false}
418
+ ```
419
+
420
+
421
+ ### Alternative Value
422
+
423
+ Benry-CmdOpt supports alternative value.
424
+
425
+ ```ruby
426
+ require 'benry/cmdopt'
427
+ cmdopt = Benry::CmdOpt.new
428
+ cmdopt.add(:help1, "-h", "help")
429
+ cmdopt.add(:help2, "-H", "help", value: "HELP") # !!!!!
100
430
 
101
- Multiple parameters
102
- -------------------
431
+ options = cmdopt.parse(["-h", "-H"])
432
+ p options[:help1] #=> true # normal
433
+ p options[:help2] #=> "HELP" # alternative value
434
+ ```
435
+
436
+ This is useful for boolean option.
103
437
 
104
438
  ```ruby
105
- cmdopt.add(:lib , "-I <NAME>", "library names") {|optdict, key, val|
106
- arr = optdict[key] || []
439
+ require 'benry/cmdopt'
440
+ cmdopt = Benry::CmdOpt.new
441
+ cmdopt.add(:flag1, "--flag1[=<on|off>]", "f1", type: TrueClass)
442
+ cmdopt.add(:flag2, "--flag2[=<on|off>]", "f2", type: TrueClass, value: false) # !!!!
443
+
444
+ ## when `--flag2` specified, got `false` value.
445
+ options = cmdopt.parse(["--flag1", "--flag2"])
446
+ p options[:flag1] #=> true
447
+ p options[:flag2] #=> false (!!!!!)
448
+ ```
449
+
450
+
451
+ ### Multiple Value Option
452
+
453
+ ```ruby
454
+ require 'benry/cmdopt'
455
+ cmdopt = Benry::CmdOpt.new
456
+
457
+ cmdopt.add(:lib , '-I <NAME>', "library name") {|options, key, val|
458
+ arr = options[key] || []
107
459
  arr << val
108
460
  arr
461
+ ## or:
462
+ #(options[key] || []) << val
109
463
  }
464
+
465
+ options = cmdopt.parse(["-I", "foo", "-I", "bar", "-Ibaz"])
466
+ p options #=> {:lib=>["foo", "bar", "baz"]}
467
+ ```
468
+
469
+
470
+ ### Hidden Option
471
+
472
+ Benry-CmdOpt regards the following options as hidden.
473
+
474
+ * Key name starts with `_` (for example `:_debug`).
475
+ * Or description is nil.
476
+
477
+ The former is better than the latter, because even hidden option should have its own description.
478
+
479
+ These hidden options are not included in help message.
480
+
481
+ ```ruby
482
+ require 'benry/cmdopt'
483
+ cmdopt = Benry::CmdOpt.new
484
+ cmdopt.add(:help , '-h', "help message")
485
+ cmdopt.add(:debug, '-D', nil) # hidden (because description is nil)
486
+ cmdopt.add(:_log , '-L', "logging") # hidden (because key starts with '_')
487
+ puts cmdopt.to_s()
488
+
489
+ ### output (neither '-D' nor '-L' is shown because hidden options)
490
+ # -h : help message
491
+ ```
492
+
493
+ To show all options including hidden ones, add `all: true` to `cmdopt.to_s()`.
494
+
495
+ ```ruby
496
+ ...(snip)...
497
+ puts cmdopt.to_s(all: true) # or: cmdopt.to_s(nil, all: true)
498
+
499
+ ### output
500
+ # -h : help message
501
+ # -D :
502
+ # -L : logging
503
+ ```
504
+
505
+
506
+ ### Global Options with Sub-Commands
507
+
508
+ `parse()` accepts boolean keyword argument `all`.
509
+
510
+ * `parse(argv, all: true)` parses even options placed after arguments. This is the default.
511
+ * `parse(argv, all: false)` only parses options placed before arguments.
512
+
513
+ ```ruby
514
+ require 'benry/cmdopt'
515
+ cmdopt = Benry::CmdOpt.new()
516
+ cmdopt.add(:help , '--help' , "print help message")
517
+ cmdopt.add(:version, '--version', "print version")
518
+
519
+ ## `parse(argv, all: true)` (default)
520
+ argv = ["--help", "arg1", "--version", "arg2"]
521
+ options = cmdopt.parse(argv, all: true) # !!!
522
+ p options #=> {:help=>true, :version=>true}
523
+ p argv #=> ["arg1", "arg2"]
524
+
525
+ ## `parse(argv, all: false)`
526
+ argv = ["--help", "arg1", "--version", "arg2"]
527
+ options = cmdopt.parse(argv, all: false) # !!!
528
+ p options #=> {:help=>true}
529
+ p argv #=> ["arg1", "--version", "arg2"]
530
+ ```
531
+
532
+ This is useful when parsing global options of sub-commands, like Git command.
533
+
534
+ ```ruby
535
+ require 'benry/cmdopt'
536
+
537
+ argv = ["-h", "commit", "xxx", "-m", "yyy"]
538
+
539
+ ## parse global options
540
+ cmdopt = Benry::CmdOpt.new()
541
+ cmdopt.add(:help, '-h', "print help message")
542
+ global_opts = cmdopt.parse(argv, all: false) # !!!false!!!
543
+ p global_opts #=> {:help=>true}
544
+ p argv #=> ["commit", "xxx", "-m", "yyy"]
545
+
546
+ ## get sub-command
547
+ sub_command = argv.shift()
548
+ p sub_command #=> "commit"
549
+ p argv #=> ["xxx", "-m", "yyy"]
550
+
551
+ ## parse sub-command options
552
+ cmdopt = Benry::CmdOpt.new()
553
+ case sub_command
554
+ when "commit"
555
+ cmdopt.add(:message, '-m <message>', "commit message")
556
+ else
557
+ # ...
558
+ end
559
+ sub_opts = cmdopt.parse(argv, all: true) # !!!true!!!
560
+ p sub_opts #=> {:message => "yyy"}
561
+ p argv #=> ["xxx"]
562
+ ```
563
+
564
+
565
+ ### Detailed Description of Option
566
+
567
+ `#add()` method in `Benry::CmdOpt` or `Benry::CmdOpt::Schema` supports `detail:` keyword argument which takes detailed description of option.
568
+
569
+ ```ruby
570
+ require 'benry/cmdopt'
571
+
572
+ cmdopt = Benry::CmdOpt.new()
573
+ cmdopt.add(:mode, "-m, --mode=<MODE>", "output mode", detail: <<"END")
574
+ v, verbose: print many output
575
+ q, quiet: print litte output
576
+ c, compact: print summary output
577
+ END
578
+ puts cmdopt.to_s()
579
+ ## or:
580
+ #cmdopt.each_option_and_desc do |optstr, desc, detail|
581
+ # puts " %-20s : %s\n" % [optstr, desc]
582
+ # puts detail.gsub(/^/, ' ' * 25) if detail
583
+ #end
584
+ ```
585
+
586
+ Output:
587
+
588
+ ```
589
+ -m, --mode=<MODE> : output mode
590
+ v, verbose: print many output
591
+ q, quiet: print litte output
592
+ c, compact: print summary output
593
+ ```
594
+
595
+
596
+ ### Option Tag
597
+
598
+ `#add()` method in `Benry::CmdOpt` or `Benry::CmdOpt::Schema` supports `tag:` keyword argument.
599
+ You can use it for any purpose.
600
+
601
+ ```ruby
602
+ require 'benry/cmdopt'
603
+
604
+ cmdopt = Benry::CmdOpt.new()
605
+ cmdopt.add(:help, "-h, --help", "help message", tag: "important") # !!!
606
+ cmdopt.add(:version, "--version", "print version", tag: nil)
607
+ cmdopt.schema.each do |item|
608
+ puts "#{item.key}: tag=#{item.tag.inspect}"
609
+ end
610
+
611
+ ## output:
612
+ #help: tag="important"
613
+ #version: tag=nil
110
614
  ```
111
615
 
112
616
 
113
- Not support
114
- -----------
617
+ ### Not Supported
115
618
 
116
619
  * default value
117
620
  * `--no-xxx` style option
621
+ * bash/zsh completion
622
+ * I18N of error message (may be supported in the future)
623
+
118
624
 
119
625
 
120
- Internal classes
121
- ================
626
+ ## Internal Classes
122
627
 
123
- * `Benry::Cmdopt::Schema` -- command option schema.
124
- * `Benry::Cmdopt::Parser` -- command option parser.
125
- * `Benry::Cmdopt::Facade` -- facade object including schema and parser.
628
+ * `Benry::CmdOpt::Schema` ... command option schema.
629
+ * `Benry::CmdOpt::Parser` ... command option parser.
630
+ * `Benry::CmdOpt::Facade` ... facade object including schema and parser.
126
631
 
127
632
  ```ruby
128
633
  require 'benry/cmdopt'
129
634
 
130
635
  ## define schema
131
- schema = Benry::Cmdopt::Schema.new
636
+ schema = Benry::CmdOpt::Schema.new
132
637
  schema.add(:help , '-h, --help' , "show help message")
133
638
  schema.add(:file , '-f, --file=<FILE>' , "filename")
134
639
  schema.add(:indent, '-i, --indent[=<WIDTH>]', "enable indent", type: Integer)
135
640
 
136
641
  ## parse options
137
- parser = Benry::Cmdopt::Parser.new(schema)
642
+ parser = Benry::CmdOpt::Parser.new(schema)
138
643
  argv = ['-hi2', '--file=blabla.txt', 'aaa', 'bbb']
139
644
  opts = parser.parse(argv) do |err|
140
645
  $stderr.puts "ERROR: #{err.message}"
141
646
  exit 1
142
647
  end
143
- p opts #=> [:help=>true, :indent=>2, :file=>"blabla.txt"]
648
+ p opts #=> {:help=>true, :indent=>2, :file=>"blabla.txt"}
144
649
  p argv #=> ["aaa", "bbb"]
145
650
  ```
146
651
 
147
- Notice that `Benry::Cmdopt.new()` returns facade object.
652
+ Notice that `Benry::CmdOpt.new()` returns a facade object.
148
653
 
149
654
  ```ruby
150
655
  require 'benry/cmdopt'
151
656
 
152
- cmdopt = Benry::Cmdopt.new() # new facade object
657
+ cmdopt = Benry::CmdOpt.new() # new facade object
153
658
  cmdopt.add(:help, '-h', "help message") # same as schema.add(...)
154
659
  opts = cmdopt.parse(ARGV) # same as parser.parse(...)
155
660
  ```
156
661
 
662
+ Notice that `cmdopt.is_a?(Benry::CmdOpt)` results in false.
663
+ Use `cmdopt.is_a?(Benry::CmdOpt::Facade)` instead if necessary.
664
+
665
+
157
666
 
158
- License and Copyright
159
- =====================
667
+ ## License and Copyright
160
668
 
161
669
  $License: MIT License $
162
670
 
163
- $Copyright: copyright(c) 2021 kuwata-lab.com all rights reserved $
671
+ $Copyright: copyright(c) 2021 kwatch@gmail.com $