lib/getoptlong.rb


DEFINITIONS

This source file includes following functions.


   1  #                                                         -*- Ruby -*-
   2  # Copyright (C) 1998, 1999, 2000  Motoyuki Kasahara
   3  #
   4  # You may redistribute it and/or modify it under the same license
   5  # terms as Ruby.
   6  #
   7  
   8  #
   9  # Documents and latest version of `getoptlong.rb' are found at:
  10  #    http://www.sra.co.jp/people/m-kasahr/ruby/getoptlong/
  11  #
  12  
  13  #
  14  # Parse command line options just like GNU getopt_long().
  15  #
  16  class GetoptLong
  17    #
  18    # Orderings.
  19    #
  20    ORDERINGS = [REQUIRE_ORDER = 0, PERMUTE = 1, RETURN_IN_ORDER = 2]
  21  
  22    #
  23    # Argument flags.
  24    #
  25    ARGUMENT_FLAGS = [NO_ARGUMENT = 0, REQUIRED_ARGUMENT = 1,
  26      OPTIONAL_ARGUMENT = 2]
  27  
  28    #
  29    # Status codes.
  30    #
  31    STATUS_YET, STATUS_STARTED, STATUS_TERMINATED = 0, 1, 2
  32  
  33    #
  34    # Error types.
  35    #
  36    class AmbigousOption   < StandardError; end
  37    class NeedlessArgument < StandardError; end
  38    class MissingArgument  < StandardError; end
  39    class InvalidOption    < StandardError; end
  40  
  41    #
  42    # Initializer.
  43    #
  44    def initialize(*arguments)
  45      #
  46      # Current ordering.
  47      #
  48      if ENV.include?('POSIXLY_CORRECT')
  49        @ordering = REQUIRE_ORDER
  50      else
  51        @ordering = PERMUTE
  52      end
  53  
  54      #
  55      # Hash table of option names.
  56      # Keyes of the table are option names, and their values are canonical
  57      # names of the options.
  58      #
  59      @canonical_names = Hash.new
  60  
  61      #
  62      # Hash table of argument flags.
  63      # Keyes of the table are option names, and their values are argument
  64      # flags of the options.
  65      #
  66      @argument_flags = Hash.new
  67  
  68      #
  69      # Whether error messages are output to stderr.
  70      #
  71      @quiet = FALSE
  72  
  73      #
  74      # Status code.
  75      #
  76      @status = STATUS_YET
  77  
  78      #
  79      # Error code.
  80      #
  81      @error = nil
  82  
  83      #
  84      # Error message.
  85      #
  86      @error_message = nil
  87  
  88      #
  89      # Rest of catinated short options.
  90      #
  91      @rest_singles = ''
  92  
  93      #
  94      # List of non-option-arguments.
  95      # Append them to ARGV when option processing is terminated.
  96      #
  97      @non_option_arguments = Array.new
  98  
  99      if 0 < arguments.length
 100        set_options(*arguments)
 101      end
 102    end
 103  
 104    #
 105    # Set ordering.
 106    #
 107    def ordering=(ordering)
 108      #
 109      # The method is failed if option processing has already started.
 110      #
 111      if @status != STATUS_YET
 112        set_error(ArgumentError, "argument error")
 113        raise RuntimeError,
 114          "invoke ordering=, but option processing has already started"
 115      end
 116  
 117      #
 118      # Check ordering.
 119      #
 120      if !ORDERINGS.include?(ordering)
 121        raise ArgumentError, "invalid ordering `#{ordering}'"
 122      end
 123      if ordering == PERMUTE && ENV.include?('POSIXLY_CORRECT')
 124        @ordering = REQUIRE_ORDER
 125      else
 126        @ordering = ordering
 127      end
 128    end
 129  
 130    #
 131    # Return ordering.
 132    #
 133    attr_reader :ordering
 134  
 135    #
 136    # Set options
 137    #
 138    def set_options(*arguments)
 139      #
 140      # The method is failed if option processing has already started.
 141      #
 142      if @status != STATUS_YET
 143        raise RuntimeError, 
 144          "invoke set_options, but option processing has already started"
 145      end
 146  
 147      #
 148      # Clear tables of option names and argument flags.
 149      #
 150      @canonical_names.clear
 151      @argument_flags.clear
 152  
 153      arguments.each do |arg|
 154        #
 155        # Each argument must be an Array.
 156        #
 157        if !arg.is_a?(Array)
 158          raise ArgumentError, "the option list contains non-Array argument"
 159        end
 160  
 161        #
 162        # Find an argument flag and it set to `argument_flag'.
 163        #
 164        argument_flag = nil
 165        arg.each do |i|
 166          if ARGUMENT_FLAGS.include?(i)
 167            if argument_flag != nil
 168              raise ArgumentError, "too many argument-flags"
 169            end
 170            argument_flag = i
 171          end
 172        end
 173        raise ArgumentError, "no argument-flag" if argument_flag == nil
 174  
 175        canonical_name = nil
 176        arg.each do |i|
 177          #
 178          # Check an option name.
 179          #
 180          next if i == argument_flag
 181          begin
 182            if !i.is_a?(String) || i !~ /^-([^-]|-.+)$/
 183              raise ArgumentError, "an invalid option `#{i}'"
 184            end
 185            if (@canonical_names.include?(i))
 186              raise ArgumentError, "option redefined `#{i}'"
 187            end
 188          rescue
 189            @canonical_names.clear
 190            @argument_flags.clear
 191            raise
 192          end
 193  
 194          #
 195          # Register the option (`i') to the `@canonical_names' and 
 196          # `@canonical_names' Hashes.
 197          #
 198          if canonical_name == nil
 199            canonical_name = i
 200          end
 201          @canonical_names[i] = canonical_name
 202          @argument_flags[i] = argument_flag
 203        end
 204        raise ArgumentError, "no option name" if canonical_name == nil
 205      end
 206      return self
 207    end
 208  
 209    #
 210    # Set/Unset `quit' mode.
 211    #
 212    attr_writer :quiet
 213  
 214    #
 215    # Return the flag of `quiet' mode.
 216    #
 217    attr_reader :quiet
 218  
 219    #
 220    # `quiet?' is an alias of `quiet'.
 221    #
 222    alias quiet? quiet
 223  
 224    #
 225    # Termintate option processing.
 226    #
 227    def terminate
 228      return nil if @status == STATUS_TERMINATED
 229      raise RuntimeError, "an error has occured" if @error != nil
 230  
 231      @status = STATUS_TERMINATED
 232      @non_option_arguments.reverse_each do |argument|
 233        ARGV.unshift(argument)
 234      end
 235  
 236      @canonical_names = nil
 237      @argument_flags = nil
 238      @rest_singles = nil
 239      @non_option_arguments = nil
 240  
 241      return self
 242    end
 243  
 244    #
 245    # Examine whether option processing is termintated or not.
 246    #
 247    def terminated?
 248      return @status == STATUS_TERMINATED
 249    end
 250  
 251    #
 252    # Set an error (protected).
 253    #
 254    def set_error(type, message)
 255      $stderr.print("#{$0}: #{message}\n") if !@quiet
 256  
 257      @error = type
 258      @error_message = message
 259      @canonical_names = nil
 260      @argument_flags = nil
 261      @rest_singles = nil
 262      @non_option_arguments = nil
 263  
 264      raise type, message
 265    end
 266    protected :set_error
 267  
 268    #
 269    # Examine whether an option processing is failed.
 270    #
 271    attr_reader :error
 272  
 273    #
 274    # `error?' is an alias of `error'.
 275    #
 276    alias error? error
 277  
 278    #
 279    # Return an error message.
 280    #
 281    def error_message
 282      return @error_message
 283    end
 284  
 285    #
 286    # Get next option name and its argument as an array.
 287    #
 288    def get
 289      option_name, option_argument = nil, ''
 290  
 291      #
 292      # Check status.
 293      #
 294      return nil if @error != nil
 295      case @status
 296      when STATUS_YET
 297        @status = STATUS_STARTED
 298      when STATUS_TERMINATED
 299        return nil
 300      end
 301  
 302      #
 303      # Get next option argument.
 304      #
 305      if 0 < @rest_singles.length
 306        argument = '-' + @rest_singles
 307      elsif (ARGV.length == 0)
 308        terminate
 309        return nil
 310      elsif @ordering == PERMUTE
 311        while 0 < ARGV.length && ARGV[0] !~ /^-./
 312          @non_option_arguments.push(ARGV.shift)
 313        end
 314        if ARGV.length == 0
 315          terminate
 316          return nil
 317        end
 318        argument = ARGV.shift
 319      elsif @ordering == REQUIRE_ORDER 
 320        if (ARGV[0] !~ /^-./)
 321          terminate
 322          return nil
 323        end
 324        argument = ARGV.shift
 325      else
 326        argument = ARGV.shift
 327      end
 328  
 329      #
 330      # Check the special argument `--'.
 331      # `--' indicates the end of the option list.
 332      #
 333      if argument == '--' && @rest_singles.length == 0
 334        terminate
 335        return nil
 336      end
 337  
 338      #
 339      # Check for long and short options.
 340      #
 341      if argument =~ /^(--[^=]+)/ && @rest_singles.length == 0
 342        #
 343        # This is a long style option, which start with `--'.
 344        #
 345        pattern = $1
 346        if @canonical_names.include?(pattern)
 347          option_name = pattern
 348        else
 349          #
 350          # The option `option_name' is not registered in `@canonical_names'.
 351          # It may be an abbreviated.
 352          #
 353          match_count = 0
 354          @canonical_names.each_key do |key|
 355            if key.index(pattern) == 0
 356              option_name = key
 357              match_count += 1
 358            end
 359          end
 360          if 2 <= match_count
 361            set_error(AmbigousOption, "option `#{argument}' is ambiguous")
 362          elsif match_count == 0
 363            set_error(InvalidOption, "unrecognized option `#{argument}'")
 364          end
 365        end
 366  
 367        #
 368        # Check an argument to the option.
 369        #
 370        if @argument_flags[option_name] == REQUIRED_ARGUMENT
 371          if argument =~ /=(.*)$/
 372            option_argument = $1
 373          elsif 0 < ARGV.length
 374            option_argument = ARGV.shift
 375          else
 376            set_error(MissingArgument,
 377                      "option `#{argument}' requires an argument")
 378          end
 379        elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT
 380          if argument =~ /=(.*)$/
 381            option_argument = $1
 382          elsif 0 < ARGV.length && ARGV[0] !~ /^-./
 383            option_argument = ARGV.shift
 384          else
 385            option_argument = ''
 386          end
 387        elsif argument =~ /=(.*)$/
 388          set_error(NeedlessArgument,
 389                    "option `#{option_name}' doesn't allow an argument")
 390        end
 391  
 392      elsif argument =~ /^(-(.))(.*)/
 393        #
 394        # This is a short style option, which start with `-' (not `--').
 395        # Short options may be catinated (e.g. `-l -g' is equivalent to
 396        # `-lg').
 397        #
 398        option_name, ch, @rest_singles = $1, $2, $3
 399  
 400        if @canonical_names.include?(option_name)
 401          #
 402          # The option `option_name' is found in `@canonical_names'.
 403          # Check its argument.
 404          #
 405          if @argument_flags[option_name] == REQUIRED_ARGUMENT
 406            if 0 < @rest_singles.length
 407              option_argument = @rest_singles
 408              @rest_singles = ''
 409            elsif 0 < ARGV.length
 410              option_argument = ARGV.shift
 411            else
 412              # 1003.2 specifies the format of this message.
 413              set_error(MissingArgument, "option requires an argument -- #{ch}")
 414            end
 415          elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT
 416            if 0 < @rest_singles.length
 417              option_argument = @rest_singles
 418              @rest_singles = ''
 419            elsif 0 < ARGV.length && ARGV[0] !~ /^-./
 420              option_argument = ARGV.shift
 421            else
 422              option_argument = ''
 423            end
 424          end
 425        else
 426          #
 427          # This is an invalid option.
 428          # 1003.2 specifies the format of this message.
 429          #
 430          if ENV.include?('POSIXLY_CORRECT')
 431            set_error(InvalidOption, "illegal option -- #{ch}")
 432          else
 433            set_error(InvalidOption, "invalid option -- #{ch}")
 434          end
 435        end
 436      else
 437        #
 438        # This is a non-option argument.
 439        # Only RETURN_IN_ORDER falled into here.
 440        #
 441        return '', argument
 442      end
 443  
 444      return @canonical_names[option_name], option_argument
 445    end
 446  
 447    #
 448    # `get_option' is an alias of `get'.
 449    #
 450    alias get_option get
 451  
 452    #
 453    # Iterator version of `get'.
 454    #
 455    def each
 456      loop do
 457        option_name, option_argument = get_option
 458        break if option_name == nil
 459        yield option_name, option_argument
 460      end
 461    end
 462  
 463    #
 464    # `each_option' is an alias of `each'.
 465    #
 466    alias each_option each
 467  end