benry-cmdopt 1.0.0 → 2.0.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.
- 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 $
|