Permalink
Browse files

BACK INCOMPAT: refactor Thor::Options

Values are no longer available when using a switch shortcut form as key.
Example:
  definition: ['--force', '-f'] => :boolean

  parsing "--force" results in {'force' => true}
  parsing "-f" results in {'force' => true}
  options['f'] is always nil!
  • Loading branch information...
1 parent c0cb7aa commit 72d4aa1157db75817b5a52f4f5a99f34b11771a4 @mislav mislav committed Aug 26, 2008
Showing with 250 additions and 244 deletions.
  1. +11 −12 lib/thor.rb
  2. +136 −101 lib/thor/options.rb
  3. +3 −5 lib/thor/task.rb
  4. +96 −119 spec/options_spec.rb
  5. +4 −7 spec/thor_spec.rb
View
@@ -61,9 +61,9 @@ def self.maxima
end
def self.start(args = ARGV)
- options = Thor::Options.new(args, self.opts)
- opts = options.getopts
- args = options.args
+ options = Thor::Options.new(self.opts)
+ opts = options.parse(args, false)
+ args = options.trailing_non_opts
meth = args.first
meth = @map[meth].to_s if @map && @map[meth]
@@ -126,15 +126,14 @@ def help(task = nil)
puts task.formatted_usage(namespace)
puts task.description
- return
- end
-
- puts "Options"
- puts "-------"
- self.class.tasks.each do |_, task|
- format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s"
- print format % ("#{task.formatted_usage}")
- puts task.description.split("\n").first
+ else
+ puts "Options"
+ puts "-------"
+ self.class.tasks.each do |_, task|
+ format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s"
+ print format % ("#{task.formatted_usage}")
+ puts task.description.split("\n").first
+ end
end
end
View
@@ -1,57 +1,41 @@
-# 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]
+
+ # read-only Hash with indifferent access
+ class Hash < ::Hash
+ def initialize(hash)
+ super()
+ update hash
+ freeze
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
end
- def skip_non_opts
- non_opts = []
- non_opts << pop until looking_at_opt? || @args.empty?
- non_opts
+ LONG_RE = /^--(\w+[-\w+]*)$/
+ SHORT_RE = /^-(\w)$/
+ EQ_RE = /^(?:--(\w+[-\w+]*)|-(\w))=(.*)$/
+ SHORT_SQ_RE = /^-(\w{2,})$/ # Allow either -x -v or -xv style for single char args
+
+ 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
@@ -65,92 +49,143 @@ def skip_non_opts
#
# 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)
+ def initialize(switches)
+ @defaults = {}
+ @shorts = {}
+
+ @switches = switches.inject({}) do |mem, (name, type)|
+ if name.is_a?(Array)
+ name, *shorts = name
+ shorts = shorts.map { |short| undash_leading short }
+ else
+ shorts = []
+ end
+ name = undash_leading name
+ # if there are no shortcuts specified, generate one using the first character
+ shorts << name[0,1] if shorts.empty? and name.length > 1
+ shorts.each { |short| @shorts[short] = name }
+
+ # normalize type
+ case type
+ when TrueClass then type = :boolean
+ when String
+ @defaults[name] = type
+ type = :optional
+ end
+
+ mem[name] = type
+ mem
+ end
+
+ # remove shortcuts that happen to coincide with one 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
hash = @defaults.dup
+
+ @leading_non_opts = []
+ if skip_leading_non_opts
+ @leading_non_opts << shift until current_is_option? || @args.empty?
+ end
- while looking_at_opt?
- case pop
+ while current_is_option?
+ case shift
when SHORT_SQ_RE
- push(*$1.split("").map {|s| s = "-#{s}"})
- next
- when LONG_EQ_RE
- push($1, $2)
+ unshift $1.split('').map { |f| "-#{f}" }
next
+ when EQ_RE
+ unshift $3
+ switch = $1 || $2
when LONG_RE, SHORT_RE
switch = $1
end
-
- case @types[switch]
+
+ switch = normalize_switch(switch)
+
+ case switch_type(switch)
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
+ raise Error, "no value provided for argument '#{switch}'" if peek.nil?
+ raise Error, "cannot pass switch '#{peek}' as an argument" if valid?(peek, true)
+ hash[switch] = shift
+ when :optional
+ hash[switch] = peek.nil? || valid?(peek, true) || shift
when :boolean
hash[switch] = true
- 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
end
end
+
+ @trailing_non_opts = @args
- hash = normalize_hash hash
- check_required_args hash if check_required
- 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}'"
- end
- end
+ check_required! hash
+ # convert to Thor::Options::Hash before returning
+ Hash.new(hash)
end
private
-
+
+ def undash_leading(str)
+ str.sub(/^-{1,2}/, '')
+ 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, raw = false)
+ raise ArgumentError, "string expected, #{arg.inspect} given" unless arg
+ if raw
+ return false unless LONG_RE =~ arg || SHORT_RE =~ arg
+ arg = $1
+ end
+ @switches.key?(arg) or @shorts.key?(arg)
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
+ valid?($1 || $2)
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)
+ @switches[switch]
+ end
+
+ def check_required!(hash)
+ for name, type in @switches
+ if type == :required and !hash[name]
+ raise Error, "no value provided for required argument '#{name}'"
+ end
+ end
+ end
+
end
end
View
@@ -69,11 +69,9 @@ def formatted_usage(namespace = false)
def parse_args(args)
return [args, {}] unless opts
- options = Thor::Options.new(args, opts)
- hash = options.getopts(false)
- list = options.skip_non_opts
- hash.update options.getopts(false)
- options.check_required_args hash
+ options = Thor::Options.new(opts)
+ hash = options.parse(args)
+ list = options.non_opts
[list, hash]
end
end
Oops, something went wrong.

0 comments on commit 72d4aa1

Please sign in to comment.