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