1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
class Thor
class Task < Struct.new(:name, :description, :usage, :options)
# Creates a dynamic task. Dynamic tasks are created on demand to allow method
# missing calls (since a method missing does not have a task object for it).
#
def self.dynamic(name)
new(name, "A dynamically-generated task", name.to_s)
end
def initialize(name, description, usage, options=nil)
super(name.to_s, description, usage, options || {})
end
def initialize_copy(other)
super(other)
self.options = other.options.dup if other.options
end
def short_description
description.split("\n").first if description
end
# By default, a task invokes a method in the thor class. You can change this
# implementation to create custom tasks.
#
def run(instance, args=[])
raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
instance.send(name, *args)
rescue ArgumentError => e
parse_argument_error(instance, e, caller)
rescue NoMethodError => e
parse_no_method_error(instance, e)
end
# Returns the formatted usage. If a class is given, the class arguments are
# injected in the usage.
#
def formatted_usage(klass=nil, namespace=false)
formatted = ''
formatted << "#{klass.namespace.gsub(/^default/,'')}:" if klass && namespace
formatted << formatted_arguments(klass)
formatted << " #{formatted_options}"
formatted.strip!
formatted
end
# Injects the class arguments into the task usage.
#
def formatted_arguments(klass)
if klass && !klass.arguments.empty?
usage.to_s.gsub(/^#{name}/) do |match|
match << " " << klass.arguments.map{ |a| a.usage }.join(' ')
end
else
usage.to_s
end
end
# Returns the options usage for this task.
#
def formatted_options
@formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ")
end
protected
# Given a target, checks if this class name is not a private/protected method.
#
def public_method?(instance)
collection = instance.private_methods + instance.protected_methods
!(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9
end
# Clean everything that comes from the Thor gempath and remove the caller.
#
def sans_backtrace(backtrace, caller)
dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/
saned = backtrace.reject { |frame| frame =~ dirname }
saned -= caller
end
def parse_argument_error(instance, e, caller)
backtrace = sans_backtrace(e.backtrace, caller)
if backtrace.empty? && e.message =~ /wrong number of arguments/
if instance.is_a?(Thor::Group)
raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
else
raise InvocationError, "'#{name}' was called incorrectly. Call as " <<
"'#{formatted_usage(instance.class, true)}'"
end
else
raise e
end
end
def parse_no_method_error(instance, e)
if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
raise UndefinedTaskError, "The #{instance.class.namespace} namespace " <<
"doesn't have a '#{name}' task"
else
raise e
end
end
end
end
|