thor 0.13.1 → 0.13.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ == 0.13, released 2010-02-03
2
+
3
+ * Several bug fixes
4
+ * Decoupled Thor::Group and Thor, so it's easier to vendor
5
+ * Added check_unknown_options! in case you want error messages to be raised in valid switches.
6
+
1
7
  == 0.12, released 2010-01-02
2
8
 
3
9
  * Methods generated by attr_* are automatically not marked as tasks
data/Thorfile CHANGED
@@ -64,6 +64,6 @@ class Default < Thor
64
64
 
65
65
  Jeweler::GemcutterTasks.new
66
66
  rescue LoadError
67
- puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
67
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
68
68
  end
69
69
  end
@@ -120,8 +120,8 @@ class Thor
120
120
  # script = MyScript.new(args, options, config)
121
121
  # script.invoke(:task, first_arg, second_arg, third_arg)
122
122
  #
123
- def start(given_args=ARGV, config={})
124
- super do
123
+ def start(original_args=ARGV, config={})
124
+ super do |given_args|
125
125
  meth = normalize_task_name(given_args.shift)
126
126
  task = all_tasks[meth]
127
127
 
@@ -145,8 +145,9 @@ class Thor
145
145
  # task_name<String>
146
146
  #
147
147
  def task_help(shell, task_name)
148
- task = all_tasks[task_name]
149
- raise UndefinedTaskError, "task '#{task_name}' could not be found in namespace '#{self.namespace}'" unless task
148
+ meth = normalize_task_name(task_name)
149
+ task = all_tasks[meth]
150
+ handle_no_task_error(meth) unless task
150
151
 
151
152
  shell.say "Usage:"
152
153
  shell.say " #{banner(task)}"
@@ -183,6 +184,10 @@ class Thor
183
184
  end
184
185
  end
185
186
 
187
+ def handle_argument_error(task, error) #:nodoc:
188
+ raise InvocationError, "#{task.name.inspect} was called incorrectly. Call as #{task.formatted_usage(self, banner_base == "thor").inspect}."
189
+ end
190
+
186
191
  protected
187
192
 
188
193
  # The banner for this class. You can customize it if you are invoking the
@@ -191,8 +196,7 @@ class Thor
191
196
  # the namespace should be displayed as arguments.
192
197
  #
193
198
  def banner(task)
194
- base = $thor_runner ? "thor" : File.basename($0.split(" ").first)
195
- "#{base} #{task.formatted_usage(self, base == "thor")}"
199
+ "#{banner_base} #{task.formatted_usage(self, banner_base == "thor")}"
196
200
  end
197
201
 
198
202
  def baseclass #:nodoc:
@@ -38,9 +38,8 @@ class Thor
38
38
  # config<Hash>:: Configuration for this Thor class.
39
39
  #
40
40
  def initialize(args=[], options={}, config={})
41
- Thor::Arguments.parse(self.class.arguments, args).each do |key, value|
42
- send("#{key}=", value)
43
- end
41
+ args = Thor::Arguments.parse(self.class.arguments, args)
42
+ args.each { |key, value| send("#{key}=", value) }
44
43
 
45
44
  parse_options = self.class.class_options
46
45
 
@@ -52,9 +51,9 @@ class Thor
52
51
  array_options, hash_options = [], options
53
52
  end
54
53
 
55
- options = Thor::Options.parse(parse_options, array_options)
56
- self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).merge!(hash_options)
57
- self.options.freeze
54
+ opts = Thor::Options.new(parse_options, hash_options)
55
+ self.options = opts.parse(array_options)
56
+ opts.check_unknown! if self.class.check_unknown_options?
58
57
  end
59
58
 
60
59
  class << self
@@ -109,6 +108,16 @@ class Thor
109
108
  no_tasks { super }
110
109
  end
111
110
 
111
+ # If you want to raise an error for unknown options, call check_unknown_options!
112
+ # This is disabled by default to allow dynamic invocations.
113
+ def check_unknown_options!
114
+ @check_unknown_options = true
115
+ end
116
+
117
+ def check_unknown_options? #:nodoc:
118
+ @check_unknown_options || false
119
+ end
120
+
112
121
  # Adds an argument to the class and creates an attr_accessor for it.
113
122
  #
114
123
  # Arguments are different from options in several aspects. The first one
@@ -355,7 +364,7 @@ class Thor
355
364
  def namespace(name=nil)
356
365
  case name
357
366
  when nil
358
- @namespace ||= Thor::Util.namespace_from_thor_class(self, false)
367
+ @namespace ||= Thor::Util.namespace_from_thor_class(self)
359
368
  else
360
369
  @namespace = name.to_s
361
370
  end
@@ -366,14 +375,18 @@ class Thor
366
375
  def start(given_args=ARGV, config={})
367
376
  self.debugging = given_args.include?("--debug")
368
377
  config[:shell] ||= Thor::Base.shell.new
369
- yield
378
+ yield(given_args.dup)
370
379
  rescue Thor::Error => e
371
- if debugging
372
- raise e
380
+ debugging ? (raise e) : config[:shell].error(e.message)
381
+ exit(1) if exit_on_failure?
382
+ end
383
+
384
+ def handle_no_task_error(task) #:nodoc:
385
+ if self.banner_base == "thor"
386
+ raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
373
387
  else
374
- config[:shell].error e.message
388
+ raise UndefinedTaskError, "Could not find task #{task.inspect}."
375
389
  end
376
- exit(1) if exit_on_failure?
377
390
  end
378
391
 
379
392
  protected
@@ -419,7 +432,6 @@ class Thor
419
432
  end
420
433
 
421
434
  # Raises an error if the word given is a Thor reserved word.
422
- #
423
435
  def is_thor_reserved_word?(word, type) #:nodoc:
424
436
  return false unless THOR_RESERVED_WORDS.include?(word.to_s)
425
437
  raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
@@ -430,7 +442,6 @@ class Thor
430
442
  # ==== Parameters
431
443
  # name<Symbol>:: The name of the argument.
432
444
  # options<Hash>:: Described in both class_option and method_option.
433
- #
434
445
  def build_option(name, options, scope) #:nodoc:
435
446
  scope[name] = Thor::Option.new(name, options[:desc], options[:required],
436
447
  options[:type], options[:default], options[:banner],
@@ -444,7 +455,6 @@ class Thor
444
455
  #
445
456
  # ==== Parameters
446
457
  # Hash[Symbol => Object]
447
- #
448
458
  def build_options(options, scope) #:nodoc:
449
459
  options.each do |key, value|
450
460
  scope[key] = Thor::Option.parse(key, value)
@@ -454,7 +464,6 @@ class Thor
454
464
  # Finds a task with the given name. If the task belongs to the current
455
465
  # class, just return it, otherwise dup it and add the fresh copy to the
456
466
  # current task hash.
457
- #
458
467
  def find_and_refresh_task(name) #:nodoc:
459
468
  task = if task = tasks[name.to_s]
460
469
  task
@@ -467,14 +476,12 @@ class Thor
467
476
 
468
477
  # Everytime someone inherits from a Thor class, register the klass
469
478
  # and file into baseclass.
470
- #
471
479
  def inherited(klass)
472
480
  Thor::Base.register_klass_file(klass)
473
481
  end
474
482
 
475
483
  # Fire this callback whenever a method is added. Added methods are
476
484
  # tracked as tasks by invoking the create_task method.
477
- #
478
485
  def method_added(meth)
479
486
  meth = meth.to_s
480
487
 
@@ -495,7 +502,6 @@ class Thor
495
502
 
496
503
  # Retrieves a value from superclass. If it reaches the baseclass,
497
504
  # returns default.
498
- #
499
505
  def from_superclass(method, default=nil)
500
506
  if self == baseclass || !superclass.respond_to?(method, true)
501
507
  default
@@ -506,11 +512,15 @@ class Thor
506
512
  end
507
513
 
508
514
  # A flag that makes the process exit with status 1 if any error happens.
509
- #
510
515
  def exit_on_failure?
511
516
  false
512
517
  end
513
518
 
519
+ # Returns the base for banner.
520
+ def banner_base
521
+ @banner_base ||= $thor_runner ? "thor" : File.basename($0.split(" ").first)
522
+ end
523
+
514
524
  # SIGNATURE: Sets the baseclass. This is where the superclass lookup
515
525
  # finishes.
516
526
  def baseclass #:nodoc:
@@ -19,6 +19,9 @@ class Thor
19
19
  class InvocationError < Error
20
20
  end
21
21
 
22
+ class UnknownArgumentError < Error
23
+ end
24
+
22
25
  class RequiredArgumentMissingError < InvocationError
23
26
  end
24
27
 
@@ -25,8 +25,8 @@ class Thor::Group
25
25
  # Start works differently in Thor::Group, it simply invokes all tasks
26
26
  # inside the class.
27
27
  #
28
- def start(given_args=ARGV, config={})
29
- super do
28
+ def start(original_args=ARGV, config={})
29
+ super do |given_args|
30
30
  if Thor::HELP_MAPPINGS.include?(given_args.first)
31
31
  help(config[:shell])
32
32
  return
@@ -219,14 +219,16 @@ class Thor::Group
219
219
  [item]
220
220
  end
221
221
 
222
+ def handle_argument_error(task, error) #:nodoc:
223
+ raise error, "#{task.name.inspect} was called incorrectly. Are you sure it has arity equals to 0?"
224
+ end
225
+
222
226
  protected
223
227
 
224
228
  # The banner for this class. You can customize it if you are invoking the
225
229
  # thor class by another ways which is not the Thor::Runner.
226
- #
227
230
  def banner
228
- base = $thor_runner ? "thor" : File.basename($0.split(" ").first)
229
- "#{base} #{self_task.formatted_usage(self, false)}"
231
+ "#{banner_base} #{self_task.formatted_usage(self, false)}"
230
232
  end
231
233
 
232
234
  # Represents the whole class as a task.
@@ -5,21 +5,20 @@ class Thor
5
5
  end
6
6
 
7
7
  module ClassMethods
8
- # Prepare for class methods invocations. This method must return a klass to
9
- # have the invoked class options showed in help messages in generators.
10
- #
8
+ # This method is responsible for receiving a name and find the proper
9
+ # class and task for it. The key is an optional parameter which is
10
+ # available only in class methods invocations (i.e. in Thor::Group).
11
11
  def prepare_for_invocation(key, name) #:nodoc:
12
12
  case name
13
13
  when Symbol, String
14
- Thor::Util.namespace_to_thor_class_and_task(name.to_s, false)
14
+ Thor::Util.find_class_and_task_by_namespace(name.to_s)
15
15
  else
16
16
  name
17
17
  end
18
18
  end
19
19
  end
20
20
 
21
- # Make initializer aware of invocations and the initializer proc.
22
- #
21
+ # Make initializer aware of invocations and the initialization args.
23
22
  def initialize(args=[], options={}, config={}, &block) #:nodoc:
24
23
  @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
25
24
  @_initializer = [ args, options, config ]
@@ -34,6 +33,8 @@ class Thor
34
33
  # the task to be invoked, if none is given, the same values used to
35
34
  # initialize the invoker are used to initialize the invoked.
36
35
  #
36
+ # When no name is given, it will invoke the default task of the current class.
37
+ #
37
38
  # ==== Examples
38
39
  #
39
40
  # class A < Thor
@@ -92,9 +93,9 @@ class Thor
92
93
  #
93
94
  # invoke Rspec::RR, [], :style => :foo
94
95
  #
95
- def invoke(name=nil, task=nil, args=nil, opts=nil, config=nil)
96
- task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array)
97
- args, opts, config = nil, args, opts if args.is_a?(Hash)
96
+ def invoke(name=nil, *args)
97
+ args.unshift(nil) if Array === args.first || NilClass === args.first
98
+ task, args, opts, config = args
98
99
 
99
100
  object, task = _prepare_for_invocation(name, task)
100
101
  klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
@@ -121,15 +122,13 @@ class Thor
121
122
  protected
122
123
 
123
124
  # Configuration values that are shared between invocations.
124
- #
125
125
  def _shared_configuration #:nodoc:
126
126
  { :invocations => @_invocations }
127
127
  end
128
128
 
129
- # Prepare for invocation in the instance level. In this case, we have to
130
- # take into account that a just a task name from the current class was
131
- # given or even a Thor::Task object.
132
- #
129
+ # This method can receive several different types of arguments and it's then
130
+ # responsible to normalize them by returning the object where the task should
131
+ # be invoked and a Thor::Task object.
133
132
  def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
134
133
  if name.is_a?(Thor::Task)
135
134
  task = name
@@ -147,18 +146,16 @@ class Thor
147
146
 
148
147
  # Check if the object given is a Thor class object and get a task object
149
148
  # for it.
150
- #
151
149
  def _validate_task(object, task) #:nodoc:
152
150
  klass = object.is_a?(Class) ? object : object.class
153
151
  raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
154
152
 
155
- task ||= klass.default_task if klass <= Thor
153
+ task ||= klass.default_task if klass.respond_to?(:default_task)
156
154
  task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task)
157
155
  task
158
156
  end
159
157
 
160
158
  # Initialize klass using values stored in the @_initializer.
161
- #
162
159
  def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
163
160
  if object.is_a?(Class)
164
161
  klass = object
@@ -16,8 +16,9 @@ class Thor
16
16
  return arguments, args[Range.new(arguments.size, -1)]
17
17
  end
18
18
 
19
- def self.parse(base, args)
20
- new(base).parse(args)
19
+ def self.parse(*args)
20
+ to_parse = args.pop
21
+ new(*args).parse(to_parse)
21
22
  end
22
23
 
23
24
  # Takes an array of Thor::Argument objects.
@@ -116,7 +117,7 @@ class Thor
116
117
  return shift if peek.is_a?(Numeric)
117
118
 
118
119
  unless peek =~ NUMERIC && $& == peek
119
- raise MalformattedArgumentError, "expected numeric value for '#{name}'; got #{peek.inspect}"
120
+ raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
120
121
  end
121
122
 
122
123
  $&.index('.') ? shift.to_f : shift.to_i
@@ -137,7 +138,7 @@ class Thor
137
138
  end.join("', '")
138
139
 
139
140
  class_name = self.class.name.split('::').last.downcase
140
- raise RequiredArgumentMissingError, "no value provided for required #{class_name} '#{names}'"
141
+ raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
141
142
  end
142
143
  end
143
144
 
@@ -55,10 +55,6 @@ class Thor
55
55
  value
56
56
  elsif required = (value == :required)
57
57
  :string
58
- elsif value == :optional
59
- # TODO Remove this warning in the future.
60
- warn "Optional type is deprecated. Choose :boolean or :string instead. Assumed to be :boolean."
61
- :boolean
62
58
  end
63
59
  when TrueClass, FalseClass
64
60
  :boolean
@@ -10,7 +10,6 @@ class Thor
10
10
  SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
11
11
 
12
12
  # Receives a hash and makes it switches.
13
- #
14
13
  def self.to_switches(options)
15
14
  options.map do |key, value|
16
15
  case value
@@ -28,12 +27,18 @@ class Thor
28
27
  end.join(" ")
29
28
  end
30
29
 
31
- # Takes a hash of Thor::Option objects.
32
- #
33
- def initialize(options={})
34
- options = options.values
30
+ # Takes a hash of Thor::Option and a hash with defaults.
31
+ def initialize(hash_options={}, defaults={})
32
+ options = hash_options.values
35
33
  super(options)
36
- @shorts, @switches = {}, {}
34
+
35
+ # Add defaults
36
+ defaults.each do |key, value|
37
+ @assigns[key.to_s] = value
38
+ @non_assigned_required.delete(hash_options[key])
39
+ end
40
+
41
+ @shorts, @switches, @unknown = {}, {}, []
37
42
 
38
43
  options.each do |option|
39
44
  @switches[option.switch_name] = option
@@ -61,16 +66,24 @@ class Thor
61
66
  end
62
67
 
63
68
  switch = normalize_switch(switch)
64
- next unless option = switch_option(switch)
65
-
69
+ option = switch_option(switch)
66
70
  @assigns[option.human_name] = parse_peek(switch, option)
71
+ elsif peek =~ /^\-/
72
+ @unknown << shift
67
73
  else
68
74
  shift
69
75
  end
70
76
  end
71
77
 
72
78
  check_requirement!
73
- @assigns
79
+
80
+ assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
81
+ assigns.freeze
82
+ assigns
83
+ end
84
+
85
+ def check_unknown!
86
+ raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
74
87
  end
75
88
 
76
89
  protected
@@ -130,7 +143,7 @@ class Thor
130
143
  elsif option.string? && !option.required?
131
144
  return option.human_name # Return the option name
132
145
  else
133
- raise MalformattedArgumentError, "no value provided for option '#{switch}'"
146
+ raise MalformattedArgumentError, "No value provided for option '#{switch}'"
134
147
  end
135
148
  end
136
149
 
@@ -16,8 +16,7 @@ class Thor::Runner < Thor #:nodoc:
16
16
  def help(meth=nil)
17
17
  if meth && !self.respond_to?(meth)
18
18
  initialize_thorfiles(meth)
19
- klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
20
- # Send mapping -h because it works with Thor::Group too
19
+ klass, task = Thor::Util.find_class_and_task_by_namespace!(meth)
21
20
  klass.start(["-h", task].compact, :shell => self.shell)
22
21
  else
23
22
  super
@@ -30,9 +29,9 @@ class Thor::Runner < Thor #:nodoc:
30
29
  def method_missing(meth, *args)
31
30
  meth = meth.to_s
32
31
  initialize_thorfiles(meth)
33
- klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
32
+ klass, task = Thor::Util.find_class_and_task_by_namespace!(meth)
34
33
  args.unshift(task) if task
35
- klass.start(args, :shell => shell)
34
+ klass.start(args, :shell => self.shell)
36
35
  end
37
36
 
38
37
  desc "install NAME", "Install an optionally named Thor file into your system tasks"
@@ -9,10 +9,11 @@ class Thor
9
9
  end
10
10
 
11
11
  def run(instance, args=[])
12
- unless (instance.methods & [name.to_s, name.to_sym]).empty?
13
- raise Error, "could not find Thor class or task '#{name}'"
12
+ if (instance.methods & [name.to_s, name.to_sym]).empty?
13
+ super
14
+ else
15
+ instance.class.handle_no_task_error(name)
14
16
  end
15
- super
16
17
  end
17
18
  end
18
19
 
@@ -28,14 +29,14 @@ class Thor
28
29
  # By default, a task invokes a method in the thor class. You can change this
29
30
  # implementation to create custom tasks.
30
31
  def run(instance, args=[])
31
- raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
32
- instance.send(name, *args)
32
+ public_method?(instance) ?
33
+ instance.send(name, *args) : instance.class.handle_no_task_error(name)
33
34
  rescue ArgumentError => e
34
- raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
35
- parse_argument_error(instance, e, caller)
35
+ handle_argument_error?(instance, e, caller) ?
36
+ instance.class.handle_argument_error(self, e) : (raise e)
36
37
  rescue NoMethodError => e
37
- raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
38
- parse_no_method_error(instance, e)
38
+ handle_no_method_error?(instance, e, caller) ?
39
+ instance.class.handle_no_task_error(name) : (raise e)
39
40
  end
40
41
 
41
42
  # Returns the formatted usage by injecting given required arguments
@@ -68,6 +69,10 @@ class Thor
68
69
 
69
70
  protected
70
71
 
72
+ def not_debugging?(instance)
73
+ !(instance.class.respond_to?(:debugging) && instance.class.debugging)
74
+ end
75
+
71
76
  def required_options
72
77
  @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
73
78
  end
@@ -83,28 +88,14 @@ class Thor
83
88
  saned -= caller
84
89
  end
85
90
 
86
- def parse_argument_error(instance, e, caller) #:nodoc:
87
- backtrace = sans_backtrace(e.backtrace, caller)
88
-
89
- if backtrace.empty? && e.message =~ /wrong number of arguments/
90
- if instance.is_a?(Thor::Group)
91
- raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
92
- else
93
- raise InvocationError, "'#{name}' was called incorrectly. Call as " <<
94
- "'#{formatted_usage(instance.class)}'"
95
- end
96
- else
97
- raise e
98
- end
91
+ def handle_argument_error?(instance, error, caller)
92
+ not_debugging?(instance) && error.message =~ /wrong number of arguments/ &&
93
+ sans_backtrace(error.backtrace, caller).empty?
99
94
  end
100
95
 
101
- def parse_no_method_error(instance, e) #:nodoc:
102
- if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
103
- raise UndefinedTaskError, "The #{instance.class.namespace} namespace " <<
104
- "doesn't have a '#{name}' task"
105
- else
106
- raise e
107
- end
96
+ def handle_no_method_error?(instance, error, caller)
97
+ not_debugging?(instance) &&
98
+ error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
108
99
  end
109
100
 
110
101
  end
@@ -23,10 +23,7 @@ class Thor
23
23
  #
24
24
  def self.find_by_namespace(namespace)
25
25
  namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
26
-
27
- Thor::Base.subclasses.find do |klass|
28
- klass.namespace == namespace
29
- end
26
+ Thor::Base.subclasses.find { |klass| klass.namespace == namespace }
30
27
  end
31
28
 
32
29
  # Receives a constant and converts it to a Thor namespace. Since Thor tasks
@@ -43,10 +40,9 @@ class Thor
43
40
  # ==== Returns
44
41
  # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
45
42
  #
46
- def self.namespace_from_thor_class(constant, remove_default=true)
43
+ def self.namespace_from_thor_class(constant)
47
44
  constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
48
45
  constant = snake_case(constant).squeeze(":")
49
- constant.gsub!(/^default/, '') if remove_default
50
46
  constant
51
47
  end
52
48
 
@@ -132,13 +128,7 @@ class Thor
132
128
  # ==== Parameters
133
129
  # namespace<String>
134
130
  #
135
- # ==== Errors
136
- # Thor::Error:: raised if the namespace cannot be found.
137
- #
138
- # Thor::Error:: raised if the namespace evals to a class which does not
139
- # inherit from Thor or Thor::Group.
140
- #
141
- def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true)
131
+ def self.find_class_and_task_by_namespace(namespace)
142
132
  if namespace.include?(?:)
143
133
  pieces = namespace.split(":")
144
134
  task = pieces.pop
@@ -149,7 +139,14 @@ class Thor
149
139
  klass, task = Thor::Util.find_by_namespace(namespace), nil
150
140
  end
151
141
 
152
- raise Error, "could not find Thor class or task '#{namespace}'" if raise_if_nil && klass.nil?
142
+ return klass, task
143
+ end
144
+
145
+ # The same as namespace_to_thor_class_and_task!, but raises an error if a klass
146
+ # could not be found.
147
+ def self.find_class_and_task_by_namespace!(namespace)
148
+ klass, task = find_class_and_task_by_namespace(namespace)
149
+ raise Error, "Could not find namespace or task #{namespace.inspect}." unless klass
153
150
  return klass, task
154
151
  end
155
152
 
@@ -1,3 +1,3 @@
1
1
  class Thor
2
- VERSION = "0.13.1".freeze
2
+ VERSION = "0.13.2".freeze
3
3
  end
@@ -230,22 +230,34 @@ describe Thor::Base do
230
230
  it "raises an error instead of rescueing if --debug is given" do
231
231
  lambda {
232
232
  MyScript.start ["what", "--debug"]
233
- }.must raise_error(Thor::UndefinedTaskError, /the 'what' task of MyScript is private/)
233
+ }.must raise_error(Thor::UndefinedTaskError, 'Could not find task "what" in "my_script" namespace.')
234
+ end
235
+
236
+ it "does not steal args" do
237
+ args = ["foo", "bar", "--force", "true"]
238
+ MyScript.start(args)
239
+ args.must == ["foo", "bar", "--force", "true"]
240
+ end
241
+
242
+ it "checks unknown options" do
243
+ capture(:stderr) {
244
+ MyScript.start(["foo", "bar", "--force", "true", "--unknown", "baz"])
245
+ }.strip.must == "Unknown switches '--unknown'"
234
246
  end
235
247
  end
236
248
 
237
249
  describe "attr_*" do
238
250
  it "should not add attr_reader as a task" do
239
- capture(:stderr){ MyScript.start(["another_attribute"]) }.must =~ /could not find/
251
+ capture(:stderr){ MyScript.start(["another_attribute"]) }.must =~ /Could not find/
240
252
  end
241
253
 
242
254
  it "should not add attr_writer as a task" do
243
- capture(:stderr){ MyScript.start(["another_attribute=", "foo"]) }.must =~ /could not find/
255
+ capture(:stderr){ MyScript.start(["another_attribute=", "foo"]) }.must =~ /Could not find/
244
256
  end
245
257
 
246
258
  it "should not add attr_accessor as a task" do
247
- capture(:stderr){ MyScript.start(["some_attribute"]) }.must =~ /could not find/
248
- capture(:stderr){ MyScript.start(["some_attribute=", "foo"]) }.must =~ /could not find/
259
+ capture(:stderr){ MyScript.start(["some_attribute"]) }.must =~ /Could not find/
260
+ capture(:stderr){ MyScript.start(["some_attribute=", "foo"]) }.must =~ /Could not find/
249
261
  end
250
262
  end
251
263
  end
@@ -1,4 +1,6 @@
1
1
  class MyScript < Thor
2
+ check_unknown_options!
3
+
2
4
  attr_accessor :some_attribute
3
5
  attr_writer :another_attribute
4
6
  attr_reader :another_attribute
@@ -53,6 +55,10 @@ END
53
55
  def long_description
54
56
  end
55
57
 
58
+ desc "name-with-dashes", "Ensure normalization of task names"
59
+ def name_with_dashes
60
+ end
61
+
56
62
  method_options :all => :boolean
57
63
  desc "with_optional NAME", "invoke with optional name"
58
64
  def with_optional(name=nil)
@@ -42,7 +42,7 @@ describe Thor::Arguments do
42
42
 
43
43
  it "and required arguments raises an error" do
44
44
  create :string => nil, :numeric => nil
45
- lambda { parse }.must raise_error(Thor::RequiredArgumentMissingError, "no value provided for required arguments 'string', 'numeric'")
45
+ lambda { parse }.must raise_error(Thor::RequiredArgumentMissingError, "No value provided for required arguments 'string', 'numeric'")
46
46
  end
47
47
 
48
48
  it "and default arguments returns default values" do
@@ -35,16 +35,6 @@ describe Thor::Option do
35
35
  end
36
36
  end
37
37
 
38
- describe "equals to :optional" do
39
- it "has type equals to :boolean" do
40
- capture(:stderr){ parse(:foo, :optional).type.must == :boolean }
41
- end
42
-
43
- it "has no default value" do
44
- capture(:stderr){ parse(:foo, :optional).default.must be_nil }
45
- end
46
- end
47
-
48
38
  describe "and symbol is not a reserved key" do
49
39
  it "has type equals to :string" do
50
40
  parse(:foo, :bar).type.must == :string
@@ -2,18 +2,22 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
  require 'thor/parser'
3
3
 
4
4
  describe Thor::Options do
5
- def create(opts)
5
+ def create(opts, defaults={})
6
6
  opts.each do |key, value|
7
7
  opts[key] = Thor::Option.parse(key, value) unless value.is_a?(Thor::Option)
8
8
  end
9
9
 
10
- @opt = Thor::Options.new(opts)
10
+ @opt = Thor::Options.new(opts, defaults)
11
11
  end
12
12
 
13
13
  def parse(*args)
14
14
  @opt.parse(args.flatten)
15
15
  end
16
16
 
17
+ def check_unknown!
18
+ @opt.check_unknown!
19
+ end
20
+
17
21
  describe "#to_switches" do
18
22
  it "turns true values into a flag" do
19
23
  Thor::Options.to_switches(:color => true).must == "--color"
@@ -81,7 +85,23 @@ describe Thor::Options do
81
85
 
82
86
  it "returns the default value if none is provided" do
83
87
  create :foo => "baz", :bar => :required
84
- parse("--bar=boom")["foo"].must == "baz"
88
+ parse("--bar", "boom")["foo"].must == "baz"
89
+ end
90
+
91
+ it "returns the default value from defaults hash to required arguments" do
92
+ create Hash[:bar => :required], Hash[:bar => "baz"]
93
+ parse["bar"].must == "baz"
94
+ end
95
+
96
+ it "gives higher priority to defaults given in the hash" do
97
+ create Hash[:bar => true], Hash[:bar => false]
98
+ parse["bar"].must == false
99
+ end
100
+
101
+ it "raises an error for unknown switches" do
102
+ create :foo => "baz", :bar => :required
103
+ parse("--bar", "baz", "--baz", "unknown")
104
+ lambda { check_unknown! }.must raise_error(Thor::UnknownArgumentError, "Unknown switches '--baz'")
85
105
  end
86
106
 
87
107
  describe "with no input" do
@@ -97,7 +117,7 @@ describe Thor::Options do
97
117
 
98
118
  it "and a required switch raises an error" do
99
119
  create "--foo" => :required
100
- lambda { parse }.must raise_error(Thor::RequiredArgumentMissingError, "no value provided for required options '--foo'")
120
+ lambda { parse }.must raise_error(Thor::RequiredArgumentMissingError, "No value provided for required options '--foo'")
101
121
  end
102
122
  end
103
123
 
@@ -247,7 +267,7 @@ describe Thor::Options do
247
267
 
248
268
  it "raises error when value isn't numeric" do
249
269
  lambda { parse("-n", "foo") }.must raise_error(Thor::MalformattedArgumentError,
250
- "expected numeric value for '-n'; got \"foo\"")
270
+ "Expected numeric value for '-n'; got \"foo\"")
251
271
  end
252
272
  end
253
273
 
@@ -32,7 +32,7 @@ describe Thor::Runner do
32
32
  it "raises error if a class/task cannot be found" do
33
33
  Thor::Runner.should_receive(:exit).with(1)
34
34
  content = capture(:stderr){ Thor::Runner.start(["help", "unknown"]) }
35
- content.must =~ /could not find Thor class or task 'unknown'/
35
+ content.strip.must == 'Could not find namespace or task "unknown".'
36
36
  end
37
37
  end
38
38
 
@@ -70,7 +70,8 @@ describe Thor::Runner do
70
70
  it "raises an error if class/task can't be found" do
71
71
  Thor::Runner.should_receive(:exit).with(1)
72
72
  ARGV.replace ["unknown"]
73
- capture(:stderr){ Thor::Runner.start }.must =~ /could not find Thor class or task 'unknown'/
73
+ content = capture(:stderr){ Thor::Runner.start }
74
+ content.strip.must == 'Could not find namespace or task "unknown".'
74
75
  end
75
76
 
76
77
  it "does not swallow NoMethodErrors that occur inside the called method" do
@@ -85,7 +86,8 @@ describe Thor::Runner do
85
86
 
86
87
  it "does not swallow Thor InvocationError" do
87
88
  ARGV.replace ["my_script:animal"]
88
- capture(:stderr) { Thor::Runner.start }.must =~ /'animal' was called incorrectly\. Call as 'my_script:animal TYPE'/
89
+ content = capture(:stderr) { Thor::Runner.start }
90
+ content.strip.must == '"animal" was called incorrectly. Call as "my_script:animal TYPE".'
89
91
  end
90
92
  end
91
93
 
@@ -38,9 +38,9 @@ describe Thor::Task do
38
38
  end
39
39
 
40
40
  it "does not invoke an existing method" do
41
- lambda {
42
- Thor::Task::Dynamic.new('to_s').run([])
43
- }.must raise_error(Thor::Error, "could not find Thor class or task 'to_s'")
41
+ mock = mock()
42
+ mock.class.should_receive(:handle_no_task_error).with("to_s")
43
+ Thor::Task::Dynamic.new('to_s').run(mock)
44
44
  end
45
45
  end
46
46
 
@@ -62,9 +62,8 @@ describe Thor::Task do
62
62
  it "raises an error if the method to be invoked is private" do
63
63
  mock = mock()
64
64
  mock.should_receive(:private_methods).and_return(['can_has'])
65
- lambda {
66
- task.run(mock)
67
- }.must raise_error(Thor::UndefinedTaskError, "the 'can_has' task of Spec::Mocks::Mock is private")
65
+ mock.class.should_receive(:handle_no_task_error).with("can_has")
66
+ task.run(mock)
68
67
  end
69
68
  end
70
69
  end
@@ -112,11 +112,11 @@ describe Thor do
112
112
  end
113
113
 
114
114
  it "raises an error if a required param is not provided" do
115
- capture(:stderr) { MyScript.start(["animal"]) }.must =~ /'animal' was called incorrectly\. Call as 'my_script:animal TYPE'/
115
+ capture(:stderr) { MyScript.start(["animal"]) }.strip.must == '"animal" was called incorrectly. Call as "my_script:animal TYPE".'
116
116
  end
117
117
 
118
118
  it "raises an error if the invoked task does not exist" do
119
- capture(:stderr) { Amazing.start(["animal"]) }.must =~ /The amazing namespace doesn't have a 'animal' task/
119
+ capture(:stderr) { Amazing.start(["animal"]) }.strip.must == 'Could not find task "animal" in "amazing" namespace.'
120
120
  end
121
121
 
122
122
  it "calls method_missing if an unknown method is passed in" do
@@ -124,7 +124,7 @@ describe Thor do
124
124
  end
125
125
 
126
126
  it "does not call a private method no matter what" do
127
- capture(:stderr) { MyScript.start(["what"]) }.must =~ /the 'what' task of MyScript is private/
127
+ capture(:stderr) { MyScript.start(["what"]) }.strip.must == 'Could not find task "what" in "my_script" namespace.'
128
128
  end
129
129
 
130
130
  it "uses task default options" do
@@ -200,7 +200,11 @@ END
200
200
  it "raises an error if the task can't be found" do
201
201
  lambda {
202
202
  MyScript.task_help(shell, "unknown")
203
- }.must raise_error(Thor::Error, "task 'unknown' could not be found in namespace 'my_script'")
203
+ }.must raise_error(Thor::UndefinedTaskError, 'Could not find task "unknown" in "my_script" namespace.')
204
+ end
205
+
206
+ it "normalizes names before claiming they don't exist" do
207
+ capture(:stdout) { MyScript.task_help(shell, "name-with-dashes") }.must =~ /thor my_script:name-with-dashes/
204
208
  end
205
209
  end
206
210
 
@@ -38,11 +38,6 @@ describe Thor::Util do
38
38
  Thor::Util.namespace_from_thor_class("FooBar::BarBaz::BazBoom").must == "foo_bar:bar_baz:baz_boom"
39
39
  end
40
40
 
41
- it "gets rid of an initial Default module" do
42
- Thor::Util.namespace_from_thor_class("Default::Foo::Bar").must == ":foo:bar"
43
- Thor::Util.namespace_from_thor_class("Default").must == ""
44
- end
45
-
46
41
  it "accepts class and module objects" do
47
42
  Thor::Util.namespace_from_thor_class(Thor::CoreExt::OrderedHash).must == "thor:core_ext:ordered_hash"
48
43
  Thor::Util.namespace_from_thor_class(Thor::Util).must == "thor:util"
@@ -92,30 +87,30 @@ describe Thor::Util do
92
87
  end
93
88
  end
94
89
 
95
- describe "#namespace_to_thor_class_and_task" do
90
+ describe "#find_class_and_task_by_namespace" do
96
91
  it "returns a Thor::Group class if full namespace matches" do
97
- Thor::Util.namespace_to_thor_class_and_task("my_counter").must == [MyCounter, nil]
92
+ Thor::Util.find_class_and_task_by_namespace("my_counter").must == [MyCounter, nil]
98
93
  end
99
94
 
100
95
  it "returns a Thor class if full namespace matches" do
101
- Thor::Util.namespace_to_thor_class_and_task("thor").must == [Thor, nil]
96
+ Thor::Util.find_class_and_task_by_namespace("thor").must == [Thor, nil]
102
97
  end
103
98
 
104
99
  it "returns a Thor class and the task name" do
105
- Thor::Util.namespace_to_thor_class_and_task("thor:help").must == [Thor, "help"]
100
+ Thor::Util.find_class_and_task_by_namespace("thor:help").must == [Thor, "help"]
106
101
  end
107
102
 
108
103
  it "fallbacks in the namespace:task look up even if a full namespace does not match" do
109
104
  Thor.const_set(:Help, Module.new)
110
- Thor::Util.namespace_to_thor_class_and_task("thor:help").must == [Thor, "help"]
105
+ Thor::Util.find_class_and_task_by_namespace("thor:help").must == [Thor, "help"]
111
106
  Thor.send :remove_const, :Help
112
107
  end
113
108
 
114
109
  describe 'errors' do
115
110
  it "raises an error if the Thor class or task can't be found" do
116
111
  lambda {
117
- Thor::Util.namespace_to_thor_class_and_task("foobar")
118
- }.must raise_error(Thor::Error, "could not find Thor class or task 'foobar'")
112
+ Thor::Util.find_class_and_task_by_namespace!("foobar")
113
+ }.must raise_error(Thor::Error, 'Could not find namespace or task "foobar".')
119
114
  end
120
115
  end
121
116
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2010-02-11 00:00:00 +01:00
13
+ date: 2010-02-17 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies: []
16
16