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