lib/thor/options.rb in mislav-thor-0.9.5 vs lib/thor/options.rb in mislav-thor-0.9.10
- old
+ new
@@ -1,59 +1,57 @@
-# This is a modified version of Daniel Berger's Getopt::ong class,
+# This is a modified version of Daniel Berger's Getopt::Long class,
# licensed under Ruby's license.
-require 'set'
-
class Thor
class Options
class Error < StandardError; end
-
- LONG_RE = /^(--\w+[-\w+]*)$/
- SHORT_RE = /^(-\w)$/
- LONG_EQ_RE = /^(--\w+[-\w+]*)=(.*?)$|(-\w?)=(.*?)$/
- SHORT_SQ_RE = /^-(\w\S+?)$/ # Allow either -x -v or -xv style for single char args
-
- attr_accessor :args
-
- def initialize(args, switches)
- @args = args
- @defaults = {}
-
- switches = switches.map do |names, type|
- case type
- when TrueClass then type = :boolean
- when String
- @defaults[names] = type
- type = :optional
- end
-
- if names.is_a?(String)
- if names =~ LONG_RE
- names = [names, "-" + names[2].chr]
- else
- names = [names]
- end
- end
-
- [names, type]
+
+ # simple Hash with indifferent access
+ class Hash < ::Hash
+ def initialize(hash)
+ super()
+ update hash
end
-
- @valid = switches.map {|s| s.first}.flatten.to_set
- @types = switches.inject({}) do |h, (forms,v)|
- forms.each {|f| h[f] ||= v}
- h
+
+ def [](key)
+ super convert_key(key)
end
- @syns = switches.inject({}) do |h, (forms,_)|
- forms.each {|f| h[f] ||= forms}
- h
+
+ def values_at(*indices)
+ indices.collect { |key| self[convert_key(key)] }
end
+
+ protected
+ def convert_key(key)
+ key.kind_of?(Symbol) ? key.to_s : key
+ end
+
+ # Magic predicates. For instance:
+ # options.force? # => !!options['force']
+ def method_missing(method, *args, &block)
+ method = method.to_s
+ if method =~ /^(\w+)=$/
+ self[$1] = args.first
+ elsif method =~ /^(\w+)\?$/
+ !!self[$1]
+ else
+ self[method]
+ end
+ end
end
- def skip_non_opts
- non_opts = []
- non_opts << pop until looking_at_opt? || @args.empty?
- non_opts
+ NUMERIC = /(\d*\.\d+|\d+)/
+ LONG_RE = /^(--\w+[-\w+]*)$/
+ SHORT_RE = /^(-[a-z])$/i
+ EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
+ SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
+ SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
+
+ attr_reader :leading_non_opts, :trailing_non_opts
+
+ def non_opts
+ leading_non_opts + trailing_non_opts
end
# Takes an array of switches. Each array consists of up to three
# elements that indicate the name and type of switch. Returns a hash
# containing each switch name, minus the '-', as a key. The value
@@ -63,94 +61,207 @@
# The long switch _must_ be provided. The short switch defaults to the
# first letter of the short switch. The default type is :boolean.
#
# Example:
#
- # opts = Thor::Options.new(args,
- # "--debug" => true,
- # ["--verbose", "-v"] => true,
- # ["--level", "-l"] => :numeric
- # ).getopts
+ # opts = Thor::Options.new(
+ # "--debug" => true,
+ # ["--verbose", "-v"] => true,
+ # ["--level", "-l"] => :numeric
+ # ).parse(args)
#
- def getopts(check_required = true)
- hash = @defaults.dup
+ def initialize(switches)
+ @defaults = {}
+ @shorts = {}
+
+ @leading_non_opts, @trailing_non_opts = [], []
- while looking_at_opt?
- case pop
+ @switches = switches.inject({}) do |mem, (name, type)|
+ if name.is_a?(Array)
+ name, *shorts = name
+ else
+ name = name.to_s
+ shorts = []
+ end
+ # we need both nice and dasherized form of switch name
+ if name.index('-') == 0
+ nice_name = undasherize name
+ else
+ nice_name = name
+ name = dasherize name
+ end
+ # if there are no shortcuts specified, generate one using the first character
+ shorts << "-" + nice_name[0,1] if shorts.empty? and nice_name.length > 1
+ shorts.each { |short| @shorts[short] = name }
+
+ # normalize type
+ case type
+ when TrueClass
+ @defaults[nice_name] = true
+ type = :boolean
+ when FalseClass
+ @defaults[nice_name] = false
+ type = :boolean
+ when String
+ @defaults[nice_name] = type
+ type = :optional
+ when Numeric
+ @defaults[nice_name] = type
+ type = :numeric
+ end
+
+ mem[name] = type
+ mem
+ end
+
+ # remove shortcuts that happen to coincide with any of the main switches
+ @shorts.keys.each do |short|
+ @shorts.delete(short) if @switches.key?(short)
+ end
+ end
+
+ def parse(args, skip_leading_non_opts = true)
+ @args = args
+ # start with Thor::Options::Hash pre-filled with defaults
+ hash = Hash.new @defaults
+
+ @leading_non_opts = []
+ if skip_leading_non_opts
+ @leading_non_opts << shift until current_is_option? || @args.empty?
+ end
+
+ while current_is_option?
+ case shift
when SHORT_SQ_RE
- push(*$1.split("").map {|s| s = "-#{s}"})
+ unshift $1.split('').map { |f| "-#{f}" }
next
- when LONG_EQ_RE
- push($1, $2)
- next
+ when EQ_RE, SHORT_NUM
+ unshift $2
+ switch = $1
when LONG_RE, SHORT_RE
switch = $1
end
-
- case @types[switch]
+
+ switch = normalize_switch(switch)
+ nice_name = undasherize(switch)
+ type = switch_type(switch)
+
+ case type
when :required
- raise Error, "no value provided for required argument '#{switch}'" if peek.nil?
- raise Error, "cannot pass switch '#{peek}' as an argument" if @valid.include?(peek)
- hash[switch] = pop
- when :boolean
- hash[switch] = true
+ assert_value!(switch)
+ raise Error, "cannot pass switch '#{peek}' as an argument" if valid?(peek)
+ hash[nice_name] = shift
when :optional
- # For optional arguments, there may be an argument. If so, it
- # cannot be another switch. If not, it is set to true.
- hash[switch] = @valid.include?(peek) || peek.nil? || pop
+ hash[nice_name] = peek.nil? || valid?(peek) || shift
+ when :boolean
+ if !@switches.key?(switch) && nice_name =~ /^no-(\w+)$/
+ hash[$1] = false
+ else
+ hash[nice_name] = true
+ end
+
+ when :numeric
+ assert_value!(switch)
+ unless peek =~ NUMERIC and $& == peek
+ raise Error, "expected numeric value for '#{switch}'; got #{peek.inspect}"
+ end
+ hash[nice_name] = $&.index('.') ? shift.to_f : shift.to_i
end
end
+
+ @trailing_non_opts = @args
- hash = normalize_hash hash
- check_required_args hash if check_required
+ check_required! hash
+ hash.freeze
hash
end
-
- def check_required_args(hash)
- @types.select {|k,v| v == :required}.map {|k,v| @syns[k]}.uniq.each do |syns|
- unless syns.map {|s| s.gsub(/^-+/, '')}.any? {|s| hash[s]}
- raise Error, "no value provided for required argument '#{syns.first}'"
+
+ def formatted_usage
+ return "" if @switches.empty?
+ @switches.map do |opt, type|
+ case type
+ when :boolean
+ "[#{opt}]"
+ when :required
+ opt + "=" + opt.gsub(/\-/, "").upcase
+ else
+ sample = @defaults[undasherize(opt)]
+ sample ||= case type
+ when :optional then undasherize(opt).gsub(/\-/, "_").upcase
+ when :numeric then "N"
+ end
+ "[" + opt + "=" + sample.to_s + "]"
end
- end
+ end.join(" ")
end
+
+ alias :to_s :formatted_usage
private
-
+
+ def assert_value!(switch)
+ raise Error, "no value provided for argument '#{switch}'" if peek.nil?
+ end
+
+ def undasherize(str)
+ str.sub(/^-{1,2}/, '')
+ end
+
+ def dasherize(str)
+ (str.length > 1 ? "--" : "-") + str
+ end
+
def peek
@args.first
end
- def pop
- arg = peek
- @args = @args[1..-1] || []
- arg
+ def shift
+ @args.shift
end
- def push(*args)
- @args = args + @args
+ def unshift(arg)
+ unless arg.kind_of?(Array)
+ @args.unshift(arg)
+ else
+ @args = arg + @args
+ end
end
+
+ def valid?(arg)
+ if arg.to_s =~ /^--no-(\w+)$/
+ @switches.key?(arg) or (@switches["--#{$1}"] == :boolean)
+ else
+ @switches.key?(arg) or @shorts.key?(arg)
+ end
+ end
- def looking_at_opt?
+ def current_is_option?
case peek
- when LONG_RE, SHORT_RE, LONG_EQ_RE
- @valid.include? $1
+ when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
+ valid?($1)
when SHORT_SQ_RE
- $1.split("").any? {|f| @valid.include? "-#{f}"}
+ $1.split('').any? { |f| valid?("-#{f}") }
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
- def normalize_hash(hash)
- hash.map do |switch, val|
- @syns[switch].map {|key| [key, val]}
- end.inject([]) {|a, v| a + v}.map do |key, value|
- [key.sub(/^-+/, ''), value]
- end.inject({}) {|h, (k,v)| h[k] = v; h[k.to_sym] = v; h}
+
+ def normalize_switch(switch)
+ @shorts.key?(switch) ? @shorts[switch] : switch
end
-
+
+ def switch_type(switch)
+ if switch =~ /^--no-(\w+)$/
+ @switches[switch] || @switches["--#{$1}"]
+ else
+ @switches[switch]
+ end
+ end
+
+ def check_required!(hash)
+ for name, type in @switches
+ if type == :required and !hash[undasherize(name)]
+ raise Error, "no value provided for required argument '#{name}'"
+ end
+ end
+ end
+
end
end