benry-cmdopt 1.1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 &gt;= 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 #=&gt; {:verbose=&gt;true} # hash key is long option name
102
+ #
103
+ opts = {}
104
+ parser.parse!(['-q'], 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 '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 #=&gt; {:verbose=&gt;true} # independent hash key of option name
115
+ #
116
+ opts = cmdopt.parse(['-q']) # 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'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 &ltN&gt;', "number", Integer, <strong>(1..)</strong>) #=&gt; NoMethodError
132
+
133
+ ### benry/cmdopt.rb
134
+ require 'benry/cmdopt'
135
+ cmdopt = Benry::CmdOpt.new
136
+ cmdopt.add(:number, "-n &ltN&gt;", "number", 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('-n &ltN&gt;', "number", Integer, <strong>[1, 2, 3]</strong>) # wrong
149
+ parser.on('-n &ltN&gt;', "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 &ltN&gt;", "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 &lthost&gt;'</strong>, "hostname") {|v| opts[:host] = v }
171
+ parser.parse([<strong>'--help'</strong>]) # &lt== terminates current process!!
172
+ puts 'xxx' #&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 '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>]) # &lt== terminates current process!!
192
+ puts 'xxx' #&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'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 '[options]' to '[&ltoptions&gt;]', 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 &ltoptions&gt;"
218
+ parser = OptionParser.new(banner) # or: OptionParser.new(banner, 25)
219
+ parser.on('-f', '--file=&ltFILE&gt;', "filename")
220
+ parser.on('-m &ltMODE&gt;' , "verbose/quiet")
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 'benry/cmdopt'
229
+ cmdopt = Benry::CmdOpt.new()
230
+ cmdopt.add(:file, '-f, --file=&ltFILE&gt;', "filename")
231
+ cmdopt.add(:mode, '-m &ltMODE&gt;' , "verbose/quiet")
232
+ puts "Usage: blabla [&ltoptions&gt;]"
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'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=&ltFILE&gt;', "filename")
251
+ opts = {}
252
+ begin
253
+ parser.parse!(ARGV, into: opts)
254
+ <strong>rescue OptionParser::ParseError =&gt; 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=&ltFILE&gt;', "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 =&gt; true, :version =&gt; true}
304
+ p ARGV # options are removed from ARGV
305
+
306
+ ## help
307
+ if options[:help]
308
+ puts "Usage: foobar [&ltoptions&gt;] [&ltargs&gt;...]"
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>=&ltFILE&gt;</strong>', "filename") # short &amp; long
331
+ cmdopt.add(:file, ' --file<strong>=&ltFILE&gt;</strong>', "filename") # long only
332
+ cmdopt.add(:file, '-f <strong>&ltFILE&gt;</strong>' , "filename") # short only
333
+
334
+ ## optional parameter
335
+ cmdopt.add(:indent, '-i, --indent<strong>[=&ltN&gt;]</strong>', "indent width") # short &amp; long
336
+ cmdopt.add(:indent, ' --indent<strong>[=&ltN&gt;]</strong>', "indent width") # long only
337
+ cmdopt.add(:indent, '-i<strong>[&ltN&gt;]</strong>' , "indent width") # short only
338
+ </pre>
339
+ <p>Notice that <code>"--file &ltFILE&gt;"</code> style is <strong>not supported for usability reason</strong>.
340
+ Use <code>"--file=&ltFILE&gt;"</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 &ltN&gt;', "indent width", <strong>type: Integer</strong>)
350
+ ## pattern (regular expression)
351
+ cmdopt.add(:indent , '-i &ltN&gt;', "indent width", <strong>rexp: /\A\d+\z/</strong>)
352
+ ## enum (Array or Set)
353
+ cmdopt.add(:indent , '-i &ltN&gt;', "indent width", <strong>enum: ["2", "4", "8"]</strong>)
354
+ ## range (endless range such as ``1..`` available)
355
+ cmdopt.add(:indent , '-i &ltN&gt;', "indent width", <strong>range: (0..8)</strong>)
356
+ ## callback
357
+ cmdopt.add(:indent , '-i &ltN&gt;', "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 &ltlang&gt;', "language", <strong>enum: ["en", "fr", "it"]</strong>)
382
+
383
+ ## error: enum values are not Integer
384
+ cmdopt.add(:lang, '-l &ltlang&gt;', "language", <strong>enum: ["en", "fr", "it"], type: Integer</strong>)
385
+
386
+ ## ok
387
+ cmdopt.add(:indent, '-i &ltN&gt;', "indent", <strong>range: (0..), type: Integer</strong>)
388
+
389
+ ## error: beginning value of range is not a String
390
+ cmdopt.add(:indent, '-i &ltN&gt;', "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=&lton|off&gt;"</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=&gt;<strong>true</strong>}
411
+ $ ruby ex3.rb <strong>--foo=on</strong> # enable
412
+ {:foo=&gt;<strong>true</strong>}
413
+ $ ruby ex3.rb <strong>--foo=off</strong> # disable
414
+ {:foo=&gt;<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] #=&gt; true # normal
428
+ p options[:help2] #=&gt; <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[=&lton|off&gt;]", "f1", type: TrueClass)
435
+ cmdopt.add(:flag2, "--flag2[=&lton|off&gt;]", "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] #=&gt; true
440
+ p options[:flag2] #=&gt; <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 &ltNAME&gt;', "library name") <strong>{|options, key, val|</strong>
450
+ <strong>arr = options[key] || []</strong>
451
+ <strong>arr &lt&lt val</strong>
452
+ <strong>arr</strong>
453
+ ## or:
454
+ #(options[key] || []) &lt&lt val
455
+ <strong>}</strong>
456
+
457
+ options = cmdopt.parse(["-I", "foo", "-I", "bar", "-Ibaz"])
458
+ p options #=&gt; <strong>{:lib=&gt;["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 #=&gt; {:help=&gt;true, <strong>:version=&gt;true</strong>}
509
+ p argv #=&gt; ["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 #=&gt; {:help=&gt;true}
515
+ p argv #=&gt; ["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 #=&gt; {:help=&gt;true}
528
+ p argv #=&gt; ["commit", "xxx", "-m", "yyy"]
529
+
530
+ ## get sub-command
531
+ sub_command = argv.shift()
532
+ p sub_command #=&gt; "commit"
533
+ p argv #=&gt; ["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 &ltmessage&gt;', "commit message")
540
+ else
541
+ # ...
542
+ end
543
+ sub_opts = cmdopt.parse(argv, <strong>all: true</strong>) # !!!true!!!
544
+ p sub_opts #=&gt; {:message =&gt; "yyy"}
545
+ p argv #=&gt; ["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=&ltMODE&gt;", "output mode", <strong>detail: &lt&lt"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=&ltMODE&gt; : 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=&ltFILE&gt;' , "filename")
618
+ schema.add(:indent, '-i, --indent[=&ltWIDTH&gt;]', "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 #=&gt; {:help=&gt;true, :indent=&gt;2, :file=&gt;"blabla.txt"}
627
+ p argv #=&gt; ["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>