├── Gemfile ├── bin ├── setup └── console ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── sample └── getoptlong │ ├── aliases.rb │ ├── simple.rb │ ├── abbrev.rb │ ├── types.rb │ ├── argv.rb │ ├── permute.rb │ ├── each.rb │ ├── require_order.rb │ ├── return_in_order.rb │ └── fibonacci.rb ├── Rakefile ├── getoptlong.gemspec ├── BSDL ├── COPYING ├── README.md ├── test └── test_getoptlong.rb └── lib └── getoptlong.rb /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rake" 4 | gem "test-unit" 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "getoptlong" 5 | 6 | require "irb" 7 | IRB.start(__FILE__) 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /sample/getoptlong/aliases.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', '-x', '--aaa', '-a', '-p', GetoptLong::NO_ARGUMENT] 5 | ) 6 | options.each do |option, argument| 7 | p [option, argument] 8 | end 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << 'test' 6 | t.libs << 'lib' 7 | t.test_files = FileList['test/test_*.rb'] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /sample/getoptlong/simple.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--number', '-n', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--verbose', '-v', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--help', '-h', GetoptLong::NO_ARGUMENT] 7 | ) 8 | -------------------------------------------------------------------------------- /sample/getoptlong/abbrev.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', GetoptLong::NO_ARGUMENT], 5 | ['--xyz', GetoptLong::NO_ARGUMENT] 6 | ) 7 | options.each do |option, argument| 8 | p [option, argument] 9 | end 10 | -------------------------------------------------------------------------------- /sample/getoptlong/types.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--yyy', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--zzz', GetoptLong::NO_ARGUMENT] 7 | ) 8 | options.each do |option, argument| 9 | p [option, argument] 10 | end 11 | -------------------------------------------------------------------------------- /sample/getoptlong/argv.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--yyy', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--zzz', GetoptLong::NO_ARGUMENT] 7 | ) 8 | puts "Original ARGV: #{ARGV}" 9 | options.each do |option, argument| 10 | p [option, argument] 11 | end 12 | puts "Remaining ARGV: #{ARGV}" 13 | -------------------------------------------------------------------------------- /sample/getoptlong/permute.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--yyy', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--zzz', GetoptLong::NO_ARGUMENT] 7 | ) 8 | puts "Original ARGV: #{ARGV}" 9 | options.each do |option, argument| 10 | p [option, argument] 11 | end 12 | puts "Remaining ARGV: #{ARGV}" 13 | -------------------------------------------------------------------------------- /sample/getoptlong/each.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', '-x', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--yyy', '-y', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--zzz', '-z',GetoptLong::NO_ARGUMENT] 7 | ) 8 | puts "Original ARGV: #{ARGV}" 9 | options.each do |option, argument| 10 | p [option, argument] 11 | end 12 | puts "Remaining ARGV: #{ARGV}" 13 | -------------------------------------------------------------------------------- /sample/getoptlong/require_order.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--yyy', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--zzz', GetoptLong::NO_ARGUMENT] 7 | ) 8 | options.ordering = GetoptLong::REQUIRE_ORDER 9 | puts "Original ARGV: #{ARGV}" 10 | options.each do |option, argument| 11 | p [option, argument] 12 | end 13 | puts "Remaining ARGV: #{ARGV}" 14 | -------------------------------------------------------------------------------- /sample/getoptlong/return_in_order.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--xxx', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--yyy', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--zzz', GetoptLong::NO_ARGUMENT] 7 | ) 8 | options.ordering = GetoptLong::RETURN_IN_ORDER 9 | puts "Original ARGV: #{ARGV}" 10 | options.each do |option, argument| 11 | p [option, argument] 12 | end 13 | puts "Remaining ARGV: #{ARGV}" 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | min_version: 2.6 10 | 11 | test: 12 | needs: ruby-versions 13 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 14 | strategy: 15 | matrix: 16 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 17 | os: [ ubuntu-latest, macos-latest, windows-latest ] 18 | exclude: 19 | - { os: windows-latest, ruby: truffleruby-head } 20 | - { os: windows-latest, ruby: truffleruby } 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v6 24 | - name: Set up Ruby 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | bundler-cache: true 29 | - name: Run test 30 | run: bundle exec rake test 31 | -------------------------------------------------------------------------------- /getoptlong.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | name = File.basename(__FILE__, ".gemspec") 4 | version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| 5 | break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| 6 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 7 | end rescue nil 8 | end 9 | 10 | Gem::Specification.new do |spec| 11 | spec.name = name 12 | spec.version = version 13 | spec.authors = ["Yukihiro Matsumoto"] 14 | spec.email = ["matz@ruby-lang.org"] 15 | 16 | spec.summary = %q{GetoptLong for Ruby} 17 | spec.description = spec.summary 18 | spec.homepage = "https://github.com/ruby/getoptlong" 19 | spec.licenses = ["Ruby", "BSD-2-Clause"] 20 | 21 | spec.metadata["homepage_uri"] = spec.homepage 22 | spec.metadata["source_code_uri"] = spec.homepage 23 | 24 | # Specify which files should be added to the gem when it is released. 25 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 26 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 27 | `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 28 | end 29 | spec.require_paths = ["lib"] 30 | end 31 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /sample/getoptlong/fibonacci.rb: -------------------------------------------------------------------------------- 1 | require 'getoptlong' 2 | 3 | options = GetoptLong.new( 4 | ['--number', '-n', GetoptLong::REQUIRED_ARGUMENT], 5 | ['--verbose', '-v', GetoptLong::OPTIONAL_ARGUMENT], 6 | ['--help', '-h', GetoptLong::NO_ARGUMENT] 7 | ) 8 | 9 | def help(status = 0) 10 | puts <<~HELP 11 | Usage: 12 | 13 | -n n, --number n: 14 | Compute Fibonacci number for n. 15 | -v [boolean], --verbose [boolean]: 16 | Show intermediate results; default is 'false'. 17 | -h, --help: 18 | Show this help. 19 | HELP 20 | exit(status) 21 | end 22 | 23 | def print_fibonacci (number) 24 | return 0 if number == 0 25 | return 1 if number == 1 or number == 2 26 | i = 0 27 | j = 1 28 | (2..number).each do 29 | k = i + j 30 | i = j 31 | j = k 32 | puts j if @verbose 33 | end 34 | puts j unless @verbose 35 | end 36 | 37 | options.each do |option, argument| 38 | case option 39 | when '--number' 40 | @number = argument.to_i 41 | when '--verbose' 42 | @verbose = if argument.empty? 43 | true 44 | elsif argument.match(/true/i) 45 | true 46 | elsif argument.match(/false/i) 47 | false 48 | else 49 | puts '--verbose argument must be true or false' 50 | help(255) 51 | end 52 | when '--help' 53 | help 54 | end 55 | end 56 | 57 | unless @number 58 | puts 'Option --number is required.' 59 | help(255) 60 | end 61 | 62 | print_fibonacci(@number) 63 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getoptlong 2 | 3 | The GetoptLong class allows you to parse command line options similarly to 4 | the GNU getopt_long() C library call. Note, however, that GetoptLong is a 5 | pure Ruby implementation. 6 | 7 | GetoptLong allows for POSIX-style options like --file as well 8 | as single letter options like -f 9 | 10 | The empty option -- (two minus symbols) is used to end option 11 | processing. This can be particularly important if options have optional 12 | arguments. 13 | 14 | ## Installation 15 | 16 | Add this line to your application's Gemfile: 17 | 18 | ```ruby 19 | gem 'getoptlong' 20 | ``` 21 | 22 | And then execute: 23 | 24 | $ bundle 25 | 26 | Or install it yourself as: 27 | 28 | $ gem install getoptlong 29 | 30 | ## Usage 31 | 32 | ```ruby 33 | require 'getoptlong' 34 | 35 | opts = GetoptLong.new( 36 | [ '--help', '-h', GetoptLong::NO_ARGUMENT ], 37 | [ '--repeat', '-n', GetoptLong::REQUIRED_ARGUMENT ], 38 | [ '--name', GetoptLong::OPTIONAL_ARGUMENT ] 39 | ) 40 | 41 | dir = nil 42 | name = nil 43 | repetitions = 1 44 | opts.each do |opt, arg| 45 | case opt 46 | when '--help' 47 | puts <<-EOF 48 | hello [OPTION] ... DIR 49 | -h, --help: 50 | show help 51 | --repeat x, -n x: 52 | repeat x times 53 | --name [name]: 54 | greet user by name, if name not supplied default is John 55 | DIR: The directory in which to issue the greeting. 56 | EOF 57 | when '--repeat' 58 | repetitions = arg.to_i 59 | when '--name' 60 | if arg == '' 61 | name = 'John' 62 | else 63 | name = arg 64 | end 65 | end 66 | end 67 | 68 | if ARGV.length != 1 69 | puts "Missing dir argument (try --help)" 70 | exit 0 71 | end 72 | 73 | dir = ARGV.shift 74 | 75 | Dir.chdir(dir) 76 | for i in (1..repetitions) 77 | print "Hello" 78 | if name 79 | print ", #{name}" 80 | end 81 | puts 82 | end 83 | ``` 84 | 85 | Example command line: 86 | 87 | ``` 88 | hello -n 6 --name -- /tmp 89 | ``` 90 | 91 | ## Development 92 | 93 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 94 | 95 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 96 | 97 | ## Contributing 98 | 99 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/getoptlong. 100 | -------------------------------------------------------------------------------- /test/test_getoptlong.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'getoptlong' 3 | 4 | class TestGetoptLong < Test::Unit::TestCase 5 | 6 | def verify(test_argv, expected_remaining_argv, expected_options) 7 | # Save ARGV and replace it with a test ARGV. 8 | argv_saved = ARGV.dup 9 | ARGV.replace(test_argv) 10 | # Define options. 11 | opts = GetoptLong.new( 12 | ['--xxx', '-x', '--aaa', '-a', GetoptLong::REQUIRED_ARGUMENT], 13 | ['--yyy', '-y', '--bbb', '-b', GetoptLong::OPTIONAL_ARGUMENT], 14 | ['--zzz', '-z', '--ccc', '-c', GetoptLong::NO_ARGUMENT] 15 | ) 16 | opts.quiet = true 17 | # Gather options. 18 | actual_options = [] 19 | opts.each do |opt, arg| 20 | actual_options << "#{opt}: #{arg}" 21 | end 22 | # Save remaining test ARGV and restore original ARGV. 23 | actual_remaining_argv = ARGV.dup 24 | ARGV.replace(argv_saved) 25 | # Assert. 26 | assert_equal(expected_remaining_argv, actual_remaining_argv, 'ARGV') 27 | assert_equal(expected_options, actual_options, 'Options') 28 | end 29 | 30 | def test_no_options 31 | expected_options = [] 32 | expected_argv = %w[foo bar] 33 | argv = %w[foo bar] 34 | verify(argv, expected_argv, expected_options) 35 | end 36 | 37 | def test_required_argument 38 | expected_options = [ 39 | '--xxx: arg' 40 | ] 41 | expected_argv = %w[foo bar] 42 | options = %w[--xxx --xx --x -x --aaa --aa --a -a] 43 | options.each do |option| 44 | argv = ['foo', option, 'arg', 'bar'] 45 | verify(argv, expected_argv, expected_options) 46 | end 47 | end 48 | 49 | def test_required_argument_missing 50 | options = %w[--xxx --xx --x -x --aaa --aa --a -a] 51 | options.each do |option| 52 | argv = [option] 53 | e = assert_raise(GetoptLong::MissingArgument) do 54 | verify(argv, [], []) 55 | end 56 | assert_match('requires an argument', e.message) 57 | end 58 | end 59 | 60 | def test_optional_argument 61 | expected_options = [ 62 | '--yyy: arg' 63 | ] 64 | expected_argv = %w[foo bar] 65 | options = %w[--yyy --y --y -y --bbb --bb --b -b] 66 | options.each do |option| 67 | argv = ['foo', 'bar', option, 'arg'] 68 | verify(argv, expected_argv, expected_options) 69 | end 70 | end 71 | 72 | def test_optional_argument_missing 73 | expected_options = [ 74 | '--yyy: ' 75 | ] 76 | expected_argv = %w[foo bar] 77 | options = %w[--yyy --y --y -y --bbb --bb --b -b] 78 | options.each do |option| 79 | argv = ['foo', 'bar', option] 80 | verify(argv, expected_argv, expected_options) 81 | end 82 | end 83 | 84 | def test_no_argument 85 | expected_options = [ 86 | '--zzz: ' 87 | ] 88 | expected_argv = %w[foo bar] 89 | options = %w[--zzz --zz --z -z --ccc --cc --c -c] 90 | options.each do |option| 91 | argv = ['foo', option, 'bar'] 92 | verify(argv, expected_argv, expected_options) 93 | end 94 | end 95 | 96 | def test_new_with_empty_array 97 | e = assert_raise(ArgumentError) do 98 | GetoptLong.new([]) 99 | end 100 | assert_match(/no argument-flag/, e.message) 101 | end 102 | 103 | def test_new_with_bad_array 104 | e = assert_raise(ArgumentError) do 105 | GetoptLong.new('foo') 106 | end 107 | assert_match(/option list contains non-Array argument/, e.message) 108 | end 109 | 110 | def test_new_with_empty_subarray 111 | e = assert_raise(ArgumentError) do 112 | GetoptLong.new([[]]) 113 | end 114 | assert_match(/no argument-flag/, e.message) 115 | end 116 | 117 | def test_new_with_bad_subarray 118 | e = assert_raise(ArgumentError) do 119 | GetoptLong.new([1]) 120 | end 121 | assert_match(/no option name/, e.message) 122 | end 123 | 124 | def test_new_with_invalid_option 125 | invalid_options = %w[verbose -verbose -- +] 126 | invalid_options.each do |invalid_option| 127 | e = assert_raise(ArgumentError, invalid_option.to_s) do 128 | arguments = [ 129 | [invalid_option, '-v', GetoptLong::NO_ARGUMENT] 130 | ] 131 | GetoptLong.new(*arguments) 132 | end 133 | assert_match(/invalid option/, e.message) 134 | end 135 | end 136 | 137 | def test_new_with_invalid_alias 138 | invalid_aliases = %w[v - -- +] 139 | invalid_aliases.each do |invalid_alias| 140 | e = assert_raise(ArgumentError, invalid_alias.to_s) do 141 | arguments = [ 142 | ['--verbose', invalid_alias, GetoptLong::NO_ARGUMENT] 143 | ] 144 | GetoptLong.new(*arguments) 145 | end 146 | assert_match(/invalid option/, e.message) 147 | end 148 | end 149 | 150 | def test_new_with_invalid_flag 151 | invalid_flags = ['foo'] 152 | invalid_flags.each do |invalid_flag| 153 | e = assert_raise(ArgumentError, invalid_flag.to_s) do 154 | arguments = [ 155 | ['--verbose', '-v', invalid_flag] 156 | ] 157 | GetoptLong.new(*arguments) 158 | end 159 | assert_match(/no argument-flag/, e.message) 160 | end 161 | end 162 | 163 | end 164 | -------------------------------------------------------------------------------- /lib/getoptlong.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # GetoptLong for Ruby 4 | # 5 | # Copyright (C) 1998, 1999, 2000 Motoyuki Kasahara. 6 | # 7 | # You may redistribute and/or modify this library under the same license 8 | # terms as Ruby. 9 | 10 | # \Class \GetoptLong provides parsing both for options 11 | # and for regular arguments. 12 | # 13 | # Using \GetoptLong, you can define options for your program. 14 | # The program can then capture and respond to whatever options 15 | # are included in the command that executes the program. 16 | # 17 | # A simple example: file simple.rb: 18 | # 19 | # :include: ../sample/getoptlong/simple.rb 20 | # 21 | # If you are somewhat familiar with options, 22 | # you may want to skip to this 23 | # {full example}[#class-GetoptLong-label-Full+Example]. 24 | # 25 | # == Options 26 | # 27 | # A \GetoptLong option has: 28 | # 29 | # - A string option name. 30 | # - Zero or more string aliases for the name. 31 | # - An option type. 32 | # 33 | # Options may be defined by calling singleton method GetoptLong.new, 34 | # which returns a new \GetoptLong object. 35 | # Options may then be processed by calling other methods 36 | # such as GetoptLong#each. 37 | # 38 | # === Option Name and Aliases 39 | # 40 | # In the array that defines an option, 41 | # the first element is the string option name. 42 | # Often the name takes the 'long' form, beginning with two hyphens. 43 | # 44 | # The option name may have any number of aliases, 45 | # which are defined by additional string elements. 46 | # 47 | # The name and each alias must be of one of two forms: 48 | # 49 | # - Two hyphens, followed by one or more letters. 50 | # - One hyphen, followed by a single letter. 51 | # 52 | # File aliases.rb: 53 | # 54 | # :include: ../sample/getoptlong/aliases.rb 55 | # 56 | # An option may be cited by its name, 57 | # or by any of its aliases; 58 | # the parsed option always reports the name, not an alias: 59 | # 60 | # $ ruby aliases.rb -a -p --xxx --aaa -x 61 | # 62 | # Output: 63 | # 64 | # ["--xxx", ""] 65 | # ["--xxx", ""] 66 | # ["--xxx", ""] 67 | # ["--xxx", ""] 68 | # ["--xxx", ""] 69 | # 70 | # 71 | # An option may also be cited by an abbreviation of its name or any alias, 72 | # as long as that abbreviation is unique among the options. 73 | # 74 | # File abbrev.rb: 75 | # 76 | # :include: ../sample/getoptlong/abbrev.rb 77 | # 78 | # Command line: 79 | # 80 | # $ ruby abbrev.rb --xxx --xx --xyz --xy 81 | # 82 | # Output: 83 | # 84 | # ["--xxx", ""] 85 | # ["--xxx", ""] 86 | # ["--xyz", ""] 87 | # ["--xyz", ""] 88 | # 89 | # This command line raises GetoptLong::AmbiguousOption: 90 | # 91 | # $ ruby abbrev.rb --x 92 | # 93 | # === Repetition 94 | # 95 | # An option may be cited more than once: 96 | # 97 | # $ ruby abbrev.rb --xxx --xyz --xxx --xyz 98 | # 99 | # Output: 100 | # 101 | # ["--xxx", ""] 102 | # ["--xyz", ""] 103 | # ["--xxx", ""] 104 | # ["--xyz", ""] 105 | # 106 | # === Treating Remaining Options as Arguments 107 | # 108 | # A option-like token that appears 109 | # anywhere after the token -- is treated as an ordinary argument, 110 | # and is not processed as an option: 111 | # 112 | # $ ruby abbrev.rb --xxx --xyz -- --xxx --xyz 113 | # 114 | # Output: 115 | # 116 | # ["--xxx", ""] 117 | # ["--xyz", ""] 118 | # 119 | # === Option Types 120 | # 121 | # Each option definition includes an option type, 122 | # which controls whether the option takes an argument. 123 | # 124 | # File types.rb: 125 | # 126 | # :include: ../sample/getoptlong/types.rb 127 | # 128 | # Note that an option type has to do with the option argument 129 | # (whether it is required, optional, or forbidden), 130 | # not with whether the option itself is required. 131 | # 132 | # ==== Option with Required Argument 133 | # 134 | # An option of type GetoptLong::REQUIRED_ARGUMENT 135 | # must be followed by an argument, which is associated with that option: 136 | # 137 | # $ ruby types.rb --xxx foo 138 | # 139 | # Output: 140 | # 141 | # ["--xxx", "foo"] 142 | # 143 | # If the option is not last, its argument is whatever follows it 144 | # (even if the argument looks like another option): 145 | # 146 | # $ ruby types.rb --xxx --yyy 147 | # 148 | # Output: 149 | # 150 | # ["--xxx", "--yyy"] 151 | # 152 | # If the option is last, an exception is raised: 153 | # 154 | # $ ruby types.rb 155 | # # Raises GetoptLong::MissingArgument 156 | # 157 | # ==== Option with Optional Argument 158 | # 159 | # An option of type GetoptLong::OPTIONAL_ARGUMENT 160 | # may be followed by an argument, which if given is associated with that option. 161 | # 162 | # If the option is last, it does not have an argument: 163 | # 164 | # $ ruby types.rb --yyy 165 | # 166 | # Output: 167 | # 168 | # ["--yyy", ""] 169 | # 170 | # If the option is followed by another option, it does not have an argument: 171 | # 172 | # $ ruby types.rb --yyy --zzz 173 | # 174 | # Output: 175 | # 176 | # ["--yyy", ""] 177 | # ["--zzz", ""] 178 | # 179 | # Otherwise the option is followed by its argument, which is associated 180 | # with that option: 181 | # 182 | # $ ruby types.rb --yyy foo 183 | # 184 | # Output: 185 | # 186 | # ["--yyy", "foo"] 187 | # 188 | # ==== Option with No Argument 189 | # 190 | # An option of type GetoptLong::NO_ARGUMENT takes no argument: 191 | # 192 | # ruby types.rb --zzz foo 193 | # 194 | # Output: 195 | # 196 | # ["--zzz", ""] 197 | # 198 | # === ARGV 199 | # 200 | # You can process options either with method #each and a block, 201 | # or with method #get. 202 | # 203 | # During processing, each found option is removed, along with its argument 204 | # if there is one. 205 | # After processing, each remaining element was neither an option 206 | # nor the argument for an option. 207 | # 208 | # File argv.rb: 209 | # 210 | # :include: ../sample/getoptlong/argv.rb 211 | # 212 | # Command line: 213 | # 214 | # $ ruby argv.rb --xxx Foo --yyy Bar Baz --zzz Bat Bam 215 | # 216 | # Output: 217 | # 218 | # Original ARGV: ["--xxx", "Foo", "--yyy", "Bar", "Baz", "--zzz", "Bat", "Bam"] 219 | # ["--xxx", "Foo"] 220 | # ["--yyy", "Bar"] 221 | # ["--zzz", ""] 222 | # Remaining ARGV: ["Baz", "Bat", "Bam"] 223 | # 224 | # === Ordering 225 | # 226 | # There are three settings that control the way the options 227 | # are interpreted: 228 | # 229 | # - +PERMUTE+. 230 | # - +REQUIRE_ORDER+. 231 | # - +RETURN_IN_ORDER+. 232 | # 233 | # The initial setting for a new \GetoptLong object is +REQUIRE_ORDER+ 234 | # if environment variable +POSIXLY_CORRECT+ is defined, +PERMUTE+ otherwise. 235 | # 236 | # ==== PERMUTE Ordering 237 | # 238 | # In the +PERMUTE+ ordering, options and other, non-option, 239 | # arguments may appear in any order and any mixture. 240 | # 241 | # File permute.rb: 242 | # 243 | # :include: ../sample/getoptlong/permute.rb 244 | # 245 | # Command line: 246 | # 247 | # $ ruby permute.rb Foo --zzz Bar --xxx Baz --yyy Bat Bam --xxx Bag Bah 248 | # 249 | # Output: 250 | # 251 | # Original ARGV: ["Foo", "--zzz", "Bar", "--xxx", "Baz", "--yyy", "Bat", "Bam", "--xxx", "Bag", "Bah"] 252 | # ["--zzz", ""] 253 | # ["--xxx", "Baz"] 254 | # ["--yyy", "Bat"] 255 | # ["--xxx", "Bag"] 256 | # Remaining ARGV: ["Foo", "Bar", "Bam", "Bah"] 257 | # 258 | # ==== REQUIRE_ORDER Ordering 259 | # 260 | # In the +REQUIRE_ORDER+ ordering, all options precede all non-options; 261 | # that is, each word after the first non-option word 262 | # is treated as a non-option word (even if it begins with a hyphen). 263 | # 264 | # File require_order.rb: 265 | # 266 | # :include: ../sample/getoptlong/require_order.rb 267 | # 268 | # Command line: 269 | # 270 | # $ ruby require_order.rb --xxx Foo Bar --xxx Baz --yyy Bat -zzz 271 | # 272 | # Output: 273 | # 274 | # Original ARGV: ["--xxx", "Foo", "Bar", "--xxx", "Baz", "--yyy", "Bat", "-zzz"] 275 | # ["--xxx", "Foo"] 276 | # Remaining ARGV: ["Bar", "--xxx", "Baz", "--yyy", "Bat", "-zzz"] 277 | # 278 | # ==== RETURN_IN_ORDER Ordering 279 | # 280 | # In the +RETURN_IN_ORDER+ ordering, every word is treated as an option. 281 | # A word that begins with a hyphen (or two) is treated in the usual way; 282 | # a word +word+ that does not so begin is treated as an option 283 | # whose name is an empty string, and whose value is +word+. 284 | # 285 | # File return_in_order.rb: 286 | # 287 | # :include: ../sample/getoptlong/return_in_order.rb 288 | # 289 | # Command line: 290 | # 291 | # $ ruby return_in_order.rb Foo --xxx Bar Baz --zzz Bat Bam 292 | # 293 | # Output: 294 | # 295 | # Original ARGV: ["Foo", "--xxx", "Bar", "Baz", "--zzz", "Bat", "Bam"] 296 | # ["", "Foo"] 297 | # ["--xxx", "Bar"] 298 | # ["", "Baz"] 299 | # ["--zzz", ""] 300 | # ["", "Bat"] 301 | # ["", "Bam"] 302 | # Remaining ARGV: [] 303 | # 304 | # === Full Example 305 | # 306 | # File fibonacci.rb: 307 | # 308 | # :include: ../sample/getoptlong/fibonacci.rb 309 | # 310 | # Command line: 311 | # 312 | # $ ruby fibonacci.rb 313 | # 314 | # Output: 315 | # 316 | # Option --number is required. 317 | # Usage: 318 | # 319 | # -n n, --number n: 320 | # Compute Fibonacci number for n. 321 | # -v [boolean], --verbose [boolean]: 322 | # Show intermediate results; default is 'false'. 323 | # -h, --help: 324 | # Show this help. 325 | # 326 | # Command line: 327 | # 328 | # $ ruby fibonacci.rb --number 329 | # 330 | # Raises GetoptLong::MissingArgument: 331 | # 332 | # fibonacci.rb: option `--number' requires an argument 333 | # 334 | # Command line: 335 | # 336 | # $ ruby fibonacci.rb --number 6 337 | # 338 | # Output: 339 | # 340 | # 8 341 | # 342 | # Command line: 343 | # 344 | # $ ruby fibonacci.rb --number 6 --verbose 345 | # 346 | # Output: 347 | # 1 348 | # 2 349 | # 3 350 | # 5 351 | # 8 352 | # 353 | # Command line: 354 | # 355 | # $ ruby fibonacci.rb --number 6 --verbose yes 356 | # 357 | # Output: 358 | # 359 | # --verbose argument must be true or false 360 | # Usage: 361 | # 362 | # -n n, --number n: 363 | # Compute Fibonacci number for n. 364 | # -v [boolean], --verbose [boolean]: 365 | # Show intermediate results; default is 'false'. 366 | # -h, --help: 367 | # Show this help. 368 | # 369 | class GetoptLong 370 | # Version. 371 | VERSION = "0.2.1" 372 | 373 | # 374 | # Orderings. 375 | # 376 | ORDERINGS = [REQUIRE_ORDER = 0, PERMUTE = 1, RETURN_IN_ORDER = 2] 377 | 378 | # 379 | # Argument flags. 380 | # 381 | ARGUMENT_FLAGS = [NO_ARGUMENT = 0, REQUIRED_ARGUMENT = 1, 382 | OPTIONAL_ARGUMENT = 2] 383 | 384 | # 385 | # Status codes. 386 | # 387 | STATUS_YET, STATUS_STARTED, STATUS_TERMINATED = 0, 1, 2 388 | 389 | # 390 | # Error types. 391 | # 392 | class Error < StandardError; end 393 | class AmbiguousOption < Error; end 394 | class NeedlessArgument < Error; end 395 | class MissingArgument < Error; end 396 | class InvalidOption < Error; end 397 | 398 | # 399 | # Returns a new \GetoptLong object based on the given +arguments+. 400 | # See {Options}[#class-GetoptLong-label-Options]. 401 | # 402 | # Example: 403 | # 404 | # :include: ../sample/getoptlong/simple.rb 405 | # 406 | # Raises an exception if: 407 | # 408 | # - Any of +arguments+ is not an array. 409 | # - Any option name or alias is not a string. 410 | # - Any option type is invalid. 411 | # 412 | def initialize(*arguments) 413 | # 414 | # Current ordering. 415 | # 416 | if ENV.include?('POSIXLY_CORRECT') 417 | @ordering = REQUIRE_ORDER 418 | else 419 | @ordering = PERMUTE 420 | end 421 | 422 | # 423 | # Hash table of option names. 424 | # Keys of the table are option names, and their values are canonical 425 | # names of the options. 426 | # 427 | @canonical_names = Hash.new 428 | 429 | # 430 | # Hash table of argument flags. 431 | # Keys of the table are option names, and their values are argument 432 | # flags of the options. 433 | # 434 | @argument_flags = Hash.new 435 | 436 | # 437 | # Whether error messages are output to $stderr. 438 | # 439 | @quiet = false 440 | 441 | # 442 | # Status code. 443 | # 444 | @status = STATUS_YET 445 | 446 | # 447 | # Error code. 448 | # 449 | @error = nil 450 | 451 | # 452 | # Error message. 453 | # 454 | @error_message = nil 455 | 456 | # 457 | # Rest of catenated short options. 458 | # 459 | @rest_singles = '' 460 | 461 | # 462 | # List of non-option-arguments. 463 | # Append them to ARGV when option processing is terminated. 464 | # 465 | @non_option_arguments = Array.new 466 | 467 | if 0 < arguments.length 468 | set_options(*arguments) 469 | end 470 | end 471 | 472 | # Sets the ordering; see {Ordering}[#class-GetoptLong-label-Ordering]; 473 | # returns the new ordering. 474 | # 475 | # If the given +ordering+ is +PERMUTE+ and environment variable 476 | # +POSIXLY_CORRECT+ is defined, sets the ordering to +REQUIRE_ORDER+; 477 | # otherwise sets the ordering to +ordering+: 478 | # 479 | # options = GetoptLong.new 480 | # options.ordering == GetoptLong::PERMUTE # => true 481 | # options.ordering = GetoptLong::RETURN_IN_ORDER 482 | # options.ordering == GetoptLong::RETURN_IN_ORDER # => true 483 | # ENV['POSIXLY_CORRECT'] = 'true' 484 | # options.ordering = GetoptLong::PERMUTE 485 | # options.ordering == GetoptLong::REQUIRE_ORDER # => true 486 | # 487 | # Raises an exception if +ordering+ is invalid. 488 | # 489 | def ordering=(ordering) 490 | # 491 | # The method is failed if option processing has already started. 492 | # 493 | if @status != STATUS_YET 494 | set_error(ArgumentError, "argument error") 495 | raise RuntimeError, 496 | "invoke ordering=, but option processing has already started" 497 | end 498 | 499 | # 500 | # Check ordering. 501 | # 502 | if !ORDERINGS.include?(ordering) 503 | raise ArgumentError, "invalid ordering `#{ordering}'" 504 | end 505 | if ordering == PERMUTE && ENV.include?('POSIXLY_CORRECT') 506 | @ordering = REQUIRE_ORDER 507 | else 508 | @ordering = ordering 509 | end 510 | end 511 | 512 | # 513 | # Returns the ordering setting. 514 | # 515 | attr_reader :ordering 516 | 517 | # 518 | # Replaces existing options with those given by +arguments+, 519 | # which have the same form as the arguments to ::new; 520 | # returns +self+. 521 | # 522 | # Raises an exception if option processing has begun. 523 | # 524 | def set_options(*arguments) 525 | # 526 | # The method is failed if option processing has already started. 527 | # 528 | if @status != STATUS_YET 529 | raise RuntimeError, 530 | "invoke set_options, but option processing has already started" 531 | end 532 | 533 | # 534 | # Clear tables of option names and argument flags. 535 | # 536 | @canonical_names.clear 537 | @argument_flags.clear 538 | 539 | arguments.each do |arg| 540 | if !arg.is_a?(Array) 541 | raise ArgumentError, "the option list contains non-Array argument" 542 | end 543 | 544 | # 545 | # Find an argument flag and it set to `argument_flag'. 546 | # 547 | argument_flag = nil 548 | arg.each do |i| 549 | if ARGUMENT_FLAGS.include?(i) 550 | if argument_flag != nil 551 | raise ArgumentError, "too many argument-flags" 552 | end 553 | argument_flag = i 554 | end 555 | end 556 | 557 | raise ArgumentError, "no argument-flag" if argument_flag == nil 558 | 559 | canonical_name = nil 560 | arg.each do |i| 561 | # 562 | # Check an option name. 563 | # 564 | next if i == argument_flag 565 | begin 566 | if !i.is_a?(String) || i !~ /\A-([^-]|-.+)\z/ 567 | raise ArgumentError, "an invalid option `#{i}'" 568 | end 569 | if (@canonical_names.include?(i)) 570 | raise ArgumentError, "option redefined `#{i}'" 571 | end 572 | rescue 573 | @canonical_names.clear 574 | @argument_flags.clear 575 | raise 576 | end 577 | 578 | # 579 | # Register the option (`i') to the `@canonical_names' and 580 | # `@canonical_names' Hashes. 581 | # 582 | if canonical_name == nil 583 | canonical_name = i 584 | end 585 | @canonical_names[i] = canonical_name 586 | @argument_flags[i] = argument_flag 587 | end 588 | raise ArgumentError, "no option name" if canonical_name == nil 589 | end 590 | return self 591 | end 592 | 593 | # 594 | # Sets quiet mode and returns the given argument: 595 | # 596 | # - When +false+ or +nil+, error messages are written to $stdout. 597 | # - Otherwise, error messages are not written. 598 | # 599 | attr_writer :quiet 600 | 601 | # 602 | # Returns the quiet mode setting. 603 | # 604 | attr_reader :quiet 605 | alias quiet? quiet 606 | 607 | # 608 | # Terminate option processing; 609 | # returns +nil+ if processing has already terminated; 610 | # otherwise returns +self+. 611 | # 612 | def terminate 613 | return nil if @status == STATUS_TERMINATED 614 | raise RuntimeError, "an error has occurred" if @error != nil 615 | 616 | @status = STATUS_TERMINATED 617 | @non_option_arguments.reverse_each do |argument| 618 | ARGV.unshift(argument) 619 | end 620 | 621 | @canonical_names = nil 622 | @argument_flags = nil 623 | @rest_singles = nil 624 | @non_option_arguments = nil 625 | 626 | return self 627 | end 628 | 629 | # 630 | # Returns +true+ if option processing has terminated, +false+ otherwise. 631 | # 632 | def terminated? 633 | return @status == STATUS_TERMINATED 634 | end 635 | 636 | # 637 | # \Set an error (a protected method). 638 | # 639 | def set_error(type, message) 640 | $stderr.print("#{$0}: #{message}\n") if !@quiet 641 | 642 | @error = type 643 | @error_message = message 644 | @canonical_names = nil 645 | @argument_flags = nil 646 | @rest_singles = nil 647 | @non_option_arguments = nil 648 | 649 | raise type, message 650 | end 651 | protected :set_error 652 | 653 | # 654 | # Returns whether option processing has failed. 655 | # 656 | attr_reader :error 657 | alias error? error 658 | 659 | # Return the appropriate error message in POSIX-defined format. 660 | # If no error has occurred, returns +nil+. 661 | # 662 | def error_message 663 | return @error_message 664 | end 665 | 666 | # 667 | # Returns the next option as a 2-element array containing: 668 | # 669 | # - The option name (the name itself, not an alias). 670 | # - The option value. 671 | # 672 | # Returns +nil+ if there are no more options. 673 | # 674 | def get 675 | option_name, option_argument = nil, '' 676 | 677 | # 678 | # Check status. 679 | # 680 | return nil if @error != nil 681 | case @status 682 | when STATUS_YET 683 | @status = STATUS_STARTED 684 | when STATUS_TERMINATED 685 | return nil 686 | end 687 | 688 | # 689 | # Get next option argument. 690 | # 691 | if 0 < @rest_singles.length 692 | argument = '-' + @rest_singles 693 | elsif (ARGV.length == 0) 694 | terminate 695 | return nil 696 | elsif @ordering == PERMUTE 697 | while 0 < ARGV.length && ARGV[0] !~ /\A-./ 698 | @non_option_arguments.push(ARGV.shift) 699 | end 700 | if ARGV.length == 0 701 | terminate 702 | return nil 703 | end 704 | argument = ARGV.shift 705 | elsif @ordering == REQUIRE_ORDER 706 | if (ARGV[0] !~ /\A-./) 707 | terminate 708 | return nil 709 | end 710 | argument = ARGV.shift 711 | else 712 | argument = ARGV.shift 713 | end 714 | 715 | # 716 | # Check the special argument `--'. 717 | # `--' indicates the end of the option list. 718 | # 719 | if argument == '--' && @rest_singles.length == 0 720 | terminate 721 | return nil 722 | end 723 | 724 | # 725 | # Check for long and short options. 726 | # 727 | if argument =~ /\A(--[^=]+)/ && @rest_singles.length == 0 728 | # 729 | # This is a long style option, which start with `--'. 730 | # 731 | pattern = $1 732 | if @canonical_names.include?(pattern) 733 | option_name = pattern 734 | else 735 | # 736 | # The option `option_name' is not registered in `@canonical_names'. 737 | # It may be an abbreviated. 738 | # 739 | matches = [] 740 | @canonical_names.each_key do |key| 741 | if key.index(pattern) == 0 742 | option_name = key 743 | matches << key 744 | end 745 | end 746 | if 2 <= matches.length 747 | set_error(AmbiguousOption, "option `#{argument}' is ambiguous between #{matches.join(', ')}") 748 | elsif matches.length == 0 749 | set_error(InvalidOption, "unrecognized option `#{argument}'") 750 | end 751 | end 752 | 753 | # 754 | # Check an argument to the option. 755 | # 756 | if @argument_flags[option_name] == REQUIRED_ARGUMENT 757 | if argument =~ /=(.*)/m 758 | option_argument = $1 759 | elsif 0 < ARGV.length 760 | option_argument = ARGV.shift 761 | else 762 | set_error(MissingArgument, 763 | "option `#{argument}' requires an argument") 764 | end 765 | elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT 766 | if argument =~ /=(.*)/m 767 | option_argument = $1 768 | elsif 0 < ARGV.length && ARGV[0] !~ /\A-./ 769 | option_argument = ARGV.shift 770 | else 771 | option_argument = '' 772 | end 773 | elsif argument =~ /=(.*)/m 774 | set_error(NeedlessArgument, 775 | "option `#{option_name}' doesn't allow an argument") 776 | end 777 | 778 | elsif argument =~ /\A(-(.))(.*)/m 779 | # 780 | # This is a short style option, which start with `-' (not `--'). 781 | # Short options may be catenated (e.g. `-l -g' is equivalent to 782 | # `-lg'). 783 | # 784 | option_name, ch, @rest_singles = $1, $2, $3 785 | 786 | if @canonical_names.include?(option_name) 787 | # 788 | # The option `option_name' is found in `@canonical_names'. 789 | # Check its argument. 790 | # 791 | if @argument_flags[option_name] == REQUIRED_ARGUMENT 792 | if 0 < @rest_singles.length 793 | option_argument = @rest_singles 794 | @rest_singles = '' 795 | elsif 0 < ARGV.length 796 | option_argument = ARGV.shift 797 | else 798 | # 1003.2 specifies the format of this message. 799 | set_error(MissingArgument, "option requires an argument -- #{ch}") 800 | end 801 | elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT 802 | if 0 < @rest_singles.length 803 | option_argument = @rest_singles 804 | @rest_singles = '' 805 | elsif 0 < ARGV.length && ARGV[0] !~ /\A-./ 806 | option_argument = ARGV.shift 807 | else 808 | option_argument = '' 809 | end 810 | end 811 | else 812 | # 813 | # This is an invalid option. 814 | # 1003.2 specifies the format of this message. 815 | # 816 | set_error(InvalidOption, "invalid option -- #{ch}") 817 | end 818 | else 819 | # 820 | # This is a non-option argument. 821 | # Only RETURN_IN_ORDER fell into here. 822 | # 823 | return '', argument 824 | end 825 | 826 | return @canonical_names[option_name], option_argument 827 | end 828 | alias get_option get 829 | 830 | # 831 | # Calls the given block with each option; 832 | # each option is a 2-element array containing: 833 | # 834 | # - The option name (the name itself, not an alias). 835 | # - The option value. 836 | # 837 | # Example: 838 | # 839 | # :include: ../sample/getoptlong/each.rb 840 | # 841 | # Command line: 842 | # 843 | # ruby each.rb -xxx Foo -x Bar --yyy Baz -y Bat --zzz 844 | # 845 | # Output: 846 | # 847 | # Original ARGV: ["-xxx", "Foo", "-x", "Bar", "--yyy", "Baz", "-y", "Bat", "--zzz"] 848 | # ["--xxx", "xx"] 849 | # ["--xxx", "Bar"] 850 | # ["--yyy", "Baz"] 851 | # ["--yyy", "Bat"] 852 | # ["--zzz", ""] 853 | # Remaining ARGV: ["Foo"] 854 | # 855 | def each 856 | loop do 857 | option_name, option_argument = get_option 858 | break if option_name == nil 859 | yield option_name, option_argument 860 | end 861 | end 862 | alias each_option each 863 | end 864 | --------------------------------------------------------------------------------