module Getopt REQUIRED = 0 BOOLEAN = 1 OPTIONAL = 2 INCREMENT = 3 NEGATABLE = 4 class LongError < StandardError; end class Long VERSION = "1.3.4" # Takes an array of switches. Each array consists of three elements. def self.getopts(*switches) if switches.empty? raise ArgumentError, "no switches provided" end hash = {} # Hash returned to user valid = [] # Tracks valid switches types = {} # Tracks argument types syns = {} # Tracks long and short arguments, or multiple shorts # If a string is passed, split it and convert it to an array of arrays if switches.first.kind_of?(String) switches = switches.join.split switches.map!{ |switch| switch = [switch] } end # Set our list of valid switches, and proper types for each switch switches.each{ |switch| valid.push(switch[0]) # Set valid long switches # Set type for long switch, default to BOOLEAN. # Create synonym hash. Default to first char of long switch for # short switch, e.g. "--verbose" creates a "-v" synonym. if switch[1].kind_of?(Fixnum) switch[2] = switch[1] types[switch[0]] = switch[2] switch[1] = switch[0][1..2] else switch[2] ||= BOOLEAN types[switch[0]] = switch[2] switch[1] ||= switch[0][1..2] end syns[switch[0]] = switch[1] syns[switch[1]] = switch[0] switch[1].each{ |char| types[char] = switch[2] # Set type for short switch valid.push(char) # Set valid short switches } } re_long = /^(--\w+[-\w+]*)?$/ re_short = /^(-\w)$/ re_long_eq = /^(--\w+[-\w+]*)?=(.*?)$|(-\w?)=(.*?)$/ re_short_sq = /^(-\w)(\S+?)$/ ARGV.each_with_index{ |opt, index| # Allow either -x -v or -xv style for single char args if re_short_sq.match(opt) chars = opt.split("")[1..-1].map{ |s| s = "-#{s}" } chars.each_with_index{ |char, i| unless valid.include?(char) raise LongError, "invalid switch '#{char}'" end # Grab the next arg if the switch takes a required arg if types[char] == REQUIRED # Deal with a argument squished up against switch if chars[i+1] arg = chars[i+1..-1].join.tr("-","") ARGV.push(char, arg) break else arg = ARGV.delete_at(index+1) if arg.nil? || valid.include?(arg) # Minor cheat here err = "no value provided for required argument '#{char}'" raise LongError, err end ARGV.push(char, arg) end elsif types[char] == OPTIONAL if chars[i+1] && !valid.include?(chars[i+1]) arg = chars[i+1..-1].join.tr("-","") ARGV.push(char, arg) break elsif if ARGV[index+1] && !valid.include?(ARGV[index+1]) arg = ARGV.delete_at(index+1) ARGV.push(char, arg) end else ARGV.push(char) end else ARGV.push(char) end } next end if match = re_long.match(opt) || match = re_short.match(opt) switch = match.captures.first end if match = re_long_eq.match(opt) switch, value = match.captures.compact ARGV.push(switch, value) next end # Make sure that all the switches are valid. If 'switch' isn't # defined at this point, it means an option was passed without # a preceding switch, e.g. --option foo bar. unless valid.include?(switch) switch ||= opt raise LongError, "invalid switch '#{switch}'" end # Required arguments if types[switch] == REQUIRED nextval = ARGV[index+1] # Make sure there's a value for mandatory arguments if nextval.nil? err = "no value provided for required argument '#{switch}'" raise LongError, err end # If there is a value, make sure it's not another switch if valid.include?(nextval) err = "cannot pass switch '#{nextval}' as an argument" raise LongError, err end # If the same option appears more than once, put the values # in array. if hash[switch] hash[switch] = [hash[switch], nextval].flatten else hash[switch] = nextval end ARGV.delete_at(index+1) end # For boolean arguments set the switch's value to true. if types[switch] == BOOLEAN if hash.has_key?(switch) raise LongError, "boolean switch already set" end hash[switch] = true end # For increment arguments, set the switch's value to 0, or # increment it by one if it already exists. if types[switch] == INCREMENT if hash.has_key?(switch) hash[switch] += 1 else hash[switch] = 1 end end # For optional argument, there may be an argument. If so, it # cannot be another switch. If not, it is set to true. if types[switch] == OPTIONAL nextval = ARGV[index+1] if valid.include?(nextval) hash[switch] = true else hash[switch] = nextval ARGV.delete_at(index+1) end end } # Set synonymous switches to the same value, e.g. if -t is a synonym # for --test, and the user passes "--test", then set "-t" to the same # value that "--test" was set to. # # This allows users to refer to the long or short switch and get # the same value hash.each{ |switch, val| if syns.keys.include?(switch) syns[switch].each{ |key| hash[key] = val } end } # Get rid of leading "--" and "-" to make it easier to reference hash.each{ |key, value| if key[0,2] == '--' nkey = key.sub('--', '') else nkey = key.sub('-', '') end hash.delete(key) hash[nkey] = value } hash end end end