Module: Choice::Parser
Overview
The parser takes our option definitions and our arguments and produces a hash of values.
Defined Under Namespace
Classes: ArgumentRequired, ArgumentRequiredWithValid, ArgumentValidationFails, HashExpectedForOption, InvalidArgument, ParseError, UnknownOption, ValidateExpectsRegexpOrBlock
Constant Summary collapse
- CAST_METHODS =
What method to call on an object for each given ‘cast’ value.
{ Integer => :to_i, String => :to_s, Float => :to_f, Symbol => :to_sym }
Instance Method Summary collapse
-
#parse(options, args) ⇒ Object
Perhaps this method does too much.
Instance Method Details
#parse(options, args) ⇒ Object
Perhaps this method does too much. It is, however, a parser. You pass it an array of arrays, the first element of each element being the option’s name and the second element being a hash of the option’s info. You also pass in your current arguments, so it knows what to check against.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/choice/parser.rb', line 17 def parse(, args) # Return empty hash if the parsing adventure would be fruitless. return {} if .nil? || ! || args.nil? || !args.is_a?(Array) # Operate on a copy of the inputs args = args.dup # If we are passed an array, make the best of it by converting it # to a hash. = .inject({}) do |hash, value| value.is_a?(Array) ? hash.merge(value.first => value[1]) : hash end if .is_a? Array # Define local hashes we're going to use. choices is where we store # the actual values we've pulled from the argument list. hashes, longs, required, validators, choices, arrayed = {}, {}, {}, {}, {}, {} hard_required = {} # We can define these on the fly because they are all so similar. params = %w[short cast filter action default valid] params.each { |param| hashes["#{param}s"] = {} } # Inspect each option and move its info into our local hashes. .each do |name, obj| name = name.to_s # Only take hashes or hash-like duck objects. raise HashExpectedForOption unless obj.respond_to? :to_h obj = obj.to_h # Is this option required? hard_required[name] = true if obj['required'] # Set the local hashes if the value exists on this option object. params.each { |param| hashes["#{param}s"][name] = obj[param] if obj[param] } # If there is a validate statement, make it a regex or proc. validators[name] = make_validation(obj['validate']) if obj['validate'] # Parse the long option. If it contains a =, figure out if the # argument is required or optional. Optional arguments are formed # like [=ARG], whereas required are just ARG (in --long=ARG style). if obj['long'] && obj['long'] =~ /(=|\[| )/ # Save the separator we used, as we're gonna need it, then split sep = $1 option, *argument = obj['long'].split(sep) # The actual name of the long switch longs[name] = option # Preserve the original argument, as it may contain [ or =, # by joining with the character we split on. Add a [ in front if # we split on that. argument = (sep == '[' ? '[' : '') << Array(argument).join(sep) # Do we expect multiple arguments which get turned into an array? arrayed[name] = true if argument =~ /^\[?=?\*(.+)\]?$/ # Is this long required or optional? required[name] = true unless argument =~ /^\[=?\*?(.+)\]$/ elsif obj['long'] # We can't have a long as a switch when valid is set -- die. raise ArgumentRequiredWithValid if obj['valid'] # Set without any checking if it's just --long longs[name] = obj['long'] end # If we were given a list of valid arguments with 'valid,' this option # is definitely required. required[name] = true if obj['valid'] end rest = [] # Go through the arguments and try to figure out whom they belong to # at this point. while arg = args.shift if hashes['shorts'].value?(arg) # Set the value to the next element in the args array since # this is a short. # If the next argument isn't a value, set this value to true if args.empty? || args.first.match(/^-/) value = true else value = args.shift end # Add this value to the choices hash with the key of the option's # name. If we expect an array, tack this argument on. name = hashes['shorts'].index(arg) if arrayed[name] choices[name] ||= [] choices[name] << value unless value.nil? choices[name] += arrayize_arguments(args) else choices[name] = value end elsif (m = arg.match(/^(--[^=]+)=?/)) && longs.value?(m[1]) # The joke here is we always accept both --long=VALUE and --long VALUE. # Grab values from --long=VALUE format name, value = arg.split('=', 2) name = longs.index(name) if value.nil? && args.first !~ /^-/ # Grab value otherwise if not in --long=VALUE format. Assume --long VALUE. # Value is nil if we don't have a = and the next argument is no good value = args.shift end # If we expect an array, tack this argument on. if arrayed[name] # If this is arrayed and the value isn't nil, set it. choices[name] ||= [] choices[name] << value unless value.nil? choices[name] += arrayize_arguments(args) else # If we set the value to nil, that means nothing was set and we # need to set the value to true. We'll find out later if that's # acceptable or not. choices[name] = value.nil? ? true : value end else # If we're here, we have no idea what the passed argument is. Die. if arg =~ /^-/ raise UnknownOption else rest << arg end end end # Okay, we got all the choices. Now go through and run any filters or # whatever on them. choices.each do |name, value| # Check to make sure we have all the required arguments. raise ArgumentRequired if required[name] && value === true # Validate the argument if we need to, against a regexp or a block. if validators[name] if validators[name].is_a?(Regexp) && validators[name] =~ value elsif validators[name].is_a?(Proc) && validators[name].call(value) else raise ArgumentValidationFails end end # Make sure the argument is valid raise InvalidArgument unless value.to_a.all? { |v| hashes['valids'][name].include?(v) } if hashes['valids'][name] # Cast the argument using the method defined in the constant hash. value = value.send(CAST_METHODS[hashes['casts'][name]]) if hashes['casts'].include?(name) # Run the value through a filter and re-set it with the return. value = hashes['filters'][name].call(value) if hashes['filters'].include?(name) # Run an action block if there is one associated. hashes['actions'][name].call(value) if hashes['actions'].include?(name) # Now that we've done all that, re-set the element of the choice hash # with the (potentially) new value. choices[name] = value end # Die if we're missing any required arguments hard_required.each do |name, value| raise ArgumentRequired unless choices[name] end # Home stretch. Go through all the defaults defined and if a choice # does not exist in our choices hash, set its value to the requested # default. hashes['defaults'].each do |name, value| choices[name] = value unless choices[name] end # Return the choices hash and the rest of the args [ choices, rest ] end |