# frozen_string_literal: true # # GetoptLong for Ruby # # Copyright (C) 1998, 1999, 2000 Motoyuki Kasahara. # # You may redistribute and/or modify this library under the same license # terms as Ruby. # \Class \GetoptLong provides parsing both for options # and for regular arguments. # # Using \GetoptLong, you can define options for your program. # The program can then capture and respond to whatever options # are included in the command that executes the program. # # A simple example: file simple.rb: # # :include: ../sample/getoptlong/simple.rb # # If you are somewhat familiar with options, # you may want to skip to this # {full example}[#class-GetoptLong-label-Full+Example]. # # == Options # # A \GetoptLong option has: # # - A string option name. # - Zero or more string aliases for the name. # - An option type. # # Options may be defined by calling singleton method GetoptLong.new, # which returns a new \GetoptLong object. # Options may then be processed by calling other methods # such as GetoptLong#each. # # === Option Name and Aliases # # In the array that defines an option, # the first element is the string option name. # Often the name takes the 'long' form, beginning with two hyphens. # # The option name may have any number of aliases, # which are defined by additional string elements. # # The name and each alias must be of one of two forms: # # - Two hyphens, followed by one or more letters. # - One hyphen, followed by a single letter. # # File aliases.rb: # # :include: ../sample/getoptlong/aliases.rb # # An option may be cited by its name, # or by any of its aliases; # the parsed option always reports the name, not an alias: # # $ ruby aliases.rb -a -p --xxx --aaa -x # # Output: # # ["--xxx", ""] # ["--xxx", ""] # ["--xxx", ""] # ["--xxx", ""] # ["--xxx", ""] # # # An option may also be cited by an abbreviation of its name or any alias, # as long as that abbreviation is unique among the options. # # File abbrev.rb: # # :include: ../sample/getoptlong/abbrev.rb # # Command line: # # $ ruby abbrev.rb --xxx --xx --xyz --xy # # Output: # # ["--xxx", ""] # ["--xxx", ""] # ["--xyz", ""] # ["--xyz", ""] # # This command line raises GetoptLong::AmbiguousOption: # # $ ruby abbrev.rb --x # # === Repetition # # An option may be cited more than once: # # $ ruby abbrev.rb --xxx --xyz --xxx --xyz # # Output: # # ["--xxx", ""] # ["--xyz", ""] # ["--xxx", ""] # ["--xyz", ""] # # === Treating Remaining Options as Arguments # # A option-like token that appears # anywhere after the token -- is treated as an ordinary argument, # and is not processed as an option: # # $ ruby abbrev.rb --xxx --xyz -- --xxx --xyz # # Output: # # ["--xxx", ""] # ["--xyz", ""] # # === Option Types # # Each option definition includes an option type, # which controls whether the option takes an argument. # # File types.rb: # # :include: ../sample/getoptlong/types.rb # # Note that an option type has to do with the option argument # (whether it is required, optional, or forbidden), # not with whether the option itself is required. # # ==== Option with Required Argument # # An option of type GetoptLong::REQUIRED_ARGUMENT # must be followed by an argument, which is associated with that option: # # $ ruby types.rb --xxx foo # # Output: # # ["--xxx", "foo"] # # If the option is not last, its argument is whatever follows it # (even if the argument looks like another option): # # $ ruby types.rb --xxx --yyy # # Output: # # ["--xxx", "--yyy"] # # If the option is last, an exception is raised: # # $ ruby types.rb # # Raises GetoptLong::MissingArgument # # ==== Option with Optional Argument # # An option of type GetoptLong::OPTIONAL_ARGUMENT # may be followed by an argument, which if given is associated with that option. # # If the option is last, it does not have an argument: # # $ ruby types.rb --yyy # # Output: # # ["--yyy", ""] # # If the option is followed by another option, it does not have an argument: # # $ ruby types.rb --yyy --zzz # # Output: # # ["--yyy", ""] # ["--zzz", ""] # # Otherwise the option is followed by its argument, which is associated # with that option: # # $ ruby types.rb --yyy foo # # Output: # # ["--yyy", "foo"] # # ==== Option with No Argument # # An option of type GetoptLong::NO_ARGUMENT takes no argument: # # ruby types.rb --zzz foo # # Output: # # ["--zzz", ""] # # === ARGV # # You can process options either with method #each and a block, # or with method #get. # # During processing, each found option is removed, along with its argument # if there is one. # After processing, each remaining element was neither an option # nor the argument for an option. # # File argv.rb: # # :include: ../sample/getoptlong/argv.rb # # Command line: # # $ ruby argv.rb --xxx Foo --yyy Bar Baz --zzz Bat Bam # # Output: # # Original ARGV: ["--xxx", "Foo", "--yyy", "Bar", "Baz", "--zzz", "Bat", "Bam"] # ["--xxx", "Foo"] # ["--yyy", "Bar"] # ["--zzz", ""] # Remaining ARGV: ["Baz", "Bat", "Bam"] # # === Ordering # # There are three settings that control the way the options # are interpreted: # # - +PERMUTE+. # - +REQUIRE_ORDER+. # - +RETURN_IN_ORDER+. # # The initial setting for a new \GetoptLong object is +REQUIRE_ORDER+ # if environment variable +POSIXLY_CORRECT+ is defined, +PERMUTE+ otherwise. # # ==== PERMUTE Ordering # # In the +PERMUTE+ ordering, options and other, non-option, # arguments may appear in any order and any mixture. # # File permute.rb: # # :include: ../sample/getoptlong/permute.rb # # Command line: # # $ ruby permute.rb Foo --zzz Bar --xxx Baz --yyy Bat Bam --xxx Bag Bah # # Output: # # Original ARGV: ["Foo", "--zzz", "Bar", "--xxx", "Baz", "--yyy", "Bat", "Bam", "--xxx", "Bag", "Bah"] # ["--zzz", ""] # ["--xxx", "Baz"] # ["--yyy", "Bat"] # ["--xxx", "Bag"] # Remaining ARGV: ["Foo", "Bar", "Bam", "Bah"] # # ==== REQUIRE_ORDER Ordering # # In the +REQUIRE_ORDER+ ordering, all options precede all non-options; # that is, each word after the first non-option word # is treated as a non-option word (even if it begins with a hyphen). # # File require_order.rb: # # :include: ../sample/getoptlong/require_order.rb # # Command line: # # $ ruby require_order.rb --xxx Foo Bar --xxx Baz --yyy Bat -zzz # # Output: # # Original ARGV: ["--xxx", "Foo", "Bar", "--xxx", "Baz", "--yyy", "Bat", "-zzz"] # ["--xxx", "Foo"] # Remaining ARGV: ["Bar", "--xxx", "Baz", "--yyy", "Bat", "-zzz"] # # ==== RETURN_IN_ORDER Ordering # # In the +RETURN_IN_ORDER+ ordering, every word is treated as an option. # A word that begins with a hyphen (or two) is treated in the usual way; # a word +word+ that does not so begin is treated as an option # whose name is an empty string, and whose value is +word+. # # File return_in_order.rb: # # :include: ../sample/getoptlong/return_in_order.rb # # Command line: # # $ ruby return_in_order.rb Foo --xxx Bar Baz --zzz Bat Bam # # Output: # # Original ARGV: ["Foo", "--xxx", "Bar", "Baz", "--zzz", "Bat", "Bam"] # ["", "Foo"] # ["--xxx", "Bar"] # ["", "Baz"] # ["--zzz", ""] # ["", "Bat"] # ["", "Bam"] # Remaining ARGV: [] # # === Full Example # # File fibonacci.rb: # # :include: ../sample/getoptlong/fibonacci.rb # # Command line: # # $ ruby fibonacci.rb # # Output: # # Option --number is required. # Usage: # # -n n, --number n: # Compute Fibonacci number for n. # -v [boolean], --verbose [boolean]: # Show intermediate results; default is 'false'. # -h, --help: # Show this help. # # Command line: # # $ ruby fibonacci.rb --number # # Raises GetoptLong::MissingArgument: # # fibonacci.rb: option `--number' requires an argument # # Command line: # # $ ruby fibonacci.rb --number 6 # # Output: # # 8 # # Command line: # # $ ruby fibonacci.rb --number 6 --verbose # # Output: # 1 # 2 # 3 # 5 # 8 # # Command line: # # $ ruby fibonacci.rb --number 6 --verbose yes # # Output: # # --verbose argument must be true or false # Usage: # # -n n, --number n: # Compute Fibonacci number for n. # -v [boolean], --verbose [boolean]: # Show intermediate results; default is 'false'. # -h, --help: # Show this help. # class GetoptLong # Version. VERSION = "0.2.1" # # Orderings. # ORDERINGS = [REQUIRE_ORDER = 0, PERMUTE = 1, RETURN_IN_ORDER = 2] # # Argument flags. # ARGUMENT_FLAGS = [NO_ARGUMENT = 0, REQUIRED_ARGUMENT = 1, OPTIONAL_ARGUMENT = 2] # # Status codes. # STATUS_YET, STATUS_STARTED, STATUS_TERMINATED = 0, 1, 2 # # Error types. # class Error < StandardError; end class AmbiguousOption < Error; end class NeedlessArgument < Error; end class MissingArgument < Error; end class InvalidOption < Error; end # # Returns a new \GetoptLong object based on the given +arguments+. # See {Options}[#class-GetoptLong-label-Options]. # # Example: # # :include: ../sample/getoptlong/simple.rb # # Raises an exception if: # # - Any of +arguments+ is not an array. # - Any option name or alias is not a string. # - Any option type is invalid. # def initialize(*arguments) # # Current ordering. # if ENV.include?('POSIXLY_CORRECT') @ordering = REQUIRE_ORDER else @ordering = PERMUTE end # # Hash table of option names. # Keys of the table are option names, and their values are canonical # names of the options. # @canonical_names = Hash.new # # Hash table of argument flags. # Keys of the table are option names, and their values are argument # flags of the options. # @argument_flags = Hash.new # # Whether error messages are output to $stderr. # @quiet = false # # Status code. # @status = STATUS_YET # # Error code. # @error = nil # # Error message. # @error_message = nil # # Rest of catenated short options. # @rest_singles = '' # # List of non-option-arguments. # Append them to ARGV when option processing is terminated. # @non_option_arguments = Array.new if 0 < arguments.length set_options(*arguments) end end # Sets the ordering; see {Ordering}[#class-GetoptLong-label-Ordering]; # returns the new ordering. # # If the given +ordering+ is +PERMUTE+ and environment variable # +POSIXLY_CORRECT+ is defined, sets the ordering to +REQUIRE_ORDER+; # otherwise sets the ordering to +ordering+: # # options = GetoptLong.new # options.ordering == GetoptLong::PERMUTE # => true # options.ordering = GetoptLong::RETURN_IN_ORDER # options.ordering == GetoptLong::RETURN_IN_ORDER # => true # ENV['POSIXLY_CORRECT'] = 'true' # options.ordering = GetoptLong::PERMUTE # options.ordering == GetoptLong::REQUIRE_ORDER # => true # # Raises an exception if +ordering+ is invalid. # def ordering=(ordering) # # The method is failed if option processing has already started. # if @status != STATUS_YET set_error(ArgumentError, "argument error") raise RuntimeError, "invoke ordering=, but option processing has already started" end # # Check ordering. # if !ORDERINGS.include?(ordering) raise ArgumentError, "invalid ordering `#{ordering}'" end if ordering == PERMUTE && ENV.include?('POSIXLY_CORRECT') @ordering = REQUIRE_ORDER else @ordering = ordering end end # # Returns the ordering setting. # attr_reader :ordering # # Replaces existing options with those given by +arguments+, # which have the same form as the arguments to ::new; # returns +self+. # # Raises an exception if option processing has begun. # def set_options(*arguments) # # The method is failed if option processing has already started. # if @status != STATUS_YET raise RuntimeError, "invoke set_options, but option processing has already started" end # # Clear tables of option names and argument flags. # @canonical_names.clear @argument_flags.clear arguments.each do |arg| if !arg.is_a?(Array) raise ArgumentError, "the option list contains non-Array argument" end # # Find an argument flag and it set to `argument_flag'. # argument_flag = nil arg.each do |i| if ARGUMENT_FLAGS.include?(i) if argument_flag != nil raise ArgumentError, "too many argument-flags" end argument_flag = i end end raise ArgumentError, "no argument-flag" if argument_flag == nil canonical_name = nil arg.each do |i| # # Check an option name. # next if i == argument_flag begin if !i.is_a?(String) || i !~ /\A-([^-]|-.+)\z/ raise ArgumentError, "an invalid option `#{i}'" end if (@canonical_names.include?(i)) raise ArgumentError, "option redefined `#{i}'" end rescue @canonical_names.clear @argument_flags.clear raise end # # Register the option (`i') to the `@canonical_names' and # `@canonical_names' Hashes. # if canonical_name == nil canonical_name = i end @canonical_names[i] = canonical_name @argument_flags[i] = argument_flag end raise ArgumentError, "no option name" if canonical_name == nil end return self end # # Sets quiet mode and returns the given argument: # # - When +false+ or +nil+, error messages are written to $stdout. # - Otherwise, error messages are not written. # attr_writer :quiet # # Returns the quiet mode setting. # attr_reader :quiet alias quiet? quiet # # Terminate option processing; # returns +nil+ if processing has already terminated; # otherwise returns +self+. # def terminate return nil if @status == STATUS_TERMINATED raise RuntimeError, "an error has occurred" if @error != nil @status = STATUS_TERMINATED @non_option_arguments.reverse_each do |argument| ARGV.unshift(argument) end @canonical_names = nil @argument_flags = nil @rest_singles = nil @non_option_arguments = nil return self end # # Returns +true+ if option processing has terminated, +false+ otherwise. # def terminated? return @status == STATUS_TERMINATED end # # \Set an error (a protected method). # def set_error(type, message) $stderr.print("#{$0}: #{message}\n") if !@quiet @error = type @error_message = message @canonical_names = nil @argument_flags = nil @rest_singles = nil @non_option_arguments = nil raise type, message end protected :set_error # # Returns whether option processing has failed. # attr_reader :error alias error? error # Return the appropriate error message in POSIX-defined format. # If no error has occurred, returns +nil+. # def error_message return @error_message end # # Returns the next option as a 2-element array containing: # # - The option name (the name itself, not an alias). # - The option value. # # Returns +nil+ if there are no more options. # def get option_name, option_argument = nil, '' # # Check status. # return nil if @error != nil case @status when STATUS_YET @status = STATUS_STARTED when STATUS_TERMINATED return nil end # # Get next option argument. # if 0 < @rest_singles.length argument = '-' + @rest_singles elsif (ARGV.length == 0) terminate return nil elsif @ordering == PERMUTE while 0 < ARGV.length && ARGV[0] !~ /\A-./ @non_option_arguments.push(ARGV.shift) end if ARGV.length == 0 terminate return nil end argument = ARGV.shift elsif @ordering == REQUIRE_ORDER if (ARGV[0] !~ /\A-./) terminate return nil end argument = ARGV.shift else argument = ARGV.shift end # # Check the special argument `--'. # `--' indicates the end of the option list. # if argument == '--' && @rest_singles.length == 0 terminate return nil end # # Check for long and short options. # if argument =~ /\A(--[^=]+)/ && @rest_singles.length == 0 # # This is a long style option, which start with `--'. # pattern = $1 if @canonical_names.include?(pattern) option_name = pattern else # # The option `option_name' is not registered in `@canonical_names'. # It may be an abbreviated. # matches = [] @canonical_names.each_key do |key| if key.index(pattern) == 0 option_name = key matches << key end end if 2 <= matches.length set_error(AmbiguousOption, "option `#{argument}' is ambiguous between #{matches.join(', ')}") elsif matches.length == 0 set_error(InvalidOption, "unrecognized option `#{argument}'") end end # # Check an argument to the option. # if @argument_flags[option_name] == REQUIRED_ARGUMENT if argument =~ /=(.*)/m option_argument = $1 elsif 0 < ARGV.length option_argument = ARGV.shift else set_error(MissingArgument, "option `#{argument}' requires an argument") end elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT if argument =~ /=(.*)/m option_argument = $1 elsif 0 < ARGV.length && ARGV[0] !~ /\A-./ option_argument = ARGV.shift else option_argument = '' end elsif argument =~ /=(.*)/m set_error(NeedlessArgument, "option `#{option_name}' doesn't allow an argument") end elsif argument =~ /\A(-(.))(.*)/m # # This is a short style option, which start with `-' (not `--'). # Short options may be catenated (e.g. `-l -g' is equivalent to # `-lg'). # option_name, ch, @rest_singles = $1, $2, $3 if @canonical_names.include?(option_name) # # The option `option_name' is found in `@canonical_names'. # Check its argument. # if @argument_flags[option_name] == REQUIRED_ARGUMENT if 0 < @rest_singles.length option_argument = @rest_singles @rest_singles = '' elsif 0 < ARGV.length option_argument = ARGV.shift else # 1003.2 specifies the format of this message. set_error(MissingArgument, "option requires an argument -- #{ch}") end elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT if 0 < @rest_singles.length option_argument = @rest_singles @rest_singles = '' elsif 0 < ARGV.length && ARGV[0] !~ /\A-./ option_argument = ARGV.shift else option_argument = '' end end else # # This is an invalid option. # 1003.2 specifies the format of this message. # if ENV.include?('POSIXLY_CORRECT') set_error(InvalidOption, "invalid option -- #{ch}") else set_error(InvalidOption, "invalid option -- #{ch}") end end else # # This is a non-option argument. # Only RETURN_IN_ORDER fell into here. # return '', argument end return @canonical_names[option_name], option_argument end alias get_option get # # Calls the given block with each option; # each option is a 2-element array containing: # # - The option name (the name itself, not an alias). # - The option value. # # Example: # # :include: ../sample/getoptlong/each.rb # # Command line: # # ruby each.rb -xxx Foo -x Bar --yyy Baz -y Bat --zzz # # Output: # # Original ARGV: ["-xxx", "Foo", "-x", "Bar", "--yyy", "Baz", "-y", "Bat", "--zzz"] # ["--xxx", "xx"] # ["--xxx", "Bar"] # ["--yyy", "Baz"] # ["--yyy", "Bat"] # ["--zzz", ""] # Remaining ARGV: ["Foo"] # def each loop do option_name, option_argument = get_option break if option_name == nil yield option_name, option_argument end end alias each_option each end