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.
@@ -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>