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.
@@ -0,0 +1,650 @@
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.0 $)</p>
25
+ <section class="section" id="overview">
26
+ <h2>Overview</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 &gt;= 2.3.</p>
37
+ </section>
38
+ <section class="section" id="table-of-contents">
39
+ <h2>Table of Contents</h2>
40
+ <div class="toc">
41
+ <ul>
42
+ <li><a href="#overview">Overview</a></li>
43
+ <li><a href="#why-not-optparserb">Why not <code>optparse.rb</code>?</a></li>
44
+ <li><a href="#install">Install</a></li>
45
+ <li><a href="#usage">Usage</a>
46
+ <ul>
47
+ <li><a href="#define-parse-and-print-help">Define, Parse, and Print Help</a></li>
48
+ <li><a href="#command-option-parameter">Command Option Parameter</a></li>
49
+ <li><a href="#argument-validation">Argument Validation</a></li>
50
+ <li><a href="#boolean-onoff-option">Boolean (on/off) Option</a></li>
51
+ <li><a href="#alternative-value">Alternative Value</a></li>
52
+ <li><a href="#multiple-value-option">Multiple Value Option</a></li>
53
+ <li><a href="#hidden-option">Hidden Option</a></li>
54
+ <li><a href="#global-options-with-sub-commands">Global Options with Sub-Commands</a></li>
55
+ <li><a href="#detailed-description-of-option">Detailed Description of Option</a></li>
56
+ <li><a href="#option-tag">Option Tag</a></li>
57
+ <li><a href="#not-supported">Not Supported</a></li>
58
+ </ul></li>
59
+ <li><a href="#internal-classes">Internal Classes</a></li>
60
+ <li><a href="#license-and-copyright">License and Copyright</a></li>
61
+ </ul>
62
+ </div>
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&#039;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&#039;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 &#039;optparse&#039;
95
+ parser = OptionParser.new
96
+ parser.on(&#039;-v&#039;, &#039;--verbose&#039;, &quot;verbose mode&quot;) # short and long option
97
+ parser.on(<strong>&#039;-q&#039;</strong>, &quot;quiet mode&quot;) # short-only option
98
+ #
99
+ opts = {}
100
+ parser.parse!([&#039;-v&#039;], into: opts) # short option
101
+ p opts #=&gt; {:verbose=&gt;true} # hash key is long option name
102
+ #
103
+ opts = {}
104
+ parser.parse!([&#039;-q&#039;], into: opts) # short option
105
+ p opts #=&gt; <strong>{:q=&gt;true}</strong> # hash key is short option name
106
+
107
+ ### benry/cmdopt.rb
108
+ require &#039;benry/cmdopt&#039;
109
+ cmdopt = Benry::CmdOpt.new
110
+ cmdopt.add(:verbose, &#039;-v, --verbose&#039;, &quot;verbose mode&quot;) # short and long
111
+ cmdopt.add(<strong>:quiet</strong> , <strong>&#039;-q&#039;</strong> , &quot;quiet mode&quot;) # short-only
112
+ #
113
+ opts = cmdopt.parse([&#039;-v&#039;]) # short option
114
+ p opts #=&gt; {:verbose=&gt;true} # independent hash key of option name
115
+ #
116
+ opts = cmdopt.parse([&#039;-q&#039;]) # short option
117
+ p opts #=&gt; <strong>{:quiet=&gt;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&#039;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(&#039;-n &ltN&gt;&#039;, &quot;number&quot;, Integer, <strong>(1..)</strong>) #=&gt; NoMethodError
132
+
133
+ ### benry/cmdopt.rb
134
+ require &#039;benry/cmdopt&#039;
135
+ cmdopt = Benry::CmdOpt.new
136
+ cmdopt.add(:number, &quot;-n &ltN&gt;&quot;, &quot;number&quot;, type: Integer, <strong>range: (1..)</strong>) #=&gt; 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(&#039;-n &ltN&gt;&#039;, &quot;number&quot;, Integer, <strong>[1, 2, 3]</strong>) # wrong
149
+ parser.on(&#039;-n &ltN&gt;&#039;, &quot;number&quot;, Integer, <strong>[&#039;1&#039;,&#039;2&#039;,&#039;3&#039;]</strong>) # ok (but not intuitive)
150
+
151
+ ### benry/cmdopt.rb
152
+ require &#039;benry/cmdopt&#039;
153
+ cmdopt = Benry::CmdOpt.new
154
+ cmdopt.add(:number, &quot;-n &ltN&gt;&quot;, &quot;number&quot;, 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 &#039;optparse&#039;
166
+ parser = OptionParser.new
167
+ ## it is able to overwrite &#039;-h&#039; and/or &#039;--help&#039;,
168
+ ## but how to remove or disable these options?
169
+ opts = {}
170
+ parser.on(<strong>&#039;-h &lthost&gt;&#039;</strong>, &quot;hostname&quot;) {|v| opts[:host] = v }
171
+ parser.parse([<strong>&#039;--help&#039;</strong>]) # &lt== terminates current process!!
172
+ puts &#039;xxx&#039; #&lt== 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 &#039;optparse&#039;
186
+ parser = OptionParser.new
187
+ ## it is able to overwrite &#039;-v&#039; and/or &#039;--version&#039;,
188
+ ## but how to remove or disable these options?
189
+ opts = {}
190
+ parser.on(<strong>&#039;-v&#039;</strong>, &quot;verbose mode&quot;) { opts[:verbose] = true }
191
+ parser.parse([<strong>&#039;--version&#039;</strong>]) # &lt== terminates current process!!
192
+ puts &#039;xxx&#039; #&lt== not printed because current process alreay terminated
193
+ </pre>
194
+ <ul>
195
+ <li><code>optparse.rb</code> generates help message automatically, but it doesn&#039;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: &ltcommand&gt; [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 &#039;[options]&#039; to &#039;[&ltoptions&gt;]&#039;, you must manipulate
203
+ help message string by yourself.
204
+ <p></p>
205
+ <code>benry/cmdopt.rb</code> doesn&#039;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&#039;t need to specify proper width in many case.</li>
213
+ </ul>
214
+ <pre class="language-ruby">
215
+ ### optparse.rb
216
+ require &#039;optparse&#039;
217
+ banner = &quot;Usage: blabla &ltoptions&gt;&quot;
218
+ parser = OptionParser.new(banner) # or: OptionParser.new(banner, 25)
219
+ parser.on(&#039;-f&#039;, &#039;--file=&ltFILE&gt;&#039;, &quot;filename&quot;)
220
+ parser.on(&#039;-m &ltMODE&gt;&#039; , &quot;verbose/quiet&quot;)
221
+ puts parser.help
222
+ ### output
223
+ # Usage: blabla &ltoptions&gt;
224
+ # <strong>-f, --file=&ltFILE&gt; filename</strong>
225
+ # <strong>-m &ltMODE&gt; verbose/quiet</strong>
226
+
227
+ ### benry/cmdopt.rb
228
+ require &#039;benry/cmdopt&#039;
229
+ cmdopt = Benry::CmdOpt.new()
230
+ cmdopt.add(:file, &#039;-f, --file=&ltFILE&gt;&#039;, &quot;filename&quot;)
231
+ cmdopt.add(:mode, &#039;-m &ltMODE&gt;&#039; , &quot;verbose/quiet&quot;)
232
+ puts &quot;Usage: blabla [&ltoptions&gt;]&quot;
233
+ puts cmdopt.to_s()
234
+ ### output (calculated proper width)
235
+ # Usage: blabla [&ltoptions&gt;]
236
+ # <strong>-f, --file=&ltFILE&gt; : filename</strong>
237
+ # <strong>-m &ltMODE&gt; : 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&#039;t need to know the error class name on error handling.</li>
245
+ </ul>
246
+ <pre class="language-ruby">
247
+ ### optparse.rb
248
+ require &#039;optparse&#039;
249
+ parser = OptionParser.new
250
+ parser.on(&#039;-f&#039;, &#039;--file=&ltFILE&gt;&#039;, &quot;filename&quot;)
251
+ opts = {}
252
+ begin
253
+ parser.parse!(ARGV, into: opts)
254
+ <strong>rescue OptionParser::ParseError =&gt; err</strong> # specify error class
255
+ $stderr.puts &quot;ERROR: #{err.message}&quot;
256
+ exit 1
257
+ end
258
+
259
+ ### benry/cmdopt.rb
260
+ require &#039;benry/cmdopt&#039;
261
+ cmdopt = Benry::CmdOpt.new
262
+ cmdopt.add(:file, &#039;-f, --file=&ltFILE&gt;&#039;, &quot;filename&quot;)
263
+ opts = cmdopt.parse(ARGV) <strong>do |err|</strong> # error handling wihtout error class name
264
+ $stderr.puts &quot;ERROR: #{err.message}&quot;
265
+ exit 1
266
+ end
267
+ </pre>
268
+ <ul>
269
+ <li>Source code of <code>optparse.rb</code> is very large and complicated, because
270
+ <code>OptParse</code> class does everything about command option parsing.
271
+ It is hard to customize or extend <code>OptionParser</code> class.
272
+ <p></p>
273
+ In contract, <code>benry/cmdopt.rb</code> consists of several classes
274
+ (schema class, parser class, and facade class).
275
+ Therefore it is easy to understand and extend these classes.
276
+ <p></p>
277
+ File <code>optparse.rb</code> (in Ruby 3.2) contains 1143 lines (except comments and blanks),
278
+ while <code>benry/cmdopt.rb</code> (v2.0) contains 427 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 &#039;benry/cmdopt&#039;</strong>
293
+
294
+ ## define
295
+ <strong>cmdopt = Benry::CmdOpt.new</strong>
296
+ cmdopt.add(:help , &#039;-h, --help&#039; , &quot;print help message&quot;)
297
+ cmdopt.add(:version, &#039; --version&#039;, &quot;print version&quot;)
298
+
299
+ ## parse with error handling
300
+ <strong>options = cmdopt.parse(ARGV)</strong> do |err|
301
+ $stderr.puts &quot;ERROR: #{err.message}&quot;
302
+ exit(1)
303
+ end
304
+ p options # ex: {:help =&gt; true, :version =&gt; true}
305
+ p ARGV # options are removed from ARGV
306
+
307
+ ## help
308
+ if options[:help]
309
+ puts &quot;Usage: foobar [&ltoptions&gt;] [&ltargs&gt;...]&quot;
310
+ puts &quot;&quot;
311
+ puts &quot;Options:&quot;
312
+ <strong>puts cmdopt.to_s()</strong>
313
+ ## or: puts cmdopt.to_s(20) # width
314
+ ## or: puts cmdopt.to_s(&quot; %-20s : %s&quot;) # format
315
+ ## or:
316
+ #format = &quot; %-20s : %s&quot;
317
+ #cmdopt.each_option_and_desc {|opt, help| puts format % [opt, help] }
318
+ end
319
+ </pre>
320
+ <p>You can set <code>nil</code> to option name only if long option specified.</p>
321
+ <pre class="language-ruby">
322
+ ## both are same
323
+ cmdopt.add(:help, &quot;-h, --help&quot;, &quot;print help message&quot;)
324
+ cmdopt.add(<strong>nil</strong> , &quot;-h, --help&quot;, &quot;print help message&quot;)
325
+ </pre>
326
+ </section>
327
+ <section class="subsection" id="command-option-parameter">
328
+ <h3>Command Option Parameter</h3>
329
+ <pre class="language-ruby">
330
+ ## required parameter
331
+ cmdopt.add(:file, &#039;-f, --file<strong>=&ltFILE&gt;</strong>&#039;, &quot;filename&quot;) # short &amp; long
332
+ cmdopt.add(:file, &#039; --file<strong>=&ltFILE&gt;</strong>&#039;, &quot;filename&quot;) # long only
333
+ cmdopt.add(:file, &#039;-f <strong>&ltFILE&gt;</strong>&#039; , &quot;filename&quot;) # short only
334
+
335
+ ## optional parameter
336
+ cmdopt.add(:indent, &#039;-i, --indent<strong>[=&ltN&gt;]</strong>&#039;, &quot;indent width&quot;) # short &amp; long
337
+ cmdopt.add(:indent, &#039; --indent<strong>[=&ltN&gt;]</strong>&#039;, &quot;indent width&quot;) # long only
338
+ cmdopt.add(:indent, &#039;-i<strong>[&ltN&gt;]</strong>&#039; , &quot;indent width&quot;) # short only
339
+ </pre>
340
+ <p>Notice that <code>&quot;--file &ltFILE&gt;&quot;</code> style is <strong>not supported for usability reason</strong>.
341
+ Use <code>&quot;--file=&ltFILE&gt;&quot;</code> style instead.</p>
342
+ <p>(From a usability perspective, the former style should not be supported.
343
+ <code>optparse.rb</code> is wrong because it supports both styles
344
+ and doesn&#039;t provide the way to disable the former style.)</p>
345
+ </section>
346
+ <section class="subsection" id="argument-validation">
347
+ <h3>Argument Validation</h3>
348
+ <pre class="language-ruby">
349
+ ## type (class)
350
+ cmdopt.add(:indent , &#039;-i &ltN&gt;&#039;, &quot;indent width&quot;, <strong>type: Integer</strong>)
351
+ ## pattern (regular expression)
352
+ cmdopt.add(:indent , &#039;-i &ltN&gt;&#039;, &quot;indent width&quot;, <strong>rexp: /\A\d+\z/</strong>)
353
+ ## enum (Array or Set)
354
+ cmdopt.add(:indent , &#039;-i &ltN&gt;&#039;, &quot;indent width&quot;, <strong>enum: [&quot;2&quot;, &quot;4&quot;, &quot;8&quot;]</strong>)
355
+ ## range (endless range such as ``1..`` available)
356
+ cmdopt.add(:indent , &#039;-i &ltN&gt;&#039;, &quot;indent width&quot;, <strong>range: (0..8)</strong>)
357
+ ## callback
358
+ cmdopt.add(:indent , &#039;-i &ltN&gt;&#039;, &quot;indent width&quot;) <strong>{|val|</strong>
359
+ val =~ /\A\d+\z/ or
360
+ <strong>raise &quot;Integer expected.&quot;</strong> # raise without exception class.
361
+ <strong>val.to_i</strong> # convert argument value.
362
+ }
363
+ </pre>
364
+ <p>(For backward compatibilidy, keyword parameter <code>pattern:</code> is available
365
+ which is same as <code>rexp:</code>.)</p>
366
+ <p><code>type:</code> keyword argument accepts the following classes.</p>
367
+ <ul>
368
+ <li>Integer (<code>/\A[-+]?\d+\z/</code>)</li>
369
+ <li>Float (<code>/\A[-+]?(\d+\.\d*\|\.\d+)z/</code>)</li>
370
+ <li>TrueClass (<code>/\A(true|on|yes|false|off|no)\z/</code>)</li>
371
+ <li>Date (<code>/\A\d\d\d\d-\d\d?-\d\d?\z/</code>)</li>
372
+ </ul>
373
+ <p>Notice that Ruby doesn&#039;t have Boolean class.
374
+ Benry-CmdOpt uses TrueClass instead.</p>
375
+ <p>In addition:</p>
376
+ <ul>
377
+ <li>Values of <code>enum:</code> or <code>range:</code> should match to type class specified by <code>type:</code>.</li>
378
+ <li>When <code>type:</code> is not specified, then String class will be used instead.</li>
379
+ </ul>
380
+ <pre class="language-ruby">
381
+ ## ok
382
+ cmdopt.add(:lang, &#039;-l &ltlang&gt;&#039;, &quot;language&quot;, <strong>enum: [&quot;en&quot;, &quot;fr&quot;, &quot;it&quot;]</strong>)
383
+
384
+ ## error: enum values are not Integer
385
+ cmdopt.add(:lang, &#039;-l &ltlang&gt;&#039;, &quot;language&quot;, <strong>enum: [&quot;en&quot;, &quot;fr&quot;, &quot;it&quot;], type: Integer</strong>)
386
+
387
+ ## ok
388
+ cmdopt.add(:indent, &#039;-i &ltN&gt;&#039;, &quot;indent&quot;, <strong>range: (0..), type: Integer</strong>)
389
+
390
+ ## error: beginning value of range is not a String
391
+ cmdopt.add(:indent, &#039;-i &ltN&gt;&#039;, &quot;indent&quot;, <strong>range: (0..)</strong>)
392
+ </pre>
393
+ </section>
394
+ <section class="subsection" id="boolean-onoff-option">
395
+ <h3>Boolean (on/off) Option</h3>
396
+ <p>Benry-CmdOpt doens&#039;t support <code>--no-xxx</code> style option for usability reason.
397
+ Use boolean option instead.</p>
398
+ <p>ex3.rb:</p>
399
+ <pre class="language-ruby">
400
+ require &#039;benry/cmdopt&#039;
401
+ cmdopt = Benry::CmdOpt.new()
402
+ cmdopt.add(:foo, <strong>&quot;--foo[=on|off]&quot;</strong>, &quot;foo feature&quot;, <strong>type: TrueClass</strong>) # !!!!
403
+ ## or:
404
+ #cmdopt.add(:foo, <strong>&quot;--foo=&lton|off&gt;&quot;</strong>, &quot;foo feature&quot;, <strong>type: TrueClass</strong>)
405
+ options = cmdopt.parse(ARGV)
406
+ p options
407
+ </pre>
408
+ <p>Output example:</p>
409
+ <pre class="language-terminal">
410
+ $ ruby ex3.rb <strong>--foo</strong> # enable
411
+ {:foo=&gt;<strong>true</strong>}
412
+ $ ruby ex3.rb <strong>--foo=on</strong> # enable
413
+ {:foo=&gt;<strong>true</strong>}
414
+ $ ruby ex3.rb <strong>--foo=off</strong> # disable
415
+ {:foo=&gt;<strong>false</strong>}
416
+ </pre>
417
+ </section>
418
+ <section class="subsection" id="alternative-value">
419
+ <h3>Alternative Value</h3>
420
+ <p>Benry-CmdOpt supports alternative value.</p>
421
+ <pre class="language-ruby">
422
+ require &#039;benry/cmdopt&#039;
423
+ cmdopt = Benry::CmdOpt.new
424
+ cmdopt.add(:help1, &quot;-h&quot;, &quot;help&quot;)
425
+ cmdopt.add(:help2, &quot;-H&quot;, &quot;help&quot;, <strong>value: &quot;HELP&quot;</strong>) # !!!!!
426
+
427
+ options = cmdopt.parse([&quot;-h&quot;, &quot;-H&quot;])
428
+ p options[:help1] #=&gt; true # normal
429
+ p options[:help2] #=&gt; <strong>&quot;HELP&quot;</strong> # alternative value
430
+ </pre>
431
+ <p>This is useful for boolean option.</p>
432
+ <pre class="language-ruby">
433
+ require &#039;benry/cmdopt&#039;
434
+ cmdopt = Benry::CmdOpt.new
435
+ cmdopt.add(:flag1, &quot;--flag1[=&lton|off&gt;]&quot;, &quot;f1&quot;, type: TrueClass)
436
+ cmdopt.add(:flag2, &quot;--flag2[=&lton|off&gt;]&quot;, &quot;f2&quot;, type: TrueClass, <strong>value: false</strong>) # !!!!
437
+
438
+ ## when `--flag2` specified, got `false` value.
439
+ options = cmdopt.parse([&quot;--flag1&quot;, &quot;--flag2&quot;])
440
+ p options[:flag1] #=&gt; true
441
+ p options[:flag2] #=&gt; <strong>false</strong> (!!!!!)
442
+ </pre>
443
+ </section>
444
+ <section class="subsection" id="multiple-value-option">
445
+ <h3>Multiple Value Option</h3>
446
+ <pre class="language-ruby">
447
+ require &#039;benry/cmdopt&#039;
448
+ cmdopt = Benry::CmdOpt.new
449
+
450
+ cmdopt.add(:lib , &#039;-I &ltNAME&gt;&#039;, &quot;library name&quot;) <strong>{|options, key, val|</strong>
451
+ <strong>arr = options[key] || []</strong>
452
+ <strong>arr &lt&lt val</strong>
453
+ <strong>arr</strong>
454
+ ## or:
455
+ #(options[key] || []) &lt&lt val
456
+ <strong>}</strong>
457
+
458
+ options = cmdopt.parse([&quot;-I&quot;, &quot;foo&quot;, &quot;-I&quot;, &quot;bar&quot;, &quot;-Ibaz&quot;])
459
+ p options #=&gt; <strong>{:lib=&gt;[&quot;foo&quot;, &quot;bar&quot;, &quot;baz&quot;]}</strong>
460
+ </pre>
461
+ </section>
462
+ <section class="subsection" id="hidden-option">
463
+ <h3>Hidden Option</h3>
464
+ <p>Benry-CmdOpt regards the following options as hidden.</p>
465
+ <ul>
466
+ <li>Key name starts with <code>_</code> (for example <code>:_debug</code>).</li>
467
+ <li>Or description is nil.</li>
468
+ </ul>
469
+ <p>The former is better than the latter, because even hidden option should have its own description.</p>
470
+ <p>These hidden options are not included in help message.</p>
471
+ <pre class="language-ruby">
472
+ require &#039;benry/cmdopt&#039;
473
+ cmdopt = Benry::CmdOpt.new
474
+ cmdopt.add(:help , &#039;-h&#039;, &quot;help message&quot;)
475
+ cmdopt.add(:debug, &#039;-D&#039;, <strong>nil</strong>) # hidden (because description is nil)
476
+ cmdopt.add(<strong>:_log</strong> , &#039;-L&#039;, &quot;logging&quot;) # hidden (because key starts with &#039;_&#039;)
477
+ puts cmdopt.to_s()
478
+
479
+ ### output (neither &#039;-D&#039; nor &#039;-L&#039; is shown because hidden options)
480
+ # -h : help message
481
+ </pre>
482
+ <p>To show all options including hidden ones, add <code>all: true</code> to <code>cmdopt.to_s()</code>.</p>
483
+ <pre class="language-ruby">
484
+ ...(snip)...
485
+ puts cmdopt.to_s(<strong>all: true</strong>) # or: cmdopt.to_s(nil, all: true)
486
+
487
+ ### output
488
+ # -h : help message
489
+ # <strong>-D :</strong>
490
+ # <strong>-L : logging</strong>
491
+ </pre>
492
+ </section>
493
+ <section class="subsection" id="global-options-with-sub-commands">
494
+ <h3>Global Options with Sub-Commands</h3>
495
+ <p><code>parse()</code> accepts boolean keyword argument <code>all</code>.</p>
496
+ <ul>
497
+ <li><code>parse(argv, all: true)</code> parses even options placed after arguments. This is the default.</li>
498
+ <li><code>parse(argv, all: false)</code> only parses options placed before arguments.</li>
499
+ </ul>
500
+ <pre class="language-ruby">
501
+ require &#039;benry/cmdopt&#039;
502
+ cmdopt = Benry::CmdOpt.new()
503
+ cmdopt.add(:help , &#039;--help&#039; , &quot;print help message&quot;)
504
+ cmdopt.add(:version, &#039;--version&#039;, &quot;print version&quot;)
505
+
506
+ ## `parse(argv, all: true)` (default)
507
+ argv = [&quot;--help&quot;, &quot;arg1&quot;, &quot;--version&quot;, &quot;arg2&quot;]
508
+ options = cmdopt.parse(argv, <strong>all: true</strong>) # !!!
509
+ p options #=&gt; {:help=&gt;true, <strong>:version=&gt;true</strong>}
510
+ p argv #=&gt; [&quot;arg1&quot;, &quot;arg2&quot;]
511
+
512
+ ## `parse(argv, all: false)`
513
+ argv = [&quot;--help&quot;, &quot;arg1&quot;, &quot;--version&quot;, &quot;arg2&quot;]
514
+ options = cmdopt.parse(argv, <strong>all: false</strong>) # !!!
515
+ p options #=&gt; {:help=&gt;true}
516
+ p argv #=&gt; [&quot;arg1&quot;, <strong>&quot;--version&quot;</strong>, &quot;arg2&quot;]
517
+ </pre>
518
+ <p>This is useful when parsing global options of sub-commands, like Git command.</p>
519
+ <pre class="language-ruby">
520
+ require &#039;benry/cmdopt&#039;
521
+
522
+ argv = [&quot;-h&quot;, &quot;commit&quot;, &quot;xxx&quot;, &quot;-m&quot;, &quot;yyy&quot;]
523
+
524
+ ## parse global options
525
+ cmdopt = Benry::CmdOpt.new()
526
+ cmdopt.add(:help, &#039;-h&#039;, &quot;print help message&quot;)
527
+ global_opts = cmdopt.parse(argv, <strong>all: false</strong>) # !!!false!!!
528
+ p global_opts #=&gt; {:help=&gt;true}
529
+ p argv #=&gt; [&quot;commit&quot;, &quot;xxx&quot;, &quot;-m&quot;, &quot;yyy&quot;]
530
+
531
+ ## get sub-command
532
+ sub_command = argv.shift()
533
+ p sub_command #=&gt; &quot;commit&quot;
534
+ p argv #=&gt; [&quot;xxx&quot;, <strong>&quot;-m&quot;</strong>, &quot;yyy&quot;]
535
+
536
+ ## parse sub-command options
537
+ cmdopt = Benry::CmdOpt.new()
538
+ case sub_command
539
+ when &quot;commit&quot;
540
+ cmdopt.add(:message, &#039;-m &ltmessage&gt;&#039;, &quot;commit message&quot;)
541
+ else
542
+ # ...
543
+ end
544
+ sub_opts = cmdopt.parse(argv, <strong>all: true</strong>) # !!!true!!!
545
+ p sub_opts #=&gt; {:message =&gt; &quot;yyy&quot;}
546
+ p argv #=&gt; [&quot;xxx&quot;]
547
+ </pre>
548
+ </section>
549
+ <section class="subsection" id="detailed-description-of-option">
550
+ <h3>Detailed Description of Option</h3>
551
+ <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>
552
+ <pre class="language-ruby">
553
+ require &#039;benry/cmdopt&#039;
554
+
555
+ cmdopt = Benry::CmdOpt.new()
556
+ cmdopt.add(:mode, &quot;-m, --mode=&ltMODE&gt;&quot;, &quot;output mode&quot;, <strong>detail: &lt&lt&quot;END&quot;</strong>)
557
+ v, verbose: print many output
558
+ q, quiet: print litte output
559
+ c, compact: print summary output
560
+ <strong>END</strong>
561
+ puts cmdopt.to_s()
562
+ ## or:
563
+ #cmdopt.each_option_and_desc do |optstr, desc, detail|
564
+ # puts &quot; %-20s : %s\n&quot; % [optstr, desc]
565
+ # puts detail.gsub(/^/, &#039; &#039; * 25) if detail
566
+ #end
567
+ </pre>
568
+ <p>Output:</p>
569
+ <pre>
570
+ -m, --mode=&ltMODE&gt; : output mode
571
+ v, verbose: print many output
572
+ q, quiet: print litte output
573
+ c, compact: print summary output
574
+ </pre>
575
+ </section>
576
+ <section class="subsection" id="option-tag">
577
+ <h3>Option Tag</h3>
578
+ <p><code>#add()</code> method in <code>Benry::CmdOpt</code> or <code>Benry::CmdOpt::Schema</code> supports <code>tag:</code> keyword argument.
579
+ You can use it for any purpose.</p>
580
+ <pre class="language-ruby">
581
+ require &#039;benry/cmdopt&#039;
582
+
583
+ cmdopt = Benry::CmdOpt.new()
584
+ cmdopt.add(:help, &quot;-h, --help&quot;, &quot;help message&quot;, <strong>tag: &quot;important&quot;</strong>) # !!!
585
+ cmdopt.add(:version, &quot;--version&quot;, &quot;print version&quot;, <strong>tag: nil</strong>)
586
+ cmdopt.schema.each do |item|
587
+ puts &quot;#{item.key}: tag=#{item.tag.inspect}&quot;
588
+ end
589
+
590
+ ## output:
591
+ #help: <strong>tag=&quot;important&quot;</strong>
592
+ #version: <strong>tag=nil</strong>
593
+ </pre>
594
+ </section>
595
+ <section class="subsection" id="not-supported">
596
+ <h3>Not Supported</h3>
597
+ <ul>
598
+ <li>default value</li>
599
+ <li><code>--no-xxx</code> style option</li>
600
+ <li>bash/zsh completion</li>
601
+ <li>I18N of error message (may be supported in the future)</li>
602
+ </ul>
603
+ </section>
604
+ </section>
605
+ <section class="section" id="internal-classes">
606
+ <h2>Internal Classes</h2>
607
+ <ul>
608
+ <li><code>Benry::CmdOpt::Schema</code> ... command option schema.</li>
609
+ <li><code>Benry::CmdOpt::Parser</code> ... command option parser.</li>
610
+ <li><code>Benry::CmdOpt::Facade</code> ... facade object including schema and parser.</li>
611
+ </ul>
612
+ <pre class="language-ruby">
613
+ require &#039;benry/cmdopt&#039;
614
+
615
+ ## define schema
616
+ <strong>schema = Benry::CmdOpt::Schema.new</strong>
617
+ schema.add(:help , &#039;-h, --help&#039; , &quot;show help message&quot;)
618
+ schema.add(:file , &#039;-f, --file=&ltFILE&gt;&#039; , &quot;filename&quot;)
619
+ schema.add(:indent, &#039;-i, --indent[=&ltWIDTH&gt;]&#039;, &quot;enable indent&quot;, type: Integer)
620
+
621
+ ## parse options
622
+ <strong>parser = Benry::CmdOpt::Parser.new(schema)</strong>
623
+ argv = [&#039;-hi2&#039;, &#039;--file=blabla.txt&#039;, &#039;aaa&#039;, &#039;bbb&#039;]
624
+ opts = parser.parse(argv) do |err|
625
+ $stderr.puts &quot;ERROR: #{err.message}&quot;
626
+ exit 1
627
+ end
628
+ p opts #=&gt; {:help=&gt;true, :indent=&gt;2, :file=&gt;&quot;blabla.txt&quot;}
629
+ p argv #=&gt; [&quot;aaa&quot;, &quot;bbb&quot;]
630
+ </pre>
631
+ <p>Notice that <code>Benry::CmdOpt.new()</code> returns a facade object.</p>
632
+ <pre class="language-ruby">
633
+ require &#039;benry/cmdopt&#039;
634
+
635
+ <strong>cmdopt = Benry::CmdOpt.new() # new facade object</strong>
636
+ <strong>cmdopt.add</strong>(:help, &#039;-h&#039;, &quot;help message&quot;) # same as <strong>schema.add</strong>(...)
637
+ opts = <strong>cmdopt.parse</strong>(ARGV) # same as <strong>parser.parse</strong>(...)
638
+ </pre>
639
+ <p>Notice that <code>cmdopt.is_a?(Benry::CmdOpt)</code> results in false.
640
+ Use <code>cmdopt.is_a?(Benry::CmdOpt::Facade)</code> instead if necessary.</p>
641
+ </section>
642
+ <section class="section" id="license-and-copyright">
643
+ <h2>License and Copyright</h2>
644
+ <p>$License: MIT License $</p>
645
+ <p>$Copyright: copyright(c) 2021 kwatch@gmail.com $</p>
646
+ </section>
647
+ </section>
648
+ </main>
649
+ </body>
650
+ </html>