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.
- data/CHANGELOG.rdoc +6 -0
- data/Thorfile +1 -1
- data/lib/thor.rb +10 -6
- data/lib/thor/base.rb +30 -20
- data/lib/thor/error.rb +3 -0
- data/lib/thor/group.rb +7 -5
- data/lib/thor/invocation.rb +14 -17
- data/lib/thor/parser/arguments.rb +5 -4
- data/lib/thor/parser/option.rb +0 -4
- data/lib/thor/parser/options.rb +23 -10
- data/lib/thor/runner.rb +3 -4
- data/lib/thor/task.rb +20 -29
- data/lib/thor/util.rb +11 -14
- data/lib/thor/version.rb +1 -1
- data/spec/base_spec.rb +17 -5
- data/spec/fixtures/script.thor +6 -0
- data/spec/parser/arguments_spec.rb +1 -1
- data/spec/parser/option_spec.rb +0 -10
- data/spec/parser/options_spec.rb +25 -5
- data/spec/runner_spec.rb +5 -3
- data/spec/task_spec.rb +5 -6
- data/spec/thor_spec.rb +8 -4
- data/spec/util_spec.rb +7 -12
- metadata +2 -2
data/CHANGELOG.rdoc
CHANGED
@@ -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:
|
67
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
|
68
68
|
end
|
69
69
|
end
|
data/lib/thor.rb
CHANGED
@@ -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(
|
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
|
-
|
149
|
-
|
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
|
-
|
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:
|
data/lib/thor/base.rb
CHANGED
@@ -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)
|
42
|
-
|
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
|
-
|
56
|
-
self.options =
|
57
|
-
self.
|
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
|
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
|
-
|
372
|
-
|
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
|
-
|
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:
|
data/lib/thor/error.rb
CHANGED
data/lib/thor/group.rb
CHANGED
@@ -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(
|
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
|
-
|
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.
|
data/lib/thor/invocation.rb
CHANGED
@@ -5,21 +5,20 @@ class Thor
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
#
|
9
|
-
#
|
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.
|
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
|
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,
|
96
|
-
|
97
|
-
args, opts, config =
|
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
|
-
#
|
130
|
-
#
|
131
|
-
#
|
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
|
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(
|
20
|
-
|
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, "
|
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, "
|
141
|
+
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
data/lib/thor/parser/option.rb
CHANGED
@@ -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
|
data/lib/thor/parser/options.rb
CHANGED
@@ -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
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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, "
|
146
|
+
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
|
134
147
|
end
|
135
148
|
end
|
136
149
|
|
data/lib/thor/runner.rb
CHANGED
@@ -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.
|
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.
|
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"
|
data/lib/thor/task.rb
CHANGED
@@ -9,10 +9,11 @@ class Thor
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def run(instance, args=[])
|
12
|
-
|
13
|
-
|
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
|
-
|
32
|
-
|
32
|
+
public_method?(instance) ?
|
33
|
+
instance.send(name, *args) : instance.class.handle_no_task_error(name)
|
33
34
|
rescue ArgumentError => e
|
34
|
-
|
35
|
-
|
35
|
+
handle_argument_error?(instance, e, caller) ?
|
36
|
+
instance.class.handle_argument_error(self, e) : (raise e)
|
36
37
|
rescue NoMethodError => e
|
37
|
-
|
38
|
-
|
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
|
87
|
-
|
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
|
102
|
-
|
103
|
-
|
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
|
data/lib/thor/util.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/thor/version.rb
CHANGED
data/spec/base_spec.rb
CHANGED
@@ -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,
|
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 =~ /
|
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 =~ /
|
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 =~ /
|
248
|
-
capture(:stderr){ MyScript.start(["some_attribute=", "foo"]) }.must =~ /
|
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
|
data/spec/fixtures/script.thor
CHANGED
@@ -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, "
|
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
|
data/spec/parser/option_spec.rb
CHANGED
@@ -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
|
data/spec/parser/options_spec.rb
CHANGED
@@ -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
|
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, "
|
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
|
-
"
|
270
|
+
"Expected numeric value for '-n'; got \"foo\"")
|
251
271
|
end
|
252
272
|
end
|
253
273
|
|
data/spec/runner_spec.rb
CHANGED
@@ -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
|
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 }
|
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 }
|
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
|
|
data/spec/task_spec.rb
CHANGED
@@ -38,9 +38,9 @@ describe Thor::Task do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
it "does not invoke an existing method" do
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
66
|
-
|
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
|
data/spec/thor_spec.rb
CHANGED
@@ -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
|
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
|
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
|
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::
|
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
|
|
data/spec/util_spec.rb
CHANGED
@@ -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 "#
|
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.
|
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.
|
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.
|
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.
|
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.
|
118
|
-
}.must raise_error(Thor::Error,
|
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.
|
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-
|
13
|
+
date: 2010-02-17 00:00:00 +01:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|