├── .gitignore ├── .travis.yml ├── README.md ├── Rakefile ├── mrbgem.rake ├── mrblib └── optparse.rb ├── optparse.rb.diff └── test └── test_optparse.rb /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | directories: 3 | - tmp 4 | before_cache: 5 | - "rake mruby" 6 | script: 7 | - "rake test" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mruby-optparse 2 | 3 | This is a port of OptionParser from CRuby. 4 | 5 | See [the CRuby OptionParser 6 | documentation](http://docs.ruby-lang.org/en/2.3.0/OptionParser.html) for usage 7 | documentation. All base features of CRuby OptionParser class should work 8 | except using the ARGV extensions. The additional acceptables you can require 9 | in CRuby (ac, shellwords, uri, date, time, version) are not included. 10 | 11 | This port of OptionParser provides OptionParser#record_separator as mruby does 12 | not provide `$/`. 13 | 14 | See [optparse.rb.diff](optparse.rb.diff) for the diff from CRuby's optparse 15 | (from Ruby 2.3.1-ish) to this port. 16 | 17 | ## License 18 | 19 | Copyright (C) Yukihiro Matsumoto. All rights reserved. 20 | 21 | Redistribution and use in source and binary forms, with or without 22 | modification, are permitted provided that the following conditions 23 | are met: 24 | 1. Redistributions of source code must retain the above copyright 25 | notice, this list of conditions and the following disclaimer. 26 | 2. Redistributions in binary form must reproduce the above copyright 27 | notice, this list of conditions and the following disclaimer in the 28 | documentation and/or other materials provided with the distribution. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 31 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 32 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 34 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 35 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 36 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 37 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 38 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 39 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | 3 | BUILD_CONFIG_FILE = File.expand_path 'tmp/mruby_build_config.rb' 4 | MRUBY_VERSION = ENV.fetch 'MRUBY_VERSION', '1.2.0' 5 | MRUBY_ROOT = File.join 'tmp', "mruby-#{MRUBY_VERSION}" 6 | MRUBY_EXE = "#{MRUBY_ROOT}/bin/mruby" 7 | 8 | CLOBBER << 'tmp' 9 | 10 | task default: :test 11 | 12 | def mruby_rake *tasks 13 | raise 'BUILD_CONFIG_FILE unset, add tmp/mruby_build_config.rb dependency' if 14 | BUILD_CONFIG_FILE.empty? 15 | 16 | cd MRUBY_ROOT do 17 | env = { 18 | 'MRUBY_CONFIG' => BUILD_CONFIG_FILE, 19 | } 20 | 21 | $stderr.puts "minirake #{tasks.join ' '}" if Rake.application.options.trace 22 | 23 | pid = Process.spawn env, 'ruby', 'minirake', *tasks 24 | 25 | Process.wait pid 26 | 27 | fail 'mruby exited non-zero' unless $?.success? 28 | end 29 | end 30 | 31 | def mruby_bin *args 32 | sh MRUBY_EXE, *args 33 | end 34 | 35 | tests = FileList['test/**/test_*.rb'] 36 | 37 | desc 'Run tests with mruby' 38 | task test: [:mruby, *tests] do 39 | mruby_bin(*tests) 40 | end 41 | 42 | desc 'Build mruby' 43 | task mruby: MRUBY_EXE 44 | 45 | directory 'tmp' 46 | 47 | file BUILD_CONFIG_FILE => 'tmp' do |t| 48 | local_gem = File.expand_path File.dirname __FILE__ 49 | 50 | build_config = <<-BUILD_CONFIG 51 | MRuby::Build.new do |conf| 52 | toolchain :gcc 53 | 54 | conf.gembox 'default' 55 | 56 | conf.gem mgem: 'mtest' 57 | conf.gem #{local_gem.dump} 58 | end 59 | BUILD_CONFIG 60 | 61 | File.write BUILD_CONFIG_FILE, build_config 62 | end 63 | 64 | mruby_file = "mruby-#{MRUBY_VERSION}.tar.gz" 65 | mruby_download = "tmp/#{mruby_file}" 66 | 67 | file mruby_download => "tmp" do 68 | sh 'curl', '-s', '-L', '-o', mruby_download, 69 | '--fail', '--retry', '3', '--retry-delay', '1', 70 | "https://github.com/mruby/mruby/archive/#{MRUBY_VERSION}.tar.gz" 71 | end 72 | 73 | directory MRUBY_ROOT => mruby_download do 74 | sh 'tar', 'xzf', mruby_download, '-C', 'tmp' 75 | end 76 | 77 | task :update_gems do 78 | Rake::FileList["#{MRUBY_ROOT}/build/mrbgems/*"].each do |gem_dir| 79 | sh 'git', '-C', gem_dir, 'pull' 80 | end 81 | end 82 | 83 | mruby_deps = Rake::FileList[ 84 | BUILD_CONFIG_FILE, 85 | MRUBY_ROOT, 86 | 'mrblib/**/*', 87 | ] 88 | 89 | file MRUBY_EXE => [:update_gems, *mruby_deps] do 90 | mruby_rake 'all' 91 | end 92 | 93 | -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | MRuby::Gem::Specification.new('mruby-optparse') do |spec| 2 | spec.license = 'MIT' 3 | spec.author = 'Internet Initiative Japan Inc.' 4 | 5 | spec.add_dependency 'mruby-array-ext', core: 'mruby-array-ext' 6 | spec.add_dependency 'mruby-exit', core: 'mruby-exit' 7 | spec.add_dependency 'mruby-hash-ext', core: 'mruby-hash-ext' 8 | spec.add_dependency 'mruby-proc-ext', core: 'mruby-proc-ext' 9 | spec.add_dependency 'mruby-string-ext', core: 'mruby-string-ext' 10 | 11 | spec.add_dependency 'mruby-catch-throw' 12 | spec.add_dependency 'mruby-env' 13 | spec.add_dependency 'mruby-onig-regexp' 14 | 15 | spec.add_test_dependency 'mruby-mtest' 16 | end 17 | -------------------------------------------------------------------------------- /mrblib/optparse.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # 3 | # optparse.rb - command-line option analysis with the OptionParser class. 4 | # 5 | # Author:: Nobu Nakada 6 | # Documentation:: Nobu Nakada and Gavin Sinclair. 7 | # 8 | # See OptionParser for documentation. 9 | # 10 | 11 | 12 | #-- 13 | # == Developer Documentation (not for RDoc output) 14 | # 15 | # === Class tree 16 | # 17 | # - OptionParser:: front end 18 | # - OptionParser::Switch:: each switches 19 | # - OptionParser::List:: options list 20 | # - OptionParser::ParseError:: errors on parsing 21 | # - OptionParser::AmbiguousOption 22 | # - OptionParser::NeedlessArgument 23 | # - OptionParser::MissingArgument 24 | # - OptionParser::InvalidOption 25 | # - OptionParser::InvalidArgument 26 | # - OptionParser::AmbiguousArgument 27 | # 28 | # === Object relationship diagram 29 | # 30 | # +--------------+ 31 | # | OptionParser |<>-----+ 32 | # +--------------+ | +--------+ 33 | # | ,-| Switch | 34 | # on_head -------->+---------------+ / +--------+ 35 | # accept/reject -->| List |<|>- 36 | # | |<|>- +----------+ 37 | # on ------------->+---------------+ `-| argument | 38 | # : : | class | 39 | # +---------------+ |==========| 40 | # on_tail -------->| | |pattern | 41 | # +---------------+ |----------| 42 | # OptionParser.accept ->| DefaultList | |converter | 43 | # reject |(shared between| +----------+ 44 | # | all instances)| 45 | # +---------------+ 46 | # 47 | #++ 48 | # 49 | # == OptionParser 50 | # 51 | # === Introduction 52 | # 53 | # OptionParser is a class for command-line option analysis. It is much more 54 | # advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented 55 | # solution. 56 | # 57 | # === Features 58 | # 59 | # 1. The argument specification and the code to handle it are written in the 60 | # same place. 61 | # 2. It can output an option summary; you don't need to maintain this string 62 | # separately. 63 | # 3. Optional and mandatory arguments are specified very gracefully. 64 | # 4. Arguments can be automatically converted to a specified class. 65 | # 5. Arguments can be restricted to a certain set. 66 | # 67 | # All of these features are demonstrated in the examples below. See 68 | # #make_switch for full documentation. 69 | # 70 | # === Minimal example 71 | # 72 | # require 'optparse' 73 | # 74 | # options = {} 75 | # OptionParser.new do |opts| 76 | # opts.banner = "Usage: example.rb [options]" 77 | # 78 | # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| 79 | # options[:verbose] = v 80 | # end 81 | # end.parse! 82 | # 83 | # p options 84 | # p ARGV 85 | # 86 | # === Generating Help 87 | # 88 | # OptionParser can be used to automatically generate help for the commands you 89 | # write: 90 | # 91 | # require 'optparse' 92 | # 93 | # Options = Struct.new(:name) 94 | # 95 | # class Parser 96 | # def self.parse(options) 97 | # args = Options.new("world") 98 | # 99 | # opt_parser = OptionParser.new do |opts| 100 | # opts.banner = "Usage: example.rb [options]" 101 | # 102 | # opts.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| 103 | # args.name = n 104 | # end 105 | # 106 | # opts.on("-h", "--help", "Prints this help") do 107 | # puts opts 108 | # exit 109 | # end 110 | # end 111 | # 112 | # opt_parser.parse!(options) 113 | # return args 114 | # end 115 | # end 116 | # options = Parser.parse %w[--help] 117 | # 118 | # #=> 119 | # # Usage: example.rb [options] 120 | # # -n, --name=NAME Name to say hello to 121 | # # -h, --help Prints this help 122 | # 123 | # === Required Arguments 124 | # 125 | # For options that require an argument, option specification strings may include an 126 | # option name in all caps. If an option is used without the required argument, 127 | # an exception will be raised. 128 | # require 'optparse' 129 | # 130 | # options = {} 131 | # OptionParser.new do |parser| 132 | # parser.on("-r", "--require LIBRARY", 133 | # "Require the LIBRARY before executing your script") do |lib| 134 | # puts "You required #{lib}!" 135 | # end 136 | # end.parse! 137 | # 138 | # Used: 139 | # 140 | # bash-3.2$ ruby optparse-test.rb -r 141 | # optparse-test.rb:9:in `
': missing argument: -r (OptionParser::MissingArgument) 142 | # bash-3.2$ ruby optparse-test.rb -r my-library 143 | # You required my-library! 144 | # 145 | # === Type Coercion 146 | # 147 | # OptionParser supports the ability to coerce command line arguments 148 | # into objects for us. 149 | # 150 | # OptionParser comes with a few ready-to-use kinds of type 151 | # coercion. They are: 152 | # 153 | # - Date -- Anything accepted by +Date.parse+ 154 | # - DateTime -- Anything accepted by +DateTime.parse+ 155 | # - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ 156 | # - URI -- Anything accepted by +URI.parse+ 157 | # - Shellwords -- Anything accepted by +Shellwords.shellwords+ 158 | # - String -- Any non-empty string 159 | # - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040) 160 | # - Float -- Any float. (e.g. 10, 3.14, -100E+13) 161 | # - Numeric -- Any integer, float, or rational (1, 3.4, 1/3) 162 | # - DecimalInteger -- Like +Integer+, but no octal format. 163 | # - OctalInteger -- Like +Integer+, but no decimal format. 164 | # - DecimalNumeric -- Decimal integer or float. 165 | # - TrueClass -- Accepts '+, yes, true, -, no, false' and 166 | # defaults as +true+ 167 | # - FalseClass -- Same as +TrueClass+, but defaults to +false+ 168 | # - Array -- Strings separated by ',' (e.g. 1,2,3) 169 | # - Regexp -- Regular expressions. Also includes options. 170 | # 171 | # We can also add our own coercions, which we will cover soon. 172 | # 173 | # ==== Using Built-in Conversions 174 | # 175 | # As an example, the built-in +Time+ conversion is used. The other built-in 176 | # conversions behave in the same way. 177 | # OptionParser will attempt to parse the argument 178 | # as a +Time+. If it succeeds, that time will be passed to the 179 | # handler block. Otherwise, an exception will be raised. 180 | # 181 | # require 'optparse' 182 | # require 'optparse/time' 183 | # OptionParser.new do |parser| 184 | # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| 185 | # p time 186 | # end 187 | # end.parse! 188 | # 189 | # Used: 190 | # bash-3.2$ ruby optparse-test.rb -t nonsense 191 | # ... invalid argument: -t nonsense (OptionParser::InvalidArgument) 192 | # from ... time.rb:5:in `block in ' 193 | # from optparse-test.rb:31:in `
' 194 | # bash-3.2$ ruby optparse-test.rb -t 10-11-12 195 | # 2010-11-12 00:00:00 -0500 196 | # bash-3.2$ ruby optparse-test.rb -t 9:30 197 | # 2014-08-13 09:30:00 -0400 198 | # 199 | # ==== Creating Custom Conversions 200 | # 201 | # The +accept+ method on OptionParser may be used to create converters. 202 | # It specifies which conversion block to call whenever a class is specified. 203 | # The example below uses it to fetch a +User+ object before the +on+ handler receives it. 204 | # 205 | # require 'optparse' 206 | # 207 | # User = Struct.new(:id, :name) 208 | # 209 | # def find_user id 210 | # not_found = ->{ raise "No User Found for id #{id}" } 211 | # [ User.new(1, "Sam"), 212 | # User.new(2, "Gandalf") ].find(not_found) do |u| 213 | # u.id == id 214 | # end 215 | # end 216 | # 217 | # op = OptionParser.new 218 | # op.accept(User) do |user_id| 219 | # find_user user_id.to_i 220 | # end 221 | # 222 | # op.on("--user ID", User) do |user| 223 | # puts user 224 | # end 225 | # 226 | # op.parse! 227 | # 228 | # output: 229 | # bash-3.2$ ruby optparse-test.rb --user 1 230 | # # 231 | # bash-3.2$ ruby optparse-test.rb --user 2 232 | # # 233 | # bash-3.2$ ruby optparse-test.rb --user 3 234 | # optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) 235 | # === Complete example 236 | # 237 | # The following example is a complete Ruby program. You can run it and see the 238 | # effect of specifying various options. This is probably the best way to learn 239 | # the features of +optparse+. 240 | # 241 | # require 'optparse' 242 | # require 'optparse/time' 243 | # require 'ostruct' 244 | # require 'pp' 245 | # 246 | # class OptparseExample 247 | # Version = '1.0.0' 248 | # 249 | # CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] 250 | # CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } 251 | # 252 | # class ScriptOptions 253 | # attr_accessor :library, :inplace, :encoding, :transfer_type, 254 | # :verbose 255 | # def initialize 256 | # self.library = [] 257 | # self.inplace = false 258 | # self.encoding = "utf8" 259 | # self.transfer_type = :auto 260 | # self.verbose = false 261 | # end 262 | # end 263 | # 264 | # # 265 | # # Return a structure describing the options. 266 | # # 267 | # def self.parse(args) 268 | # # The options specified on the command line will be collected in 269 | # # *options*. 270 | # 271 | # @options = ScriptOptions.new 272 | # option_parser.parse!(args) 273 | # @options 274 | # end 275 | # 276 | # attr_reader :parser, :options 277 | # 278 | # def option_parser 279 | # @parser ||= OptionParser.new do |parser| 280 | # parser.banner = "Usage: example.rb [options]" 281 | # parser.separator "" 282 | # parser.separator "Specific options:" 283 | # 284 | # # add additional options 285 | # perform_inplace_option 286 | # delay_execution_option 287 | # execute_at_time_option 288 | # specify_record_separator_option 289 | # list_example_option 290 | # specify_encoding_option 291 | # optional_option_argument_with_keyword_completion_option 292 | # boolean_verbose_option 293 | # 294 | # parser.separator "" 295 | # parser.separator "Common options:" 296 | # # No argument, shows at tail. This will print an options summary. 297 | # # Try it and see! 298 | # parser.on_tail("-h", "--help", "Show this message") do 299 | # puts parser 300 | # exit 301 | # end 302 | # # Another typical switch to print the version. 303 | # parser.on_tail("--version", "Show version") do 304 | # puts Version 305 | # exit 306 | # end 307 | # end 308 | # end 309 | # 310 | # def perform_inplace_option 311 | # # Specifies an optional option argument 312 | # parser.on("-i", "--inplace [EXTENSION]", 313 | # "Edit ARGV files in place", 314 | # " (make backup if EXTENSION supplied)") do |ext| 315 | # options.inplace = true 316 | # options.extension = ext || '' 317 | # options.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. 318 | # end 319 | # end 320 | # 321 | # def delay_execution_option 322 | # # Cast 'delay' argument to a Float. 323 | # parser.on("--delay N", Float, "Delay N seconds before executing") do |n| 324 | # options.delay = n 325 | # end 326 | # end 327 | # 328 | # def execute_at_time_option 329 | # # Cast 'time' argument to a Time object. 330 | # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| 331 | # options.time = time 332 | # end 333 | # end 334 | # 335 | # 336 | # def specify_record_separator_option 337 | # # Cast to octal integer. 338 | # parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger, 339 | # "Specify record separator (default \\0)") do |rs| 340 | # options.record_separator = rs 341 | # end 342 | # end 343 | # 344 | # def list_example_option 345 | # # List of arguments. 346 | # parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| 347 | # options.list = list 348 | # end 349 | # end 350 | # 351 | # def specify_encoding_option 352 | # # Keyword completion. We are specifying a specific set of arguments (CODES 353 | # # and CODE_ALIASES - notice the latter is a Hash), and the user may provide 354 | # # the shortest unambiguous text. 355 | # code_list = (CODE_ALIASES.keys + CODES).join(',') 356 | # parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", 357 | # " (#{code_list})") do |encoding| 358 | # options.encoding = encoding 359 | # end 360 | # end 361 | # 362 | # 363 | # def optional_option_argument_with_keyword_completion_option 364 | # # Optional '--type' option argument with keyword completion. 365 | # parser.on("--type [TYPE]", [:text, :binary, :auto], 366 | # "Select transfer type (text, binary, auto)") do |t| 367 | # options.transfer_type = t 368 | # end 369 | # end 370 | # 371 | # 372 | # def boolean_verbose_option 373 | # # Boolean switch. 374 | # parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| 375 | # options.verbose = v 376 | # end 377 | # end 378 | # 379 | # end # class OptparseExample 380 | # options = OptparseExample.parse(ARGV) 381 | # pp options 382 | # pp ARGV 383 | # 384 | # === Shell Completion 385 | # 386 | # For modern shells (e.g. bash, zsh, etc.), you can use shell 387 | # completion for command line options. 388 | # 389 | # === Further documentation 390 | # 391 | # The above examples should be enough to learn how to use this class. If you 392 | # have any questions, file a ticket at http://bugs.ruby-lang.org. 393 | # 394 | class OptionParser 395 | # :stopdoc: 396 | NoArgument = [NO_ARGUMENT = :NONE, nil] 397 | RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true] 398 | OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false] 399 | # :startdoc: 400 | 401 | # 402 | # Keyword completion module. This allows partial arguments to be specified 403 | # and resolved against a list of acceptable values. 404 | # 405 | module Completion 406 | def self.regexp(key, icase) 407 | icase = nil if icase == false 408 | Regexp.new('\A' + Regexp.quote(key).gsub(/(\w+\b)/, '\1\w*'), icase) 409 | end 410 | 411 | def self.candidate(key, icase = false, pat = nil, &block) 412 | pat ||= Completion.regexp(key, icase) 413 | candidates = [] 414 | block.call do |k, *v| 415 | (if Regexp === k 416 | kn = nil 417 | k === key 418 | else 419 | kn = k.respond_to?(:to_s) ? k.to_s : k 420 | pat === kn 421 | end) or next 422 | v << k if v.empty? 423 | candidates << [k, v, kn] 424 | end 425 | candidates 426 | end 427 | 428 | def candidate(key, icase = false, pat = nil) 429 | Completion.candidate(key, icase, pat) do |&b| 430 | each(&b) 431 | end 432 | end 433 | 434 | public 435 | def complete(key, icase = false, pat = nil, &block) 436 | candidates = candidate(key, icase, pat) do |&b| 437 | each(&b) 438 | end.sort_by {|k, v, kn| kn.size} 439 | if candidates.size == 1 440 | canon, sw, = candidates[0] 441 | elsif candidates.size > 1 442 | canon, sw, cn = candidates.shift 443 | candidates.each do |k, v, kn| 444 | next if sw == v 445 | if String === cn and String === kn 446 | if cn.rindex(kn, 0) 447 | canon, sw, cn = k, v, kn 448 | next 449 | elsif kn.rindex(cn, 0) 450 | next 451 | end 452 | end 453 | throw :ambiguous, key 454 | end 455 | end 456 | if canon 457 | block_given? or return key, *sw 458 | yield(key, *sw) 459 | end 460 | end 461 | 462 | def convert(opt = nil, val = nil, *) 463 | val 464 | end 465 | end 466 | 467 | 468 | # 469 | # Map from option/keyword string to object with completion. 470 | # 471 | class OptionMap < Hash 472 | include Completion 473 | end 474 | 475 | 476 | # 477 | # Individual switch class. Not important to the user. 478 | # 479 | # Defined within Switch are several Switch-derived classes: NoArgument, 480 | # RequiredArgument, etc. 481 | # 482 | class Switch 483 | attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block 484 | 485 | def self.>=(other) 486 | return true if other == self 487 | return false unless Class === other 488 | return true if other.ancestors.include? self 489 | end 490 | 491 | # 492 | # Guesses argument style from +arg+. Returns corresponding 493 | # OptionParser::Switch class (OptionalArgument, etc.). 494 | # 495 | def self.guess(arg) 496 | case arg 497 | when "" 498 | t = self 499 | when /\A=?\[/ 500 | t = Switch::OptionalArgument 501 | when /\A\s+\[/ 502 | t = Switch::PlacedArgument 503 | else 504 | t = Switch::RequiredArgument 505 | end 506 | self >= t or incompatible_argument_styles(arg, t) 507 | t 508 | end 509 | 510 | def self.incompatible_argument_styles(arg, t) 511 | raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}", 512 | ParseError.filter_backtrace(caller(2))) 513 | end 514 | 515 | def self.pattern 516 | NilClass 517 | end 518 | 519 | # XXX: the block argument here is suspect. Originally it was: 520 | # 521 | # block = Proc.new 522 | # 523 | # Which in ruby allows you to pass in a block (which is captured via 524 | # Proc.new in the block variable) or a pre-existing Method, Proc, or Lambda. 525 | # 526 | # On mruby Proc.new doesn't work that way and requires an explicit block 527 | # so this may be broken for some uses. 528 | def initialize(pattern = nil, conv = nil, 529 | short = nil, long = nil, arg = nil, 530 | desc = ([] if short or long), given_block = nil, &block) 531 | block = given_block if given_block 532 | raise if Array === pattern 533 | @pattern, @conv, @short, @long, @arg, @desc, @block = 534 | pattern, conv, short, long, arg, desc, block 535 | end 536 | 537 | # 538 | # Parses +arg+ and returns rest of +arg+ and matched portion to the 539 | # argument pattern. Yields when the pattern doesn't match substring. 540 | # 541 | def parse_arg(arg) 542 | pattern or return nil, [arg] 543 | unless m = pattern.match(arg) 544 | yield(InvalidArgument, arg) 545 | return arg, [] 546 | end 547 | if String === m 548 | m = [s = m] 549 | else 550 | m = m.to_a 551 | s = m[0] 552 | return nil, m unless String === s 553 | end 554 | raise InvalidArgument, arg unless arg.rindex(s, 0) 555 | return nil, m if s.length == arg.length 556 | yield(InvalidArgument, arg) # didn't match whole arg 557 | return arg[s.length..-1], m 558 | end 559 | private :parse_arg 560 | 561 | # 562 | # Parses argument, converts and returns +arg+, +block+ and result of 563 | # conversion. Yields at semi-error condition instead of raising an 564 | # exception. 565 | # 566 | def conv_arg(arg, val = []) 567 | if conv 568 | val = conv.call(*val) 569 | else 570 | val = proc {|v| v}.call(*val) 571 | end 572 | return arg, block, val 573 | end 574 | private :conv_arg 575 | 576 | # 577 | # Produces the summary text. Each line of the summary is yielded to the 578 | # block (without newline). 579 | # 580 | # +sdone+:: Already summarized short style options keyed hash. 581 | # +ldone+:: Already summarized long style options keyed hash. 582 | # +width+:: Width of left side (option part). In other words, the right 583 | # side (description part) starts after +width+ columns. 584 | # +max+:: Maximum width of left side -> the options are filled within 585 | # +max+ columns. 586 | # +indent+:: Prefix string indents all summarized lines. 587 | # 588 | def summarize(sdone = [], ldone = [], width = 1, max = width - 1, indent = "") 589 | sopts, lopts = [], [], nil 590 | @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short 591 | @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long 592 | return if sopts.empty? and lopts.empty? # completely hidden 593 | 594 | left = [sopts.join(', ')] 595 | right = desc.dup 596 | 597 | while s = lopts.shift 598 | l = left[-1].length + s.length 599 | l += arg.length if left.size == 1 && arg 600 | l < max or sopts.empty? or left << '' 601 | left[-1] << if left[-1].empty? then ' ' * 4 else ', ' end << s 602 | end 603 | 604 | if arg 605 | left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg) 606 | end 607 | mlen = left.collect {|ss| ss.length}.max.to_i 608 | while mlen > width and l = left.shift 609 | mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen 610 | if l.length < width and (r = right[0]) and !r.empty? 611 | l = l.to_s.ljust(width) + ' ' + r 612 | right.shift 613 | end 614 | yield(indent + l) 615 | end 616 | 617 | while begin l = left.shift; r = right.shift; l or r end 618 | l = l.to_s.ljust(width) + ' ' + r if r and !r.empty? 619 | yield(indent + l) 620 | end 621 | 622 | self 623 | end 624 | 625 | def add_banner(to) # :nodoc: 626 | unless @short or @long 627 | s = desc.join 628 | to << " [" + s + "]..." unless s.empty? 629 | end 630 | to 631 | end 632 | 633 | def match_nonswitch?(str) # :nodoc: 634 | @pattern =~ str unless @short or @long 635 | end 636 | 637 | # 638 | # Main name of the switch. 639 | # 640 | def switch_name 641 | (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '') 642 | end 643 | 644 | def compsys(sdone, ldone) # :nodoc: 645 | sopts, lopts = [], [] 646 | @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short 647 | @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long 648 | return if sopts.empty? and lopts.empty? # completely hidden 649 | 650 | (sopts+lopts).each do |opt| 651 | # "(-x -c -r)-l[left justify]" \ 652 | if /^--\[no-\](.+)$/ =~ opt 653 | o = $1 654 | yield("--#{o}", desc.join("")) 655 | yield("--no-#{o}", desc.join("")) 656 | else 657 | yield("#{opt}", desc.join("")) 658 | end 659 | end 660 | end 661 | 662 | # 663 | # Switch that takes no arguments. 664 | # 665 | class NoArgument < self 666 | 667 | # 668 | # Raises an exception if any arguments given. 669 | # 670 | def parse(arg, argv) 671 | yield(NeedlessArgument, arg) if arg 672 | conv_arg(arg) 673 | end 674 | 675 | def self.incompatible_argument_styles(*) 676 | end 677 | 678 | def self.pattern 679 | Object 680 | end 681 | end 682 | 683 | # 684 | # Switch that takes an argument. 685 | # 686 | class RequiredArgument < self 687 | 688 | # 689 | # Raises an exception if argument is not present. 690 | # 691 | def parse(arg, argv) 692 | unless arg 693 | raise MissingArgument if argv.empty? 694 | arg = argv.shift 695 | end 696 | parsed = parse_arg(arg) do |klass, message| 697 | raise klass, message 698 | end 699 | conv_arg(*parsed) 700 | end 701 | end 702 | 703 | # 704 | # Switch that can omit argument. 705 | # 706 | class OptionalArgument < self 707 | 708 | # 709 | # Parses argument if given, or uses default value. 710 | # 711 | def parse(arg, argv, &error) 712 | if arg 713 | conv_arg(*parse_arg(arg, &error)) 714 | else 715 | conv_arg(arg) 716 | end 717 | end 718 | end 719 | 720 | # 721 | # Switch that takes an argument, which does not begin with '-'. 722 | # 723 | class PlacedArgument < self 724 | 725 | # 726 | # Returns nil if argument is not present or begins with '-'. 727 | # 728 | def parse(arg, argv, &error) 729 | if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0])) 730 | return nil, block, nil 731 | end 732 | opt = (val = parse_arg(val, &error))[1] 733 | val = conv_arg(*val) 734 | if opt and !arg 735 | argv.shift 736 | else 737 | val[0] = nil 738 | end 739 | val 740 | end 741 | end 742 | end 743 | 744 | # 745 | # Simple option list providing mapping from short and/or long option 746 | # string to OptionParser::Switch and mapping from acceptable argument to 747 | # matching pattern and converter pair. Also provides summary feature. 748 | # 749 | class List 750 | # Map from acceptable argument types to pattern and converter pairs. 751 | attr_reader :atype 752 | 753 | # Map from short style option switches to actual switch objects. 754 | attr_reader :short 755 | 756 | # Map from long style option switches to actual switch objects. 757 | attr_reader :long 758 | 759 | # List of all switches and summary string. 760 | attr_reader :list 761 | 762 | # 763 | # Just initializes all instance variables. 764 | # 765 | def initialize 766 | @atype = {} 767 | @short = OptionMap.new 768 | @long = OptionMap.new 769 | @list = [] 770 | end 771 | 772 | # 773 | # See OptionParser.accept. 774 | # 775 | def accept(t, pat = /.*/m, &block) 776 | if pat 777 | pat.respond_to?(:match) or 778 | raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2)) 779 | else 780 | pat = t if t.respond_to?(:match) 781 | end 782 | unless block 783 | block = pat.method(:convert).to_proc if pat.respond_to?(:convert) 784 | end 785 | @atype[t] = [pat, block] 786 | end 787 | 788 | # 789 | # See OptionParser.reject. 790 | # 791 | def reject(t) 792 | @atype.delete(t) 793 | end 794 | 795 | # 796 | # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+. 797 | # 798 | # +sw+:: OptionParser::Switch instance to be added. 799 | # +sopts+:: Short style option list. 800 | # +lopts+:: Long style option list. 801 | # +nlopts+:: Negated long style options list. 802 | # 803 | def update(sw, sopts, lopts, nsw = nil, nlopts = nil) 804 | sopts.each {|o| @short[o] = sw} if sopts 805 | lopts.each {|o| @long[o] = sw} if lopts 806 | nlopts.each {|o| @long[o] = nsw} if nsw and nlopts 807 | used = @short.invert.update(@long.invert) 808 | @list.delete_if {|o| Switch === o and !used[o]} 809 | end 810 | private :update 811 | 812 | # 813 | # Inserts +switch+ at the head of the list, and associates short, long 814 | # and negated long options. Arguments are: 815 | # 816 | # +switch+:: OptionParser::Switch instance to be inserted. 817 | # +short_opts+:: List of short style options. 818 | # +long_opts+:: List of long style options. 819 | # +nolong_opts+:: List of long style options with "no-" prefix. 820 | # 821 | # prepend(switch, short_opts, long_opts, nolong_opts) 822 | # 823 | def prepend(*args) 824 | update(*args) 825 | @list.unshift(args[0]) 826 | end 827 | 828 | # 829 | # Appends +switch+ at the tail of the list, and associates short, long 830 | # and negated long options. Arguments are: 831 | # 832 | # +switch+:: OptionParser::Switch instance to be inserted. 833 | # +short_opts+:: List of short style options. 834 | # +long_opts+:: List of long style options. 835 | # +nolong_opts+:: List of long style options with "no-" prefix. 836 | # 837 | # append(switch, short_opts, long_opts, nolong_opts) 838 | # 839 | def append(*args) 840 | update(*args) 841 | @list.push(args[0]) 842 | end 843 | 844 | # 845 | # Searches +key+ in +id+ list. The result is returned or yielded if a 846 | # block is given. If it isn't found, nil is returned. 847 | # 848 | def search(id, key) 849 | if list = __send__(id) 850 | val = list.fetch(key) {return nil} 851 | block_given? ? yield(val) : val 852 | end 853 | end 854 | 855 | # 856 | # Searches list +id+ for +opt+ and the optional patterns for completion 857 | # +pat+. If +icase+ is true, the search is case insensitive. The result 858 | # is returned or yielded if a block is given. If it isn't found, nil is 859 | # returned. 860 | # 861 | def complete(id, opt, icase = false, *pat, &block) 862 | __send__(id).complete(opt, icase, *pat, &block) 863 | end 864 | 865 | # 866 | # Iterates over each option, passing the option to the +block+. 867 | # 868 | def each_option(&block) 869 | list.each(&block) 870 | end 871 | 872 | # 873 | # Creates the summary table, passing each line to the +block+ (without 874 | # newline). The arguments +args+ are passed along to the summarize 875 | # method which is called on every option. 876 | # 877 | def summarize(*args, &block) 878 | sum = [] 879 | list.reverse_each do |opt| 880 | if opt.respond_to?(:summarize) # perhaps OptionParser::Switch 881 | s = [] 882 | opt.summarize(*args) {|l| s << l} 883 | sum.concat(s.reverse) 884 | elsif !opt or opt.empty? 885 | sum << "" 886 | elsif opt.respond_to?(:lines) 887 | sum.concat([*opt.lines].reverse) 888 | else 889 | sum.concat([*opt.each].reverse) 890 | end 891 | end 892 | sum.reverse_each(&block) 893 | end 894 | 895 | def add_banner(to) # :nodoc: 896 | list.each do |opt| 897 | if opt.respond_to?(:add_banner) 898 | opt.add_banner(to) 899 | end 900 | end 901 | to 902 | end 903 | 904 | def compsys(*args, &block) # :nodoc: 905 | list.each do |opt| 906 | if opt.respond_to?(:compsys) 907 | opt.compsys(*args, &block) 908 | end 909 | end 910 | end 911 | end 912 | 913 | # 914 | # Hash with completion search feature. See OptionParser::Completion. 915 | # 916 | class CompletingHash < Hash 917 | include Completion 918 | 919 | # 920 | # Completion for hash key. 921 | # 922 | def match(key) 923 | *values = fetch(key) { 924 | raise AmbiguousArgument, catch(:ambiguous) {return complete(key)} 925 | } 926 | return key, *values 927 | end 928 | end 929 | 930 | # :stopdoc: 931 | 932 | # 933 | # Enumeration of acceptable argument styles. Possible values are: 934 | # 935 | # NO_ARGUMENT:: The switch takes no arguments. (:NONE) 936 | # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED) 937 | # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL) 938 | # 939 | # Use like --switch=argument (long style) or -Xargument (short style). For 940 | # short style, only portion matched to argument pattern is treated as 941 | # argument. 942 | # 943 | ArgumentStyle = {} 944 | NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} 945 | RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} 946 | OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument} 947 | 948 | # 949 | # Switches common used such as '--', and also provides default 950 | # argument classes 951 | # 952 | DefaultList = List.new 953 | DefaultList.short['-'] = Switch::NoArgument.new {} 954 | DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} 955 | 956 | 957 | COMPSYS_HEADER = " 958 | typeset -A opt_args 959 | local context state line 960 | 961 | _arguments -s -S \ 962 | " 963 | 964 | def compsys(to, name = File.basename($0)) # :nodoc: 965 | to << "#compdef #{name}\n" 966 | to << COMPSYS_HEADER 967 | visit(:compsys, {}, {}) {|o, d| 968 | to << %Q[ "#{o}[#{d.gsub(/([\"\[\]])/, '\\\\\1')}]" \\\n] 969 | } 970 | to << " '*:file:_files' && return 0\n" 971 | end 972 | 973 | # 974 | # Default options for ARGV, which never appear in option summary. 975 | # 976 | Officious = {} 977 | 978 | # 979 | # --help 980 | # Shows option summary. 981 | # 982 | Officious['help'] = proc do |parser| 983 | Switch::NoArgument.new do |arg| 984 | puts parser.help 985 | exit 986 | end 987 | end 988 | 989 | # 990 | # --*-completion-bash=WORD 991 | # Shows candidates for command line completion. 992 | # 993 | Officious['*-completion-bash'] = proc do |parser| 994 | Switch::RequiredArgument.new do |arg| 995 | puts parser.candidate(arg) 996 | exit 997 | end 998 | end 999 | 1000 | # 1001 | # --*-completion-zsh[=NAME:FILE] 1002 | # Creates zsh completion file. 1003 | # 1004 | Officious['*-completion-zsh'] = proc do |parser| 1005 | Switch::OptionalArgument.new do |arg| 1006 | parser.compsys(STDOUT, arg) 1007 | exit 1008 | end 1009 | end 1010 | 1011 | # 1012 | # --version 1013 | # Shows version string if Version is defined. 1014 | # 1015 | Officious['version'] = proc do |parser| 1016 | Switch::OptionalArgument.new do |pkg| 1017 | if pkg 1018 | begin 1019 | require 'optparse/version' 1020 | rescue LoadError 1021 | else 1022 | show_version(*pkg.split(/,/)) or 1023 | abort("#{parser.program_name}: no version found in package #{pkg}") 1024 | exit 1025 | end 1026 | end 1027 | v = parser.ver or abort("#{parser.program_name}: version unknown") 1028 | puts v 1029 | exit 1030 | end 1031 | end 1032 | 1033 | # :startdoc: 1034 | 1035 | # 1036 | # Class methods 1037 | # 1038 | 1039 | # 1040 | # Initializes a new instance and evaluates the optional block in context 1041 | # of the instance. Arguments +args+ are passed to #new, see there for 1042 | # description of parameters. 1043 | # 1044 | # This method is *deprecated*, its behavior corresponds to the older #new 1045 | # method. 1046 | # 1047 | def self.with(*args, &block) 1048 | opts = new(*args) 1049 | opts.instance_eval(&block) 1050 | opts 1051 | end 1052 | 1053 | # 1054 | # Returns an incremented value of +default+ according to +arg+. 1055 | # 1056 | def self.inc(arg, default = nil) 1057 | case arg 1058 | when Integer 1059 | arg.nonzero? 1060 | when nil 1061 | default.to_i + 1 1062 | end 1063 | end 1064 | def inc(*args) 1065 | self.class.inc(*args) 1066 | end 1067 | 1068 | # 1069 | # Initializes the instance and yields itself if called with a block. 1070 | # 1071 | # +banner+:: Banner message. 1072 | # +width+:: Summary width. 1073 | # +indent+:: Summary indent. 1074 | # 1075 | def initialize(banner = nil, width = 32, indent = ' ' * 4) 1076 | @stack = [DefaultList, List.new, List.new] 1077 | @program_name = nil 1078 | @banner = banner 1079 | @summary_width = width 1080 | @summary_indent = indent 1081 | @default_argv = [] 1082 | @record_separator = "\n" 1083 | add_officious 1084 | yield self if block_given? 1085 | end 1086 | 1087 | def add_officious # :nodoc: 1088 | list = base() 1089 | Officious.each do |opt, block| 1090 | list.long[opt] ||= block.call(self) 1091 | end 1092 | end 1093 | 1094 | # 1095 | # Terminates option parsing. Optional parameter +arg+ is a string pushed 1096 | # back to be the first non-option argument. 1097 | # 1098 | def terminate(arg = nil) 1099 | self.class.terminate(arg) 1100 | end 1101 | def self.terminate(arg = nil) 1102 | throw :terminate, arg 1103 | end 1104 | 1105 | @stack = [DefaultList] 1106 | def self.top() DefaultList end 1107 | 1108 | # 1109 | # Directs to accept specified class +t+. The argument string is passed to 1110 | # the block in which it should be converted to the desired class. 1111 | # 1112 | # +t+:: Argument class specifier, any object including Class. 1113 | # +pat+:: Pattern for argument, defaults to +t+ if it responds to match. 1114 | # 1115 | # accept(t, pat, &block) 1116 | # 1117 | def accept(*args, &blk) top.accept(*args, &blk) end 1118 | # 1119 | # See #accept. 1120 | # 1121 | def self.accept(*args, &blk) top.accept(*args, &blk) end 1122 | 1123 | # 1124 | # Directs to reject specified class argument. 1125 | # 1126 | # +t+:: Argument class specifier, any object including Class. 1127 | # 1128 | # reject(t) 1129 | # 1130 | def reject(*args, &blk) top.reject(*args, &blk) end 1131 | # 1132 | # See #reject. 1133 | # 1134 | def self.reject(*args, &blk) top.reject(*args, &blk) end 1135 | 1136 | # 1137 | # Instance methods 1138 | # 1139 | 1140 | # Heading banner preceding summary. 1141 | attr_writer :banner 1142 | 1143 | # Program name to be emitted in error message and default banner, 1144 | # defaults to $0. 1145 | attr_writer :program_name 1146 | 1147 | # Width for option list portion of summary. Must be Numeric. 1148 | attr_accessor :summary_width 1149 | 1150 | # Indentation for summary. Must be String (or have + String method). 1151 | attr_accessor :summary_indent 1152 | 1153 | # Strings to be parsed in default. 1154 | attr_accessor :default_argv 1155 | 1156 | # Default record separator ($/ in CRuby) 1157 | attr_accessor :record_separator 1158 | 1159 | # 1160 | # Heading banner preceding summary. 1161 | # 1162 | def banner 1163 | unless @banner 1164 | @banner = "Usage: #{program_name} [options]" 1165 | visit(:add_banner, @banner) 1166 | end 1167 | @banner 1168 | end 1169 | 1170 | # 1171 | # Program name to be emitted in error message and default banner, defaults 1172 | # to $0. 1173 | # 1174 | def program_name 1175 | @program_name || File.basename($0, '.*') 1176 | end 1177 | 1178 | # for experimental cascading :-) 1179 | alias set_banner banner= 1180 | alias set_program_name program_name= 1181 | alias set_summary_width summary_width= 1182 | alias set_summary_indent summary_indent= 1183 | 1184 | # Version 1185 | attr_writer :version 1186 | # Release code 1187 | attr_writer :release 1188 | 1189 | # 1190 | # Version 1191 | # 1192 | def version 1193 | @version || (defined?(::Version) && ::Version) 1194 | end 1195 | 1196 | # 1197 | # Release code 1198 | # 1199 | def release 1200 | @release || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE) 1201 | end 1202 | 1203 | # 1204 | # Returns version string from program_name, version and release. 1205 | # 1206 | def ver 1207 | if v = version 1208 | str = "#{program_name} #{[v].join('.')}" 1209 | str << " (#{v})" if v = release 1210 | str 1211 | end 1212 | end 1213 | 1214 | def warn(mesg = $!) 1215 | super("#{program_name}: #{mesg}") 1216 | end 1217 | 1218 | def abort(mesg = $!) 1219 | super("#{program_name}: #{mesg}") 1220 | end 1221 | 1222 | # 1223 | # Subject of #on / #on_head, #accept / #reject 1224 | # 1225 | def top 1226 | @stack[-1] 1227 | end 1228 | 1229 | # 1230 | # Subject of #on_tail. 1231 | # 1232 | def base 1233 | @stack[1] 1234 | end 1235 | 1236 | # 1237 | # Pushes a new List. 1238 | # 1239 | def new 1240 | @stack.push(List.new) 1241 | if block_given? 1242 | yield self 1243 | else 1244 | self 1245 | end 1246 | end 1247 | 1248 | # 1249 | # Removes the last List. 1250 | # 1251 | def remove 1252 | @stack.pop 1253 | end 1254 | 1255 | # 1256 | # Puts option summary into +to+ and returns +to+. Yields each line if 1257 | # a block is given. 1258 | # 1259 | # +to+:: Output destination, which must have method <<. Defaults to []. 1260 | # +width+:: Width of left side, defaults to @summary_width. 1261 | # +max+:: Maximum length allowed for left side, defaults to +width+ - 1. 1262 | # +indent+:: Indentation, defaults to @summary_indent. 1263 | # 1264 | def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) 1265 | blk ||= proc {|l| to << (l.index(@record_separator, -1) ? l : l + @record_separator)} 1266 | visit(:summarize, {}, {}, width, max, indent, &blk) 1267 | to 1268 | end 1269 | 1270 | # 1271 | # Returns option summary string. 1272 | # 1273 | def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end 1274 | alias to_s help 1275 | 1276 | # 1277 | # Returns option summary list. 1278 | # 1279 | def to_a; summarize("#{banner}".split(/^/)) end 1280 | 1281 | # 1282 | # Checks if an argument is given twice, in which case an ArgumentError is 1283 | # raised. Called from OptionParser#switch only. 1284 | # 1285 | # +obj+:: New argument. 1286 | # +prv+:: Previously specified argument. 1287 | # +msg+:: Exception message. 1288 | # 1289 | def notwice(obj, prv, msg) 1290 | unless !prv or prv == obj 1291 | raise(ArgumentError, "argument #{msg} given twice: #{obj}", 1292 | ParseError.filter_backtrace(caller(2))) 1293 | end 1294 | obj 1295 | end 1296 | private :notwice 1297 | 1298 | SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: 1299 | # 1300 | # Creates an OptionParser::Switch from the parameters. The parsed argument 1301 | # value is passed to the given block, where it can be processed. 1302 | # 1303 | # See at the beginning of OptionParser for some full examples. 1304 | # 1305 | # +opts+ can include the following elements: 1306 | # 1307 | # [Argument style:] 1308 | # One of the following: 1309 | # :NONE, :REQUIRED, :OPTIONAL 1310 | # 1311 | # [Argument pattern:] 1312 | # Acceptable option argument format, must be pre-defined with 1313 | # OptionParser.accept or OptionParser#accept, or Regexp. This can appear 1314 | # once or assigned as String if not present, otherwise causes an 1315 | # ArgumentError. Examples: 1316 | # Float, Time, Array 1317 | # 1318 | # [Possible argument values:] 1319 | # Hash or Array. 1320 | # [:text, :binary, :auto] 1321 | # %w[iso-2022-jp shift_jis euc-jp utf8 binary] 1322 | # { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } 1323 | # 1324 | # [Long style switch:] 1325 | # Specifies a long style switch which takes a mandatory, optional or no 1326 | # argument. It's a string of the following form: 1327 | # "--switch=MANDATORY" or "--switch MANDATORY" 1328 | # "--switch[=OPTIONAL]" 1329 | # "--switch" 1330 | # 1331 | # [Short style switch:] 1332 | # Specifies short style switch which takes a mandatory, optional or no 1333 | # argument. It's a string of the following form: 1334 | # "-xMANDATORY" 1335 | # "-x[OPTIONAL]" 1336 | # "-x" 1337 | # There is also a special form which matches character range (not full 1338 | # set of regular expression): 1339 | # "-[a-z]MANDATORY" 1340 | # "-[a-z][OPTIONAL]" 1341 | # "-[a-z]" 1342 | # 1343 | # [Argument style and description:] 1344 | # Instead of specifying mandatory or optional arguments directly in the 1345 | # switch parameter, this separate parameter can be used. 1346 | # "=MANDATORY" 1347 | # "=[OPTIONAL]" 1348 | # 1349 | # [Description:] 1350 | # Description string for the option. 1351 | # "Run verbosely" 1352 | # 1353 | # [Handler:] 1354 | # Handler for the parsed argument value. Either give a block or pass a 1355 | # Proc or Method as an argument. 1356 | # 1357 | def make_switch(opts, block = nil) 1358 | short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], [] 1359 | ldesc, sdesc, desc, arg = [], [], [] 1360 | default_style = Switch::NoArgument 1361 | default_pattern = nil 1362 | klass = nil 1363 | q, a = nil 1364 | 1365 | opts.each do |o| 1366 | # argument class 1367 | next if search(:atype, o) do |pat, c| 1368 | klass = notwice(o, klass, 'type') 1369 | if not_style and not_style != Switch::NoArgument 1370 | not_pattern, not_conv = pat, c 1371 | else 1372 | default_pattern, conv = pat, c 1373 | end 1374 | end 1375 | 1376 | # directly specified pattern(any object possible to match) 1377 | if (!(String === o || Symbol === o)) and o.respond_to?(:match) 1378 | pattern = notwice(o, pattern, 'pattern') 1379 | if pattern.respond_to?(:convert) 1380 | conv = Proc.new do |*a| 1381 | pattern.convert(*a) 1382 | end 1383 | else 1384 | conv = SPLAT_PROC 1385 | end 1386 | next 1387 | end 1388 | 1389 | # anything others 1390 | case o 1391 | when Proc 1392 | block = notwice(o, block, 'block') 1393 | when Array, Hash 1394 | case pattern 1395 | when CompletingHash 1396 | when nil 1397 | pattern = CompletingHash.new 1398 | conv = Proc.new do |*a| 1399 | pattern.convert(*a) 1400 | end if pattern.respond_to?(:convert) 1401 | else 1402 | raise ArgumentError, "argument pattern given twice" 1403 | end 1404 | o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} 1405 | when Module 1406 | raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) 1407 | when *ArgumentStyle.keys 1408 | style = notwice(ArgumentStyle[o], style, 'style') 1409 | when /^--no-([^\[\]=\s]*)(.+)?/ 1410 | q, a = $1, $2 1411 | o = notwice(a ? Object : TrueClass, klass, 'type') 1412 | not_pattern, not_conv = search(:atype, o) unless not_style 1413 | not_style = (not_style || default_style).guess(arg = a) if a 1414 | default_style = Switch::NoArgument 1415 | default_pattern, conv = search(:atype, FalseClass) unless default_pattern 1416 | ldesc << "--no-#{q}" 1417 | long << 'no-' + (q = q.downcase) 1418 | nolong << q 1419 | when /^--\[no-\]([^\[\]=\s]*)(.+)?/ 1420 | q, a = $1, $2 1421 | o = notwice(a ? Object : TrueClass, klass, 'type') 1422 | if a 1423 | default_style = default_style.guess(arg = a) 1424 | default_pattern, conv = search(:atype, o) unless default_pattern 1425 | end 1426 | ldesc << "--[no-]#{q}" 1427 | long << (o = q.downcase) 1428 | not_pattern, not_conv = search(:atype, FalseClass) unless not_style 1429 | not_style = Switch::NoArgument 1430 | nolong << 'no-' + o 1431 | when /^--([^\[\]=\s]*)(.+)?/ 1432 | q, a = $1, $2 1433 | if a 1434 | o = notwice(NilClass, klass, 'type') 1435 | default_style = default_style.guess(arg = a) 1436 | default_pattern, conv = search(:atype, o) unless default_pattern 1437 | end 1438 | ldesc << "--#{q}" 1439 | long << (o = q.downcase) 1440 | when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ 1441 | q, a = $1, $2 1442 | o = notwice(Object, klass, 'type') 1443 | if a 1444 | default_style = default_style.guess(arg = a) 1445 | default_pattern, conv = search(:atype, o) unless default_pattern 1446 | end 1447 | sdesc << "-#{q}" 1448 | short << Regexp.new(q) 1449 | when /^-(.)(.+)?/ 1450 | q, a = $1, $2 1451 | if a 1452 | o = notwice(NilClass, klass, 'type') 1453 | default_style = default_style.guess(arg = a) 1454 | default_pattern, conv = search(:atype, o) unless default_pattern 1455 | end 1456 | sdesc << "-#{q}" 1457 | short << q 1458 | when /^=/ 1459 | style = notwice(default_style.guess(arg = o), style, 'style') 1460 | default_pattern, conv = search(:atype, Object) unless default_pattern 1461 | else 1462 | desc.push(o) 1463 | end 1464 | end 1465 | 1466 | default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern 1467 | if !(short.empty? and long.empty?) 1468 | s = (style || default_style).new(pattern || default_pattern, 1469 | conv, sdesc, ldesc, arg, desc, block) 1470 | elsif !block 1471 | if style or pattern 1472 | raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) 1473 | end 1474 | s = desc 1475 | else 1476 | short << pattern 1477 | s = (style || default_style).new(pattern, 1478 | conv, nil, nil, arg, desc, block) 1479 | end 1480 | return s, short, long, 1481 | (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), 1482 | nolong 1483 | end 1484 | 1485 | def define(*opts, &block) 1486 | top.append(*(sw = make_switch(opts, block))) 1487 | sw[0] 1488 | end 1489 | 1490 | # 1491 | # Add option switch and handler. See #make_switch for an explanation of 1492 | # parameters. 1493 | # 1494 | def on(*opts, &block) 1495 | define(*opts, &block) 1496 | self 1497 | end 1498 | alias def_option define 1499 | 1500 | def define_head(*opts, &block) 1501 | top.prepend(*(sw = make_switch(opts, block))) 1502 | sw[0] 1503 | end 1504 | 1505 | # 1506 | # Add option switch like with #on, but at head of summary. 1507 | # 1508 | def on_head(*opts, &block) 1509 | define_head(*opts, &block) 1510 | self 1511 | end 1512 | alias def_head_option define_head 1513 | 1514 | def define_tail(*opts, &block) 1515 | base.append(*(sw = make_switch(opts, block))) 1516 | sw[0] 1517 | end 1518 | 1519 | # 1520 | # Add option switch like with #on, but at tail of summary. 1521 | # 1522 | def on_tail(*opts, &block) 1523 | define_tail(*opts, &block) 1524 | self 1525 | end 1526 | alias def_tail_option define_tail 1527 | 1528 | # 1529 | # Add separator in summary. 1530 | # 1531 | def separator(string) 1532 | top.append(string, nil, nil) 1533 | end 1534 | 1535 | # 1536 | # Parses command line arguments +argv+ in order. When a block is given, 1537 | # each non-option argument is yielded. 1538 | # 1539 | # Returns the rest of +argv+ left unparsed. 1540 | # 1541 | def order(*argv, &block) 1542 | argv = argv[0].dup if argv.size == 1 and Array === argv[0] 1543 | order!(argv, &block) 1544 | end 1545 | 1546 | # 1547 | # Same as #order, but removes switches destructively. 1548 | # Non-option arguments remain in +argv+. 1549 | # 1550 | def order!(argv = default_argv, &nonopt) 1551 | parse_in_order(argv, &nonopt) 1552 | end 1553 | 1554 | def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 1555 | opt, arg, val, rest = nil 1556 | nonopt ||= proc {|a| throw :terminate, a} 1557 | argv.unshift(arg) if arg = catch(:terminate) { 1558 | while arg = argv.shift 1559 | case arg 1560 | # long option 1561 | when /\A--([^=]*)(?:=(.*))?/m 1562 | opt, rest = $1, $2 1563 | begin 1564 | sw, = complete(:long, opt, true) 1565 | rescue ParseError => e 1566 | raise e.set_option(arg, true) 1567 | end 1568 | begin 1569 | opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} 1570 | val = cb.call(val) if cb 1571 | setter.call(sw.switch_name, val) if setter 1572 | rescue ParseError => e 1573 | raise e.set_option(arg, rest) 1574 | end 1575 | 1576 | # short option 1577 | when /\A-(.)((=).*|.+)?/m 1578 | opt, has_arg, eq, val, rest = $1, $3, $3, $2, $2 1579 | begin 1580 | sw, = search(:short, opt) 1581 | unless sw 1582 | begin 1583 | sw, = complete(:short, opt) 1584 | # short option matched. 1585 | val = arg.sub(/\A-/, '') 1586 | has_arg = true 1587 | rescue InvalidOption 1588 | # if no short options match, try completion with long 1589 | # options. 1590 | sw, = complete(:long, opt) 1591 | eq ||= !rest 1592 | end 1593 | end 1594 | rescue ParseError => e 1595 | raise e.set_option(arg, true) 1596 | end 1597 | begin 1598 | opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} 1599 | raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}" 1600 | argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') 1601 | val = cb.call(val) if cb 1602 | setter.call(sw.switch_name, val) if setter 1603 | rescue ParseError => e 1604 | raise e.set_option(arg, arg.length > 2) 1605 | end 1606 | 1607 | # non-option argument 1608 | else 1609 | catch(:prune) do 1610 | visit(:each_option) do |sw0| 1611 | sw = sw0 1612 | sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg) 1613 | end 1614 | nonopt.call(arg) 1615 | end 1616 | end 1617 | end 1618 | 1619 | nil 1620 | } 1621 | 1622 | visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern} 1623 | 1624 | argv 1625 | end 1626 | private :parse_in_order 1627 | 1628 | # 1629 | # Parses command line arguments +argv+ in permutation mode and returns 1630 | # list of non-option arguments. 1631 | # 1632 | def permute(*argv) 1633 | argv = argv[0].dup if argv.size == 1 and Array === argv[0] 1634 | permute!(argv) 1635 | end 1636 | 1637 | # 1638 | # Same as #permute, but removes switches destructively. 1639 | # Non-option arguments remain in +argv+. 1640 | # 1641 | def permute!(argv = default_argv) 1642 | nonopts = [] 1643 | order!(argv) do |arg| 1644 | nonopts << arg 1645 | end 1646 | argv[0, 0] = nonopts 1647 | argv 1648 | end 1649 | 1650 | # 1651 | # Parses command line arguments +argv+ in order when environment variable 1652 | # POSIXLY_CORRECT is set, and in permutation mode otherwise. 1653 | # 1654 | def parse(*argv) 1655 | argv = argv[0].dup if argv.size == 1 and Array === argv[0] 1656 | parse!(argv) 1657 | end 1658 | 1659 | # 1660 | # Same as #parse, but removes switches destructively. 1661 | # Non-option arguments remain in +argv+. 1662 | # 1663 | def parse!(argv = default_argv) 1664 | if ENV.include?('POSIXLY_CORRECT') 1665 | order!(argv) 1666 | else 1667 | permute!(argv) 1668 | end 1669 | end 1670 | 1671 | # 1672 | # Wrapper method for getopts.rb. 1673 | # 1674 | # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option) 1675 | # # params[:a] = true # -a 1676 | # # params[:b] = "1" # -b1 1677 | # # params[:foo] = "1" # --foo 1678 | # # params[:bar] = "x" # --bar x 1679 | # # params[:zot] = "z" # --zot Z 1680 | # 1681 | def getopts(*args) 1682 | argv = Array === args.first ? args.shift : default_argv 1683 | single_options, *long_options = *args 1684 | 1685 | result = {} 1686 | 1687 | single_options.scan(/(.)(:)?/) do |opt, val| 1688 | if val 1689 | result[opt] = nil 1690 | define("-#{opt} VAL") 1691 | else 1692 | result[opt] = false 1693 | define("-#{opt}") 1694 | end 1695 | end if single_options 1696 | 1697 | long_options.each do |arg| 1698 | arg, desc = arg.split(';', 2) 1699 | opt, val = arg.split(':', 2) 1700 | if val 1701 | result[opt] = val.empty? ? nil : val 1702 | define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) 1703 | else 1704 | result[opt] = false 1705 | define("--#{opt}", *[desc].compact) 1706 | end 1707 | end 1708 | 1709 | parse_in_order(argv, result.method(:[]=)) 1710 | result 1711 | end 1712 | 1713 | # 1714 | # See #getopts. 1715 | # 1716 | def self.getopts(*args) 1717 | new.getopts(*args) 1718 | end 1719 | 1720 | # 1721 | # Traverses @stack, sending each element method +id+ with +args+ and 1722 | # +block+. 1723 | # 1724 | def visit(id, *args, &block) 1725 | @stack.reverse_each do |el| 1726 | el.send(id, *args, &block) 1727 | end 1728 | nil 1729 | end 1730 | private :visit 1731 | 1732 | # 1733 | # Searches +key+ in @stack for +id+ hash and returns or yields the result. 1734 | # 1735 | def search(id, key) 1736 | block_given = block_given? 1737 | visit(:search, id, key) do |k| 1738 | return block_given ? yield(k) : k 1739 | end 1740 | end 1741 | private :search 1742 | 1743 | # 1744 | # Completes shortened long style option switch and returns pair of 1745 | # canonical switch and switch descriptor OptionParser::Switch. 1746 | # 1747 | # +typ+:: Searching table. 1748 | # +opt+:: Searching key. 1749 | # +icase+:: Search case insensitive if true. 1750 | # +pat+:: Optional pattern for completion. 1751 | # 1752 | def complete(typ, opt, icase = false, *pat) 1753 | if pat.empty? 1754 | search(typ, opt) {|sw| return [sw, opt]} # exact match or... 1755 | end 1756 | raise AmbiguousOption, catch(:ambiguous) { 1757 | visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} 1758 | raise InvalidOption, opt 1759 | } 1760 | end 1761 | private :complete 1762 | 1763 | def candidate(word) 1764 | list = [] 1765 | case word 1766 | when /\A--/ 1767 | word, arg = word.split(/=/, 2) 1768 | argpat = Completion.regexp(arg, false) if arg and !arg.empty? 1769 | long = true 1770 | when /\A-(!-)/ 1771 | short = true 1772 | when /\A-/ 1773 | long = short = true 1774 | end 1775 | pat = Completion.regexp(word, true) 1776 | visit(:each_option) do |opt| 1777 | next unless Switch === opt 1778 | opts = (long ? opt.long : []) + (short ? opt.short : []) 1779 | opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat 1780 | if /\A=/ =~ opt.arg 1781 | opts.map! {|sw| sw + "="} 1782 | if arg and CompletingHash === opt.pattern 1783 | if opts = opt.pattern.candidate(arg, false, argpat) 1784 | opts.map!(&:last) 1785 | end 1786 | end 1787 | end 1788 | list.concat(opts) 1789 | end 1790 | list 1791 | end 1792 | 1793 | # 1794 | # Loads options from file names as +filename+. Does nothing when the file 1795 | # is not present. Returns whether successfully loaded. 1796 | # 1797 | # +filename+ defaults to basename of the program without suffix in a 1798 | # directory ~/.options. 1799 | # 1800 | def load(filename = nil) 1801 | begin 1802 | filename ||= File.expand_path(File.basename($0, '.*'), '~/.options') 1803 | rescue 1804 | return false 1805 | end 1806 | begin 1807 | parse(*IO.readlines(filename).each {|s| s.chomp!}) 1808 | true 1809 | rescue Errno::ENOENT, Errno::ENOTDIR 1810 | false 1811 | end 1812 | end 1813 | 1814 | # 1815 | # Parses environment variable +env+ or its uppercase with splitting like a 1816 | # shell. 1817 | # 1818 | # +env+ defaults to the basename of the program. 1819 | # 1820 | def environment(env = File.basename($0, '.*')) 1821 | env = ENV[env] || ENV[env.upcase] or return 1822 | require 'shellwords' 1823 | parse(*Shellwords.shellwords(env)) 1824 | end 1825 | 1826 | # 1827 | # Acceptable argument classes 1828 | # 1829 | 1830 | # 1831 | # Any string and no conversion. This is fall-back. 1832 | # 1833 | accept(Object) {|s,|s or s.nil?} 1834 | 1835 | accept(NilClass) {|s,|s} 1836 | 1837 | # 1838 | # Any non-empty string, and no conversion. 1839 | # 1840 | accept(String, /.+/m) {|s,*|s} 1841 | 1842 | # 1843 | # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal 1844 | # for 0x, and decimal for others; with optional sign prefix. Converts to 1845 | # Integer. 1846 | # 1847 | decimal = '\d+(?:_\d+)*' 1848 | binary = 'b[01]+(?:_[01]+)*' 1849 | hex = 'x[\da-f]+(?:_[\da-f]+)*' 1850 | octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?" 1851 | integer = "#{octal}|#{decimal}" 1852 | 1853 | accept(Integer, %r"\A[-+]?(?:#{integer})\z"i) {|s,| 1854 | begin 1855 | Integer(s) 1856 | rescue ArgumentError 1857 | raise OptionParser::InvalidArgument, s 1858 | end if s 1859 | } 1860 | 1861 | # 1862 | # Float number format, and converts to Float. 1863 | # 1864 | float = "(?:#{decimal}(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" 1865 | floatpat = %r"\A[-+]?#{float}\z"i 1866 | accept(Float, floatpat) {|s,| s.to_f if s} 1867 | 1868 | # 1869 | # Generic numeric format, converts to Integer for integer format, Float 1870 | # for float format, and Rational for rational format. 1871 | # 1872 | real = "[-+]?(?:#{octal}|#{float})" 1873 | accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/i) {|s, d, n| 1874 | if n 1875 | Rational(d, n) 1876 | elsif s 1877 | eval(s) 1878 | end 1879 | } 1880 | 1881 | # 1882 | # Decimal integer format, to be converted to Integer. 1883 | # 1884 | DecimalInteger = /\A[-+]?#{decimal}\z/i 1885 | accept(DecimalInteger, DecimalInteger) {|s,| 1886 | begin 1887 | Integer(s) 1888 | rescue ArgumentError 1889 | raise OptionParser::InvalidArgument, s 1890 | end if s 1891 | } 1892 | 1893 | # 1894 | # Ruby/C like octal/hexadecimal/binary integer format, to be converted to 1895 | # Integer. 1896 | # 1897 | OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/i 1898 | accept(OctalInteger, OctalInteger) {|s,| 1899 | begin 1900 | Integer(s, 8) 1901 | rescue ArgumentError 1902 | raise OptionParser::InvalidArgument, s 1903 | end if s 1904 | } 1905 | 1906 | # 1907 | # Decimal integer/float number format, to be converted to Integer for 1908 | # integer format, Float for float format. 1909 | # 1910 | DecimalNumeric = floatpat # decimal integer is allowed as float also. 1911 | accept(DecimalNumeric, floatpat) {|s,| 1912 | begin 1913 | eval(s) 1914 | rescue SyntaxError 1915 | raise OptionParser::InvalidArgument, s 1916 | end if s 1917 | } 1918 | 1919 | # 1920 | # Boolean switch, which means whether it is present or not, whether it is 1921 | # absent or not with prefix no-, or it takes an argument 1922 | # yes/no/true/false/+/-. 1923 | # 1924 | yesno = CompletingHash.new 1925 | %w[- no false].each {|el| yesno[el] = false} 1926 | %w[+ yes true].each {|el| yesno[el] = true} 1927 | yesno['nil'] = false # should be nil? 1928 | accept(TrueClass, yesno) {|arg, val| val == nil or val} 1929 | # 1930 | # Similar to TrueClass, but defaults to false. 1931 | # 1932 | accept(FalseClass, yesno) {|arg, val| val != nil and val} 1933 | 1934 | # 1935 | # List of strings separated by ",". 1936 | # 1937 | accept(Array) do |s,| 1938 | if s 1939 | s = s.split(',').collect {|ss| ss unless ss.empty?} 1940 | end 1941 | s 1942 | end 1943 | 1944 | # 1945 | # Regular expression with options. 1946 | # 1947 | accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o| 1948 | f = 0 1949 | if o 1950 | f |= Regexp::IGNORECASE if /i/ =~ o 1951 | f |= Regexp::MULTILINE if /m/ =~ o 1952 | f |= Regexp::EXTENDED if /x/ =~ o 1953 | k = o.delete("imx") 1954 | k = nil if k.empty? 1955 | end 1956 | Regexp.new(s || all, f, k) 1957 | end 1958 | 1959 | # 1960 | # Exceptions 1961 | # 1962 | 1963 | # 1964 | # Base class of exceptions from OptionParser. 1965 | # 1966 | class ParseError < RuntimeError 1967 | # Reason which caused the error. 1968 | Reason = 'parse error' 1969 | 1970 | def initialize(*args) 1971 | @args = args 1972 | @reason = nil 1973 | end 1974 | 1975 | attr_reader :args 1976 | attr_writer :reason 1977 | 1978 | # 1979 | # Pushes back erred argument(s) to +argv+. 1980 | # 1981 | def recover(argv) 1982 | argv[0, 0] = @args 1983 | argv 1984 | end 1985 | 1986 | def self.filter_backtrace(array) 1987 | unless $DEBUG 1988 | array.delete_if do |item| 1989 | %r"\A#{Regexp.quote(__FILE__)}:" =~ item 1990 | end 1991 | end 1992 | array 1993 | end 1994 | 1995 | def set_backtrace(array) 1996 | super(self.class.filter_backtrace(array)) 1997 | end 1998 | 1999 | def set_option(opt, eq) 2000 | if eq 2001 | @args[0] = opt 2002 | else 2003 | @args.unshift(opt) 2004 | end 2005 | self 2006 | end 2007 | 2008 | # 2009 | # Returns error reason. Override this for I18N. 2010 | # 2011 | def reason 2012 | @reason || self.class::Reason 2013 | end 2014 | 2015 | def inspect 2016 | "#<#{self.class}: #{args.join(' ')}>" 2017 | end 2018 | 2019 | # 2020 | # Default stringizing method to emit standard error message. 2021 | # 2022 | def message 2023 | reason + ': ' + args.join(' ') 2024 | end 2025 | 2026 | alias to_s message 2027 | end 2028 | 2029 | # 2030 | # Raises when ambiguously completable string is encountered. 2031 | # 2032 | class AmbiguousOption < ParseError 2033 | const_set(:Reason, 'ambiguous option') 2034 | end 2035 | 2036 | # 2037 | # Raises when there is an argument for a switch which takes no argument. 2038 | # 2039 | class NeedlessArgument < ParseError 2040 | const_set(:Reason, 'needless argument') 2041 | end 2042 | 2043 | # 2044 | # Raises when a switch with mandatory argument has no argument. 2045 | # 2046 | class MissingArgument < ParseError 2047 | const_set(:Reason, 'missing argument') 2048 | end 2049 | 2050 | # 2051 | # Raises when switch is undefined. 2052 | # 2053 | class InvalidOption < ParseError 2054 | const_set(:Reason, 'invalid option') 2055 | end 2056 | 2057 | # 2058 | # Raises when the given argument does not match required format. 2059 | # 2060 | class InvalidArgument < ParseError 2061 | const_set(:Reason, 'invalid argument') 2062 | end 2063 | 2064 | # 2065 | # Raises when the given argument word can't be completed uniquely. 2066 | # 2067 | class AmbiguousArgument < InvalidArgument 2068 | const_set(:Reason, 'ambiguous argument') 2069 | end 2070 | 2071 | # 2072 | # Miscellaneous 2073 | # 2074 | 2075 | # 2076 | # Extends command line arguments array (ARGV) to parse itself. 2077 | # 2078 | module Arguable 2079 | 2080 | # 2081 | # Sets OptionParser object, when +opt+ is +false+ or +nil+, methods 2082 | # OptionParser::Arguable#options and OptionParser::Arguable#options= are 2083 | # undefined. Thus, there is no ways to access the OptionParser object 2084 | # via the receiver object. 2085 | # 2086 | def options=(opt) 2087 | unless @optparse = opt 2088 | class << self 2089 | undef_method(:options) 2090 | undef_method(:options=) 2091 | end 2092 | end 2093 | end 2094 | 2095 | # 2096 | # Actual OptionParser object, automatically created if nonexistent. 2097 | # 2098 | # If called with a block, yields the OptionParser object and returns the 2099 | # result of the block. If an OptionParser::ParseError exception occurs 2100 | # in the block, it is rescued, a error message printed to STDERR and 2101 | # +nil+ returned. 2102 | # 2103 | def options 2104 | @optparse ||= OptionParser.new 2105 | @optparse.default_argv = self 2106 | block_given? or return @optparse 2107 | begin 2108 | yield @optparse 2109 | rescue ParseError => e 2110 | @optparse.warn e 2111 | nil 2112 | end 2113 | end 2114 | 2115 | # 2116 | # Parses +self+ destructively in order and returns +self+ containing the 2117 | # rest arguments left unparsed. 2118 | # 2119 | def order!(&blk) options.order!(self, &blk) end 2120 | 2121 | # 2122 | # Parses +self+ destructively in permutation mode and returns +self+ 2123 | # containing the rest arguments left unparsed. 2124 | # 2125 | def permute!() options.permute!(self) end 2126 | 2127 | # 2128 | # Parses +self+ destructively and returns +self+ containing the 2129 | # rest arguments left unparsed. 2130 | # 2131 | def parse!() options.parse!(self) end 2132 | 2133 | # 2134 | # Substitution of getopts is possible as follows. Also see 2135 | # OptionParser#getopts. 2136 | # 2137 | # def getopts(*args) 2138 | # ($OPT = ARGV.getopts(*args)).each do |opt, val| 2139 | # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val" 2140 | # end 2141 | # rescue OptionParser::ParseError 2142 | # end 2143 | # 2144 | def getopts(*args) 2145 | options.getopts(self, *args) 2146 | end 2147 | 2148 | # 2149 | # Initializes instance variable. 2150 | # 2151 | def self.extend_object(obj) 2152 | super 2153 | obj.instance_variable_set :@optparse, nil 2154 | end 2155 | 2156 | def initialize(*args) 2157 | super 2158 | @optparse = nil 2159 | end 2160 | end 2161 | 2162 | # 2163 | # Acceptable argument classes. Now contains DecimalInteger, OctalInteger 2164 | # and DecimalNumeric. See Acceptable argument classes (in source code). 2165 | # 2166 | module Acceptables 2167 | const_set(:DecimalInteger, OptionParser::DecimalInteger) 2168 | const_set(:OctalInteger, OptionParser::OctalInteger) 2169 | const_set(:DecimalNumeric, OptionParser::DecimalNumeric) 2170 | end 2171 | end 2172 | 2173 | -------------------------------------------------------------------------------- /optparse.rb.diff: -------------------------------------------------------------------------------- 1 | --- cruby/optparse.rb 2016-04-20 16:30:09.000000000 -0700 2 | +++ mruby/optparse.rb 2016-05-20 10:18:32.000000000 -0700 3 | @@ -393,9 +393,9 @@ 4 | # 5 | class OptionParser 6 | # :stopdoc: 7 | - NoArgument = [NO_ARGUMENT = :NONE, nil].freeze 8 | - RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze 9 | - OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze 10 | + NoArgument = [NO_ARGUMENT = :NONE, nil] 11 | + RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true] 12 | + OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false] 13 | # :startdoc: 14 | 15 | # 16 | @@ -404,7 +404,8 @@ 17 | # 18 | module Completion 19 | def self.regexp(key, icase) 20 | - Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) 21 | + icase = nil if icase == false 22 | + Regexp.new('\A' + Regexp.quote(key).gsub(/(\w+\b)/, '\1\w*'), icase) 23 | end 24 | 25 | def self.candidate(key, icase = false, pat = nil, &block) 26 | @@ -415,7 +416,7 @@ 27 | kn = nil 28 | k === key 29 | else 30 | - kn = defined?(k.id2name) ? k.id2name : k 31 | + kn = k.respond_to?(:to_s) ? k.to_s : k 32 | pat === kn 33 | end) or next 34 | v << k if v.empty? 35 | @@ -425,14 +426,18 @@ 36 | end 37 | 38 | def candidate(key, icase = false, pat = nil) 39 | - Completion.candidate(key, icase, pat, &method(:each)) 40 | + Completion.candidate(key, icase, pat) do |&b| 41 | + each(&b) 42 | + end 43 | end 44 | 45 | public 46 | - def complete(key, icase = false, pat = nil) 47 | - candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} 48 | + def complete(key, icase = false, pat = nil, &block) 49 | + candidates = candidate(key, icase, pat) do |&b| 50 | + each(&b) 51 | + end.sort_by {|k, v, kn| kn.size} 52 | if candidates.size == 1 53 | - canon, sw, * = candidates[0] 54 | + canon, sw, = candidates[0] 55 | elsif candidates.size > 1 56 | canon, sw, cn = candidates.shift 57 | candidates.each do |k, v, kn| 58 | @@ -477,6 +482,12 @@ 59 | class Switch 60 | attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block 61 | 62 | + def self.>=(other) 63 | + return true if other == self 64 | + return false unless Class === other 65 | + return true if other.ancestors.include? self 66 | + end 67 | + 68 | # 69 | # Guesses argument style from +arg+. Returns corresponding 70 | # OptionParser::Switch class (OptionalArgument, etc.). 71 | @@ -505,9 +516,19 @@ 72 | NilClass 73 | end 74 | 75 | + # XXX: the block argument here is suspect. Originally it was: 76 | + # 77 | + # block = Proc.new 78 | + # 79 | + # Which in ruby allows you to pass in a block (which is captured via 80 | + # Proc.new in the block variable) or a pre-existing Method, Proc, or Lambda. 81 | + # 82 | + # On mruby Proc.new doesn't work that way and requires an explicit block 83 | + # so this may be broken for some uses. 84 | def initialize(pattern = nil, conv = nil, 85 | short = nil, long = nil, arg = nil, 86 | - desc = ([] if short or long), block = Proc.new) 87 | + desc = ([] if short or long), given_block = nil, &block) 88 | + block = given_block if given_block 89 | raise if Array === pattern 90 | @pattern, @conv, @short, @long, @arg, @desc, @block = 91 | pattern, conv, short, long, arg, desc, block 92 | @@ -577,7 +598,7 @@ 93 | l = left[-1].length + s.length 94 | l += arg.length if left.size == 1 && arg 95 | l < max or sopts.empty? or left << '' 96 | - left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s 97 | + left[-1] << if left[-1].empty? then ' ' * 4 else ', ' end << s 98 | end 99 | 100 | if arg 101 | @@ -672,7 +693,10 @@ 102 | raise MissingArgument if argv.empty? 103 | arg = argv.shift 104 | end 105 | - conv_arg(*parse_arg(arg, &method(:raise))) 106 | + parsed = parse_arg(arg) do |klass, message| 107 | + raise klass, message 108 | + end 109 | + conv_arg(*parsed) 110 | end 111 | end 112 | 113 | @@ -920,7 +944,6 @@ 114 | NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument} 115 | RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument} 116 | OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument} 117 | - ArgumentStyle.freeze 118 | 119 | # 120 | # Switches common used such as '--', and also provides default 121 | @@ -931,19 +954,18 @@ 122 | DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} 123 | 124 | 125 | - COMPSYS_HEADER = <<'XXX' # :nodoc: 126 | - 127 | + COMPSYS_HEADER = " 128 | typeset -A opt_args 129 | local context state line 130 | 131 | _arguments -s -S \ 132 | -XXX 133 | +" 134 | 135 | def compsys(to, name = File.basename($0)) # :nodoc: 136 | to << "#compdef #{name}\n" 137 | to << COMPSYS_HEADER 138 | visit(:compsys, {}, {}) {|o, d| 139 | - to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n] 140 | + to << %Q[ "#{o}[#{d.gsub(/([\"\[\]])/, '\\\\\1')}]" \\\n] 141 | } 142 | to << " '*:file:_files' && return 0\n" 143 | end 144 | @@ -1056,7 +1078,8 @@ 145 | @banner = banner 146 | @summary_width = width 147 | @summary_indent = indent 148 | - @default_argv = ARGV 149 | + @default_argv = [] 150 | + @record_separator = "\n" 151 | add_officious 152 | yield self if block_given? 153 | end 154 | @@ -1130,6 +1153,9 @@ 155 | # Strings to be parsed in default. 156 | attr_accessor :default_argv 157 | 158 | + # Default record separator ($/ in CRuby) 159 | + attr_accessor :record_separator 160 | + 161 | # 162 | # Heading banner preceding summary. 163 | # 164 | @@ -1236,7 +1262,7 @@ 165 | # +indent+:: Indentation, defaults to @summary_indent. 166 | # 167 | def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk) 168 | - blk ||= proc {|l| to << (l.index($/, -1) ? l : l + $/)} 169 | + blk ||= proc {|l| to << (l.index(@record_separator, -1) ? l : l + @record_separator)} 170 | visit(:summarize, {}, {}, width, max, indent, &blk) 171 | to 172 | end 173 | @@ -1351,7 +1377,9 @@ 174 | if (!(String === o || Symbol === o)) and o.respond_to?(:match) 175 | pattern = notwice(o, pattern, 'pattern') 176 | if pattern.respond_to?(:convert) 177 | - conv = pattern.method(:convert).to_proc 178 | + conv = Proc.new do |*a| 179 | + pattern.convert(*a) 180 | + end 181 | else 182 | conv = SPLAT_PROC 183 | end 184 | @@ -1360,14 +1388,16 @@ 185 | 186 | # anything others 187 | case o 188 | - when Proc, Method 189 | + when Proc 190 | block = notwice(o, block, 'block') 191 | when Array, Hash 192 | case pattern 193 | when CompletingHash 194 | when nil 195 | pattern = CompletingHash.new 196 | - conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert) 197 | + conv = Proc.new do |*a| 198 | + pattern.convert(*a) 199 | + end if pattern.respond_to?(:convert) 200 | else 201 | raise ArgumentError, "argument pattern given twice" 202 | end 203 | @@ -1508,18 +1538,17 @@ 204 | # 205 | # Returns the rest of +argv+ left unparsed. 206 | # 207 | - def order(*argv, into: nil, &nonopt) 208 | + def order(*argv, &block) 209 | argv = argv[0].dup if argv.size == 1 and Array === argv[0] 210 | - order!(argv, into: into, &nonopt) 211 | + order!(argv, &block) 212 | end 213 | 214 | # 215 | # Same as #order, but removes switches destructively. 216 | # Non-option arguments remain in +argv+. 217 | # 218 | - def order!(argv = default_argv, into: nil, &nonopt) 219 | - setter = ->(name, val) {into[name.to_sym] = val} if into 220 | - parse_in_order(argv, setter, &nonopt) 221 | + def order!(argv = default_argv, &nonopt) 222 | + parse_in_order(argv, &nonopt) 223 | end 224 | 225 | def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: 226 | @@ -1533,15 +1562,15 @@ 227 | opt, rest = $1, $2 228 | begin 229 | sw, = complete(:long, opt, true) 230 | - rescue ParseError 231 | - raise $!.set_option(arg, true) 232 | + rescue ParseError => e 233 | + raise e.set_option(arg, true) 234 | end 235 | begin 236 | opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} 237 | val = cb.call(val) if cb 238 | setter.call(sw.switch_name, val) if setter 239 | - rescue ParseError 240 | - raise $!.set_option(arg, rest) 241 | + rescue ParseError => e 242 | + raise e.set_option(arg, rest) 243 | end 244 | 245 | # short option 246 | @@ -1562,8 +1591,8 @@ 247 | eq ||= !rest 248 | end 249 | end 250 | - rescue ParseError 251 | - raise $!.set_option(arg, true) 252 | + rescue ParseError => e 253 | + raise e.set_option(arg, true) 254 | end 255 | begin 256 | opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq} 257 | @@ -1571,8 +1600,8 @@ 258 | argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') 259 | val = cb.call(val) if cb 260 | setter.call(sw.switch_name, val) if setter 261 | - rescue ParseError 262 | - raise $!.set_option(arg, arg.length > 2) 263 | + rescue ParseError => e 264 | + raise e.set_option(arg, arg.length > 2) 265 | end 266 | 267 | # non-option argument 268 | @@ -1600,18 +1629,20 @@ 269 | # Parses command line arguments +argv+ in permutation mode and returns 270 | # list of non-option arguments. 271 | # 272 | - def permute(*argv, into: nil) 273 | + def permute(*argv) 274 | argv = argv[0].dup if argv.size == 1 and Array === argv[0] 275 | - permute!(argv, into: into) 276 | + permute!(argv) 277 | end 278 | 279 | # 280 | # Same as #permute, but removes switches destructively. 281 | # Non-option arguments remain in +argv+. 282 | # 283 | - def permute!(argv = default_argv, into: nil) 284 | + def permute!(argv = default_argv) 285 | nonopts = [] 286 | - order!(argv, into: into, &nonopts.method(:<<)) 287 | + order!(argv) do |arg| 288 | + nonopts << arg 289 | + end 290 | argv[0, 0] = nonopts 291 | argv 292 | end 293 | @@ -1620,20 +1651,20 @@ 294 | # Parses command line arguments +argv+ in order when environment variable 295 | # POSIXLY_CORRECT is set, and in permutation mode otherwise. 296 | # 297 | - def parse(*argv, into: nil) 298 | + def parse(*argv) 299 | argv = argv[0].dup if argv.size == 1 and Array === argv[0] 300 | - parse!(argv, into: into) 301 | + parse!(argv) 302 | end 303 | 304 | # 305 | # Same as #parse, but removes switches destructively. 306 | # Non-option arguments remain in +argv+. 307 | # 308 | - def parse!(argv = default_argv, into: nil) 309 | + def parse!(argv = default_argv) 310 | if ENV.include?('POSIXLY_CORRECT') 311 | - order!(argv, into: into) 312 | + order!(argv) 313 | else 314 | - permute!(argv, into: into) 315 | + permute!(argv) 316 | end 317 | end 318 | 319 | @@ -1819,7 +1850,7 @@ 320 | octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?" 321 | integer = "#{octal}|#{decimal}" 322 | 323 | - accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,| 324 | + accept(Integer, %r"\A[-+]?(?:#{integer})\z"i) {|s,| 325 | begin 326 | Integer(s) 327 | rescue ArgumentError 328 | @@ -1831,7 +1862,7 @@ 329 | # Float number format, and converts to Float. 330 | # 331 | float = "(?:#{decimal}(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?" 332 | - floatpat = %r"\A[-+]?#{float}\z"io 333 | + floatpat = %r"\A[-+]?#{float}\z"i 334 | accept(Float, floatpat) {|s,| s.to_f if s} 335 | 336 | # 337 | @@ -1839,7 +1870,7 @@ 338 | # for float format, and Rational for rational format. 339 | # 340 | real = "[-+]?(?:#{octal}|#{float})" 341 | - accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, n| 342 | + accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/i) {|s, d, n| 343 | if n 344 | Rational(d, n) 345 | elsif s 346 | @@ -1850,7 +1881,7 @@ 347 | # 348 | # Decimal integer format, to be converted to Integer. 349 | # 350 | - DecimalInteger = /\A[-+]?#{decimal}\z/io 351 | + DecimalInteger = /\A[-+]?#{decimal}\z/i 352 | accept(DecimalInteger, DecimalInteger) {|s,| 353 | begin 354 | Integer(s) 355 | @@ -1863,7 +1894,7 @@ 356 | # Ruby/C like octal/hexadecimal/binary integer format, to be converted to 357 | # Integer. 358 | # 359 | - OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io 360 | + OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/i 361 | accept(OctalInteger, OctalInteger) {|s,| 362 | begin 363 | Integer(s, 8) 364 | @@ -1903,7 +1934,7 @@ 365 | # 366 | # List of strings separated by ",". 367 | # 368 | - accept(Array) do |s, | 369 | + accept(Array) do |s,| 370 | if s 371 | s = s.split(',').collect {|ss| ss unless ss.empty?} 372 | end 373 | @@ -1934,7 +1965,7 @@ 374 | # 375 | class ParseError < RuntimeError 376 | # Reason which caused the error. 377 | - Reason = 'parse error'.freeze 378 | + Reason = 'parse error' 379 | 380 | def initialize(*args) 381 | @args = args 382 | @@ -1954,7 +1985,9 @@ 383 | 384 | def self.filter_backtrace(array) 385 | unless $DEBUG 386 | - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) 387 | + array.delete_if do |item| 388 | + %r"\A#{Regexp.quote(__FILE__)}:" =~ item 389 | + end 390 | end 391 | array 392 | end 393 | @@ -1997,42 +2030,42 @@ 394 | # Raises when ambiguously completable string is encountered. 395 | # 396 | class AmbiguousOption < ParseError 397 | - const_set(:Reason, 'ambiguous option'.freeze) 398 | + const_set(:Reason, 'ambiguous option') 399 | end 400 | 401 | # 402 | # Raises when there is an argument for a switch which takes no argument. 403 | # 404 | class NeedlessArgument < ParseError 405 | - const_set(:Reason, 'needless argument'.freeze) 406 | + const_set(:Reason, 'needless argument') 407 | end 408 | 409 | # 410 | # Raises when a switch with mandatory argument has no argument. 411 | # 412 | class MissingArgument < ParseError 413 | - const_set(:Reason, 'missing argument'.freeze) 414 | + const_set(:Reason, 'missing argument') 415 | end 416 | 417 | # 418 | # Raises when switch is undefined. 419 | # 420 | class InvalidOption < ParseError 421 | - const_set(:Reason, 'invalid option'.freeze) 422 | + const_set(:Reason, 'invalid option') 423 | end 424 | 425 | # 426 | # Raises when the given argument does not match required format. 427 | # 428 | class InvalidArgument < ParseError 429 | - const_set(:Reason, 'invalid argument'.freeze) 430 | + const_set(:Reason, 'invalid argument') 431 | end 432 | 433 | # 434 | # Raises when the given argument word can't be completed uniquely. 435 | # 436 | class AmbiguousArgument < InvalidArgument 437 | - const_set(:Reason, 'ambiguous argument'.freeze) 438 | + const_set(:Reason, 'ambiguous argument') 439 | end 440 | 441 | # 442 | @@ -2073,8 +2106,8 @@ 443 | block_given? or return @optparse 444 | begin 445 | yield @optparse 446 | - rescue ParseError 447 | - @optparse.warn $! 448 | + rescue ParseError => e 449 | + @optparse.warn e 450 | nil 451 | end 452 | end 453 | @@ -2117,8 +2150,9 @@ 454 | # 455 | def self.extend_object(obj) 456 | super 457 | - obj.instance_eval {@optparse = nil} 458 | + obj.instance_variable_set :@optparse, nil 459 | end 460 | + 461 | def initialize(*args) 462 | super 463 | @optparse = nil 464 | @@ -2136,7 +2170,3 @@ 465 | end 466 | end 467 | 468 | -# ARGV is arguable by OptionParser 469 | -ARGV.extend(OptionParser::Arguable) 470 | - 471 | -OptParse = OptionParser 472 | -------------------------------------------------------------------------------- /test/test_optparse.rb: -------------------------------------------------------------------------------- 1 | class TestOptparse < MTest::Unit::TestCase 2 | def setup 3 | @configuration = {} 4 | 5 | @environments = { 6 | 'production' => 'https://prod.example', 7 | 'staging' => 'https://stg.example', 8 | 'development' => 'http://dev.example' 9 | } 10 | 11 | @o = OptionParser.new 12 | 13 | @o.on "--[no-]cleanup", 14 | "Remove WAF at end of run." do |cleanup| 15 | @configuration[:CLEANUP] = cleanup 16 | end 17 | 18 | @o.on_head "--environment=ENVIRONMENT", @environments.keys, 19 | "Set the environment you want to work with." do |environment| 20 | @configuration[:ENVIRONMENT] = environment 21 | end 22 | 23 | @o.on "--array=ITEMS", Array do |array| 24 | @configuration[:ARRAY] = array 25 | end 26 | 27 | @o.on "--integer=INTEGER", Integer do |integer| 28 | @configuration[:INTEGER] = integer 29 | end 30 | 31 | @o.banner = "Usage: mruby #{__FILE__} [OPTIONS]" 32 | end 33 | 34 | def test_parse 35 | @o.parse '--cleanup' 36 | 37 | assert @configuration[:CLEANUP] 38 | end 39 | 40 | def test_parse_argument_completion 41 | @o.parse '--clean' 42 | 43 | assert @configuration[:CLEANUP] 44 | end 45 | 46 | def test_parse_argument_conversion 47 | @o.parse '--array=1,2,3' 48 | 49 | assert_equal %w[1 2 3], @configuration[:ARRAY] 50 | end 51 | 52 | def test_parse_argument_validation 53 | @o.parse '--integer=1' 54 | 55 | assert_equal 1, @configuration[:INTEGER] 56 | end 57 | 58 | def test_parse_argument_validation_invalid 59 | assert_raise OptionParser::InvalidArgument do 60 | @o.parse '--integer=string' 61 | end 62 | end 63 | 64 | def test_parse_value_completion 65 | @o.parse '--environment', 'prod' 66 | 67 | assert_equal 'production', @configuration[:ENVIRONMENT] 68 | end 69 | 70 | def test_record_separator 71 | assert_equal "\n", @o.record_separator 72 | 73 | @o.record_separator = "\r\n" 74 | 75 | assert_equal "\r\n", @o.record_separator 76 | end 77 | 78 | def test_to_s 79 | help = @o.to_s 80 | 81 | assert_match 'Usage: ', help 82 | assert_match '--environment', help 83 | assert_match 'Set the environment', help 84 | assert_match '--[no-]cleanup', help 85 | end 86 | end 87 | 88 | MTest::Unit.new.run if __FILE__ == $0 89 | --------------------------------------------------------------------------------