Snippet: modifications to GetoptLong to allow processing of method arguments

I don’t know exactly how useful any of you out there will find the
following, but I thought it was interesting enough to share. The main
modification is to allow the ‘get’ and ‘each’ methods to take an
argument – an array of strings akin to ARGV. If this argument is
present, it is processed by the Getoptlong object instead of ARGV. If
the argument is not present, ARGV is processed as in the original
version. I overrode the existing get method, copying it almost verbatim
except for basically substituting the passed-in argument for ARGV
everywhere. The following code can be put in your code to gain the
enhancement in behavior.

Why did I do this? I had an existing program whose code was not
encapsulated in a class, and I needed to do so in order to enable
someone else to use this code. I didn’t want to lose the ability to
flexibly process arbitrary arguments, so I decided to see how hard it
would be to modify Getoptlong. As it turns out, it was pretty easy.
Within 90 minutes I had worked out all the little kinks (e.g., is it
*args or args in my method definition?), resulting in a new version
which was 100% compatible with existing clients’ uses of the code and
also cleanly usable as an object by new clients.

Al

class GetoptLong

Give ourselves read access to some of GetoptLong’s internal data

structures

(makes printing the usage information for programs easier).

attr_reader :canonical_names, :argument_flags

alias :original_get :get

Changed so that we can specify an array of arguments to process,

which defaults to ARGV for backward compatibility.

def get( arguments )
if arguments.empty? then
arguments = ARGV
end

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 (arguments.length == 0)
  terminate
  return nil
elsif @ordering == PERMUTE
  while 0 < arguments.length && arguments[0] !~ /^-./
    @non_option_arguments.push(arguments.shift)
  end
  if arguments.length == 0
terminate
return nil
  end
  argument = arguments.shift
elsif @ordering == REQUIRE_ORDER
  if (arguments[0] !~ /^-./)
terminate
return nil
  end
  argument = arguments.shift
else
  argument = arguments.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 =~ /^(--[^=]+)/ && @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. # match_count = 0 @canonical_names.each_key do |key| if key.index(pattern) == 0 option_name = key match_count += 1 end end if 2 <= match_count set_error(AmbigousOption, "option#{argument}’ is ambiguous")
elsif match_count == 0
set_error(InvalidOption, “unrecognized option `#{argument}’”)
end
end

  #
  # Check an argument to the option.
  #
  if @argument_flags[option_name] == REQUIRED_ARGUMENT
    if argument =~ /=(.*)$/
      option_argument = $1
    elsif 0 < arguments.length
      option_argument = arguments.shift
    else
      set_error(MissingArgument,
            "option `#{argument}' requires an argument")
    end
  elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT
    if argument =~ /=(.*)$/
      option_argument = $1
    elsif 0 < arguments.length && arguments[0] !~ /^-./
      option_argument = arguments.shift
    else
      option_argument = ''
    end
  elsif argument =~ /=(.*)$/
    set_error(NeedlessArgument,
      "option `#{option_name}' doesn't allow an argument")
  end

elsif argument =~ /^(-(.))(.*)/
  #
  # This is a short style option, which start with `-' (not `--').
  # Short options may be catinated (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 < arguments.length
        option_argument = arguments.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 < arguments.length && arguments[0] !~ /^-./
option_argument = arguments.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, “illegal option – #{ch}”)
else
set_error(InvalidOption, “invalid option – #{ch}”)
end
end
else
#
# This is a non-option argument.
# Only RETURN_IN_ORDER falled into here.
#
return ‘’, argument
end

return @canonical_names[option_name], option_argument

end

Make sure get_option is an alias for the new get method defined

above!
alias :get_option :get

alias :original_each :each

Changed so that we can specify an array of arguments to process,

which defaults to ARGV for backward compatibility.

def each( arguments = nil )
loop do
if arguments.nil? then
option_name, option_argument = get_option
else
option_name, option_argument = get_option( arguments )
end
break if option_name == nil
yield option_name, option_argument
end
end
end


Albert Davidson Chou, QA Manager
TeaLeaf Technology, Inc.
(415) 932-5031
AChou@TeaLeaf.com | http://www.TeaLeaf.com/

Evaluate TeaLeaf Technology today:
http://www.TeaLeaf.com/demo/

Wouldn’t it make more sense to, since you’ve already s/ARGV/arguments/,
add a function use_args(array) to set what arguments points to? You
could make initialize set arguments = ARGV, and if user code wanted to
parse a different array, it would cal use_args(differentArray). It
might be more sane to only allow use_args before processing has started.

This way, it still wouldn’t change client code, but you wouldn’t have to
pass the array for each get. I think the library should be like that in
the first place.

···

On Thu, 2003-04-03 at 21:16, Albert Chou wrote:

I don’t know exactly how useful any of you out there will find the
following, but I thought it was interesting enough to share. The main
modification is to allow the ‘get’ and ‘each’ methods to take an
argument – an array of strings akin to ARGV. If this argument is
present, it is processed by the Getoptlong object instead of ARGV. If
the argument is not present, ARGV is processed as in the original
version. I overrode the existing get method, copying it almost verbatim
except for basically substituting the passed-in argument for ARGV
everywhere. The following code can be put in your code to gain the
enhancement in behavior.

Why did I do this? I had an existing program whose code was not
encapsulated in a class, and I needed to do so in order to enable
someone else to use this code. I didn’t want to lose the ability to
flexibly process arbitrary arguments, so I decided to see how hard it
would be to modify Getoptlong. As it turns out, it was pretty easy.
Within 90 minutes I had worked out all the little kinks (e.g., is it
*args or args in my method definition?), resulting in a new version
which was 100% compatible with existing clients’ uses of the code and
also cleanly usable as an object by new clients.

Al


Tom Felker

It might look like I’m standing motionless, but I’m actively waiting for
my
problems to go away.