1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
|
class Thor
class Arguments #:nodoc:
NUMERIC = /[-+]?(\d*\.\d+|\d+)/
# Receives an array of args and returns two arrays, one with arguments
# and one with switches.
#
def self.split(args)
arguments = []
args.each do |item|
break if item.is_a?(String) && item =~ /^-/
arguments << item
end
[arguments, args[Range.new(arguments.size, -1)]]
end
def self.parse(*args)
to_parse = args.pop
new(*args).parse(to_parse)
end
# Takes an array of Thor::Argument objects.
#
def initialize(arguments = [])
@assigns = {}
@non_assigned_required = []
@switches = arguments
arguments.each do |argument|
if !argument.default.nil?
@assigns[argument.human_name] = argument.default.dup
elsif argument.required?
@non_assigned_required << argument
end
end
end
def parse(args)
@pile = args.dup
@switches.each do |argument|
break unless peek
@non_assigned_required.delete(argument)
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
end
check_requirement!
@assigns
end
def remaining
@pile
end
private
def no_or_skip?(arg)
arg =~ /^--(no|skip)-([-\w]+)$/
$2
end
def last?
@pile.empty?
end
def peek
@pile.first
end
def shift
@pile.shift
end
def unshift(arg)
if arg.is_a?(Array)
@pile = arg + @pile
else
@pile.unshift(arg)
end
end
def current_is_value?
peek && peek.to_s !~ /^-{1,2}\S+/
end
# Runs through the argument array getting strings that contains ":" and
# mark it as a hash:
#
# [ "name:string", "age:integer" ]
#
# Becomes:
#
# { "name" => "string", "age" => "integer" }
#
def parse_hash(name)
return shift if peek.is_a?(Hash)
hash = {}
while current_is_value? && peek.include?(":")
key, value = shift.split(":", 2)
raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key
hash[key] = value
end
hash
end
# Runs through the argument array getting all strings until no string is
# found or a switch is found.
#
# ["a", "b", "c"]
#
# And returns it as an array:
#
# ["a", "b", "c"]
#
def parse_array(name)
return shift if peek.is_a?(Array)
array = []
while current_is_value?
value = shift
if !value.empty?
validate_enum_value!(name, value, "Expected all values of '%s' to be one of %s; got %s")
end
array << value
end
array
end
# Check if the peek is numeric format and return a Float or Integer.
# Check if the peek is included in enum if enum is provided.
# Otherwise raises an error.
#
def parse_numeric(name)
return shift if peek.is_a?(Numeric)
unless peek =~ NUMERIC && $& == peek
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
end
value = $&.index(".") ? shift.to_f : shift.to_i
validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s")
value
end
# Parse string:
# for --string-arg, just return the current value in the pile
# for --no-string-arg, nil
# Check if the peek is included in enum if enum is provided. Otherwise raises an error.
#
def parse_string(name)
if no_or_skip?(name)
nil
else
value = shift
validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s")
value
end
end
# Raises an error if the switch is an enum and the values aren't included on it.
#
def validate_enum_value!(name, value, message)
return unless @switches.is_a?(Hash)
switch = @switches[name]
return unless switch
if switch.enum && !switch.enum.include?(value)
raise MalformattedArgumentError, message % [name, switch.enum_to_s, value]
end
end
# Raises an error if @non_assigned_required array is not empty.
#
def check_requirement!
return if @non_assigned_required.empty?
names = @non_assigned_required.map do |o|
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
end.join("', '")
class_name = self.class.name.split("::").last.downcase
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
end
end
end
|