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.
- checksums.yaml +4 -4
- data/CHANGES.md +34 -1
- data/MIT-LICENSE +21 -0
- data/README.md +570 -62
- data/Rakefile.rb +6 -87
- data/benry-cmdopt.gemspec +23 -21
- data/doc/benry-cmdopt.html +650 -0
- data/doc/css/style.css +160 -0
- data/lib/benry/cmdopt.rb +568 -417
- data/task/common-task.rb +138 -0
- data/task/package-task.rb +72 -0
- data/task/readme-task.rb +125 -0
- data/task/test-task.rb +81 -0
- data/test/cmdopt_test.rb +1372 -681
- metadata +22 -28
data/README.md
CHANGED
@@ -1,37 +1,288 @@
|
|
1
|
-
Benry
|
2
|
-
====================
|
1
|
+
# Benry-CmdOpt
|
3
2
|
|
4
|
-
($Release:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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::
|
33
|
-
cmdopt.add(:help ,
|
34
|
-
cmdopt.add(: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.
|
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.
|
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
|
58
|
-
------------------------
|
318
|
+
### Command Option Parameter
|
59
319
|
|
60
320
|
```ruby
|
61
321
|
## required parameter
|
62
|
-
cmdopt.add(:file,
|
63
|
-
cmdopt.add(:file,
|
64
|
-
cmdopt.add(:file,
|
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(:
|
68
|
-
cmdopt.add(:
|
69
|
-
cmdopt.add(:
|
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
|
-
|
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 ,
|
79
|
-
## pattern
|
80
|
-
cmdopt.add(:indent ,
|
81
|
-
## enum
|
82
|
-
cmdopt.add(:indent ,
|
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 ,
|
352
|
+
cmdopt.add(:indent , '-i <N>', "indent width") {|val|
|
85
353
|
val =~ /\A\d+\z/ or
|
86
|
-
raise "
|
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
|
-
|
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
|
-
|
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
|
106
|
-
|
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
|
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
|
121
|
-
================
|
626
|
+
## Internal Classes
|
122
627
|
|
123
|
-
* `Benry::
|
124
|
-
* `Benry::
|
125
|
-
* `Benry::
|
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::
|
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::
|
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 #=>
|
648
|
+
p opts #=> {:help=>true, :indent=>2, :file=>"blabla.txt"}
|
144
649
|
p argv #=> ["aaa", "bbb"]
|
145
650
|
```
|
146
651
|
|
147
|
-
Notice that `Benry::
|
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::
|
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
|
671
|
+
$Copyright: copyright(c) 2021 kwatch@gmail.com $
|