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