benry-cmdopt 1.1.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>