thor 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
 
OSZAR »