github_cli 0.5.0 → 0.5.1
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.md +5 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +4 -1
- data/github_cli.gemspec +3 -1
- data/lib/github_cli/dsl.rb +7 -3
- data/lib/github_cli/thor_ext.rb +1 -3
- data/lib/github_cli/vendor/thor/actions/create_file.rb +105 -0
- data/lib/github_cli/vendor/thor/actions/create_link.rb +57 -0
- data/lib/github_cli/vendor/thor/actions/directory.rb +98 -0
- data/lib/github_cli/vendor/thor/actions/empty_directory.rb +153 -0
- data/lib/github_cli/vendor/thor/actions/file_manipulation.rb +308 -0
- data/lib/github_cli/vendor/thor/actions/inject_into_file.rb +109 -0
- data/lib/github_cli/vendor/thor/actions.rb +318 -0
- data/lib/github_cli/vendor/thor/base.rb +641 -0
- data/lib/github_cli/vendor/thor/core_ext/dir_escape.rb +0 -0
- data/lib/github_cli/vendor/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/github_cli/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/github_cli/vendor/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/github_cli/vendor/thor/empty.txt +0 -0
- data/lib/github_cli/vendor/thor/error.rb +35 -0
- data/lib/github_cli/vendor/thor/group.rb +285 -0
- data/lib/github_cli/vendor/thor/invocation.rb +170 -0
- data/lib/github_cli/vendor/thor/parser/argument.rb +74 -0
- data/lib/github_cli/vendor/thor/parser/arguments.rb +171 -0
- data/lib/github_cli/vendor/thor/parser/option.rb +121 -0
- data/lib/github_cli/vendor/thor/parser/options.rb +178 -0
- data/lib/github_cli/vendor/thor/parser.rb +4 -0
- data/lib/github_cli/vendor/thor/rake_compat.rb +71 -0
- data/lib/github_cli/vendor/thor/runner.rb +321 -0
- data/lib/github_cli/vendor/thor/shell/basic.rb +389 -0
- data/lib/github_cli/vendor/thor/shell/color.rb +144 -0
- data/lib/github_cli/vendor/thor/shell/html.rb +123 -0
- data/lib/github_cli/vendor/thor/shell.rb +88 -0
- data/lib/github_cli/vendor/thor/task.rb +132 -0
- data/lib/github_cli/vendor/thor/util.rb +266 -0
- data/lib/github_cli/vendor/thor/version.rb +3 -0
- data/lib/github_cli/vendor/thor.rb +379 -0
- data/lib/github_cli/vendor.rb +7 -5
- data/lib/github_cli/version.rb +3 -1
- metadata +45 -22
- data/bin/ghc +0 -2
@@ -0,0 +1,75 @@
|
|
1
|
+
class Thor
|
2
|
+
module CoreExt #:nodoc:
|
3
|
+
|
4
|
+
# A hash with indifferent access and magic predicates.
|
5
|
+
#
|
6
|
+
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
|
7
|
+
#
|
8
|
+
# hash[:foo] #=> 'bar'
|
9
|
+
# hash['foo'] #=> 'bar'
|
10
|
+
# hash.foo? #=> true
|
11
|
+
#
|
12
|
+
class HashWithIndifferentAccess < ::Hash #:nodoc:
|
13
|
+
|
14
|
+
def initialize(hash={})
|
15
|
+
super()
|
16
|
+
hash.each do |key, value|
|
17
|
+
self[convert_key(key)] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
super(convert_key(key))
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
super(convert_key(key), value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(key)
|
30
|
+
super(convert_key(key))
|
31
|
+
end
|
32
|
+
|
33
|
+
def values_at(*indices)
|
34
|
+
indices.collect { |key| self[convert_key(key)] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge(other)
|
38
|
+
dup.merge!(other)
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge!(other)
|
42
|
+
other.each do |key, value|
|
43
|
+
self[convert_key(key)] = value
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def convert_key(key)
|
51
|
+
key.is_a?(Symbol) ? key.to_s : key
|
52
|
+
end
|
53
|
+
|
54
|
+
# Magic predicates. For instance:
|
55
|
+
#
|
56
|
+
# options.force? # => !!options['force']
|
57
|
+
# options.shebang # => "/usr/lib/local/ruby"
|
58
|
+
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
59
|
+
#
|
60
|
+
def method_missing(method, *args, &block)
|
61
|
+
method = method.to_s
|
62
|
+
if method =~ /^(\w+)\?$/
|
63
|
+
if args.empty?
|
64
|
+
!!self[$1]
|
65
|
+
else
|
66
|
+
self[$1] == args.first
|
67
|
+
end
|
68
|
+
else
|
69
|
+
self[method]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class Thor
|
2
|
+
module CoreExt #:nodoc:
|
3
|
+
|
4
|
+
if RUBY_VERSION >= '1.9'
|
5
|
+
class OrderedHash < ::Hash
|
6
|
+
end
|
7
|
+
else
|
8
|
+
# This class is based on the Ruby 1.9 ordered hashes.
|
9
|
+
#
|
10
|
+
# It keeps the semantics and most of the efficiency of normal hashes
|
11
|
+
# while also keeping track of the order in which elements were set.
|
12
|
+
#
|
13
|
+
class OrderedHash #:nodoc:
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
Node = Struct.new(:key, :value, :next, :prev)
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@hash = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
@hash[key] && @hash[key].value
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
if node = @hash[key]
|
28
|
+
node.value = value
|
29
|
+
else
|
30
|
+
node = Node.new(key, value)
|
31
|
+
|
32
|
+
if @first.nil?
|
33
|
+
@first = @last = node
|
34
|
+
else
|
35
|
+
node.prev = @last
|
36
|
+
@last.next = node
|
37
|
+
@last = node
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@hash[key] = node
|
42
|
+
value
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(key)
|
46
|
+
if node = @hash[key]
|
47
|
+
prev_node = node.prev
|
48
|
+
next_node = node.next
|
49
|
+
|
50
|
+
next_node.prev = prev_node if next_node
|
51
|
+
prev_node.next = next_node if prev_node
|
52
|
+
|
53
|
+
@first = next_node if @first == node
|
54
|
+
@last = prev_node if @last == node
|
55
|
+
|
56
|
+
value = node.value
|
57
|
+
end
|
58
|
+
|
59
|
+
@hash.delete(key)
|
60
|
+
value
|
61
|
+
end
|
62
|
+
|
63
|
+
def keys
|
64
|
+
self.map { |k, v| k }
|
65
|
+
end
|
66
|
+
|
67
|
+
def values
|
68
|
+
self.map { |k, v| v }
|
69
|
+
end
|
70
|
+
|
71
|
+
def each
|
72
|
+
return unless @first
|
73
|
+
yield [@first.key, @first.value]
|
74
|
+
node = @first
|
75
|
+
yield [node.key, node.value] while node = node.next
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def merge(other)
|
80
|
+
hash = self.class.new
|
81
|
+
|
82
|
+
self.each do |key, value|
|
83
|
+
hash[key] = value
|
84
|
+
end
|
85
|
+
|
86
|
+
other.each do |key, value|
|
87
|
+
hash[key] = value
|
88
|
+
end
|
89
|
+
|
90
|
+
hash
|
91
|
+
end
|
92
|
+
|
93
|
+
def empty?
|
94
|
+
@hash.empty?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Thor
|
2
|
+
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
3
|
+
# errors have their backtrace suppressed and are nicely shown to the user.
|
4
|
+
#
|
5
|
+
# Errors that are caused by the developer, like declaring a method which
|
6
|
+
# overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
|
7
|
+
# ensure that developer errors are shown with full backtrace.
|
8
|
+
#
|
9
|
+
class Error < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raised when a task was not found.
|
13
|
+
#
|
14
|
+
class UndefinedTaskError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when a task was found, but not invoked properly.
|
18
|
+
#
|
19
|
+
class InvocationError < Error
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnknownArgumentError < Error
|
23
|
+
end
|
24
|
+
|
25
|
+
class RequiredArgumentMissingError < InvocationError
|
26
|
+
end
|
27
|
+
|
28
|
+
class MalformattedArgumentError < InvocationError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when a user tries to call a private method encoded in templated filename.
|
32
|
+
#
|
33
|
+
class PrivateMethodEncodedError < Error
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require 'thor/base'
|
2
|
+
|
3
|
+
# Thor has a special class called Thor::Group. The main difference to Thor class
|
4
|
+
# is that it invokes all tasks at once. It also include some methods that allows
|
5
|
+
# invocations to be done at the class method, which are not available to Thor
|
6
|
+
# tasks.
|
7
|
+
class Thor::Group
|
8
|
+
class << self
|
9
|
+
# The description for this Thor::Group. If none is provided, but a source root
|
10
|
+
# exists, tries to find the USAGE one folder above it, otherwise searches
|
11
|
+
# in the superclass.
|
12
|
+
#
|
13
|
+
# ==== Parameters
|
14
|
+
# description<String>:: The description for this Thor::Group.
|
15
|
+
#
|
16
|
+
def desc(description=nil)
|
17
|
+
case description
|
18
|
+
when nil
|
19
|
+
@desc ||= from_superclass(:desc, nil)
|
20
|
+
else
|
21
|
+
@desc = description
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Prints help information.
|
26
|
+
#
|
27
|
+
# ==== Options
|
28
|
+
# short:: When true, shows only usage.
|
29
|
+
#
|
30
|
+
def help(shell)
|
31
|
+
shell.say "Usage:"
|
32
|
+
shell.say " #{banner}\n"
|
33
|
+
shell.say
|
34
|
+
class_options_help(shell)
|
35
|
+
shell.say self.desc if self.desc
|
36
|
+
end
|
37
|
+
|
38
|
+
# Stores invocations for this class merging with superclass values.
|
39
|
+
#
|
40
|
+
def invocations #:nodoc:
|
41
|
+
@invocations ||= from_superclass(:invocations, {})
|
42
|
+
end
|
43
|
+
|
44
|
+
# Stores invocation blocks used on invoke_from_option.
|
45
|
+
#
|
46
|
+
def invocation_blocks #:nodoc:
|
47
|
+
@invocation_blocks ||= from_superclass(:invocation_blocks, {})
|
48
|
+
end
|
49
|
+
|
50
|
+
# Invoke the given namespace or class given. It adds an instance
|
51
|
+
# method that will invoke the klass and task. You can give a block to
|
52
|
+
# configure how it will be invoked.
|
53
|
+
#
|
54
|
+
# The namespace/class given will have its options showed on the help
|
55
|
+
# usage. Check invoke_from_option for more information.
|
56
|
+
#
|
57
|
+
def invoke(*names, &block)
|
58
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
59
|
+
verbose = options.fetch(:verbose, true)
|
60
|
+
|
61
|
+
names.each do |name|
|
62
|
+
invocations[name] = false
|
63
|
+
invocation_blocks[name] = block if block_given?
|
64
|
+
|
65
|
+
class_eval <<-METHOD, __FILE__, __LINE__
|
66
|
+
def _invoke_#{name.to_s.gsub(/\W/, '_')}
|
67
|
+
klass, task = self.class.prepare_for_invocation(nil, #{name.inspect})
|
68
|
+
|
69
|
+
if klass
|
70
|
+
say_status :invoke, #{name.inspect}, #{verbose.inspect}
|
71
|
+
block = self.class.invocation_blocks[#{name.inspect}]
|
72
|
+
_invoke_for_class_method klass, task, &block
|
73
|
+
else
|
74
|
+
say_status :error, %(#{name.inspect} [not found]), :red
|
75
|
+
end
|
76
|
+
end
|
77
|
+
METHOD
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Invoke a thor class based on the value supplied by the user to the
|
82
|
+
# given option named "name". A class option must be created before this
|
83
|
+
# method is invoked for each name given.
|
84
|
+
#
|
85
|
+
# ==== Examples
|
86
|
+
#
|
87
|
+
# class GemGenerator < Thor::Group
|
88
|
+
# class_option :test_framework, :type => :string
|
89
|
+
# invoke_from_option :test_framework
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# ==== Boolean options
|
93
|
+
#
|
94
|
+
# In some cases, you want to invoke a thor class if some option is true or
|
95
|
+
# false. This is automatically handled by invoke_from_option. Then the
|
96
|
+
# option name is used to invoke the generator.
|
97
|
+
#
|
98
|
+
# ==== Preparing for invocation
|
99
|
+
#
|
100
|
+
# In some cases you want to customize how a specified hook is going to be
|
101
|
+
# invoked. You can do that by overwriting the class method
|
102
|
+
# prepare_for_invocation. The class method must necessarily return a klass
|
103
|
+
# and an optional task.
|
104
|
+
#
|
105
|
+
# ==== Custom invocations
|
106
|
+
#
|
107
|
+
# You can also supply a block to customize how the option is going to be
|
108
|
+
# invoked. The block receives two parameters, an instance of the current
|
109
|
+
# class and the klass to be invoked.
|
110
|
+
#
|
111
|
+
def invoke_from_option(*names, &block)
|
112
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
113
|
+
verbose = options.fetch(:verbose, :white)
|
114
|
+
|
115
|
+
names.each do |name|
|
116
|
+
unless class_options.key?(name)
|
117
|
+
raise ArgumentError, "You have to define the option #{name.inspect} " <<
|
118
|
+
"before setting invoke_from_option."
|
119
|
+
end
|
120
|
+
|
121
|
+
invocations[name] = true
|
122
|
+
invocation_blocks[name] = block if block_given?
|
123
|
+
|
124
|
+
class_eval <<-METHOD, __FILE__, __LINE__
|
125
|
+
def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
|
126
|
+
return unless options[#{name.inspect}]
|
127
|
+
|
128
|
+
value = options[#{name.inspect}]
|
129
|
+
value = #{name.inspect} if TrueClass === value
|
130
|
+
klass, task = self.class.prepare_for_invocation(#{name.inspect}, value)
|
131
|
+
|
132
|
+
if klass
|
133
|
+
say_status :invoke, value, #{verbose.inspect}
|
134
|
+
block = self.class.invocation_blocks[#{name.inspect}]
|
135
|
+
_invoke_for_class_method klass, task, &block
|
136
|
+
else
|
137
|
+
say_status :error, %(\#{value} [not found]), :red
|
138
|
+
end
|
139
|
+
end
|
140
|
+
METHOD
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Remove a previously added invocation.
|
145
|
+
#
|
146
|
+
# ==== Examples
|
147
|
+
#
|
148
|
+
# remove_invocation :test_framework
|
149
|
+
#
|
150
|
+
def remove_invocation(*names)
|
151
|
+
names.each do |name|
|
152
|
+
remove_task(name)
|
153
|
+
remove_class_option(name)
|
154
|
+
invocations.delete(name)
|
155
|
+
invocation_blocks.delete(name)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Overwrite class options help to allow invoked generators options to be
|
160
|
+
# shown recursively when invoking a generator.
|
161
|
+
#
|
162
|
+
def class_options_help(shell, groups={}) #:nodoc:
|
163
|
+
get_options_from_invocations(groups, class_options) do |klass|
|
164
|
+
klass.send(:get_options_from_invocations, groups, class_options)
|
165
|
+
end
|
166
|
+
super(shell, groups)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Get invocations array and merge options from invocations. Those
|
170
|
+
# options are added to group_options hash. Options that already exists
|
171
|
+
# in base_options are not added twice.
|
172
|
+
#
|
173
|
+
def get_options_from_invocations(group_options, base_options) #:nodoc:
|
174
|
+
invocations.each do |name, from_option|
|
175
|
+
value = if from_option
|
176
|
+
option = class_options[name]
|
177
|
+
option.type == :boolean ? name : option.default
|
178
|
+
else
|
179
|
+
name
|
180
|
+
end
|
181
|
+
next unless value
|
182
|
+
|
183
|
+
klass, _ = prepare_for_invocation(name, value)
|
184
|
+
next unless klass && klass.respond_to?(:class_options)
|
185
|
+
|
186
|
+
value = value.to_s
|
187
|
+
human_name = value.respond_to?(:classify) ? value.classify : value
|
188
|
+
|
189
|
+
group_options[human_name] ||= []
|
190
|
+
group_options[human_name] += klass.class_options.values.select do |class_option|
|
191
|
+
base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
|
192
|
+
!group_options.values.flatten.any? { |i| i.name == class_option.name }
|
193
|
+
end
|
194
|
+
|
195
|
+
yield klass if block_given?
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns tasks ready to be printed.
|
200
|
+
def printable_tasks(*)
|
201
|
+
item = []
|
202
|
+
item << banner
|
203
|
+
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
|
204
|
+
[item]
|
205
|
+
end
|
206
|
+
|
207
|
+
def handle_argument_error(task, error, arity=nil) #:nodoc:
|
208
|
+
if arity > 0
|
209
|
+
msg = "#{basename} #{task.name} takes #{arity} argument"
|
210
|
+
msg << "s" if arity > 1
|
211
|
+
msg << ", but it should not."
|
212
|
+
else
|
213
|
+
msg = "You should not pass arguments to #{basename} #{task.name}."
|
214
|
+
end
|
215
|
+
|
216
|
+
raise error, msg
|
217
|
+
end
|
218
|
+
|
219
|
+
protected
|
220
|
+
|
221
|
+
# The method responsible for dispatching given the args.
|
222
|
+
def dispatch(task, given_args, given_opts, config) #:nodoc:
|
223
|
+
if Thor::HELP_MAPPINGS.include?(given_args.first)
|
224
|
+
help(config[:shell])
|
225
|
+
return
|
226
|
+
end
|
227
|
+
|
228
|
+
args, opts = Thor::Options.split(given_args)
|
229
|
+
opts = given_opts || opts
|
230
|
+
|
231
|
+
instance = new(args, opts, config)
|
232
|
+
yield instance if block_given?
|
233
|
+
args = instance.args
|
234
|
+
|
235
|
+
if task
|
236
|
+
instance.invoke_task(all_tasks[task])
|
237
|
+
else
|
238
|
+
instance.invoke_all
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# The banner for this class. You can customize it if you are invoking the
|
243
|
+
# thor class by another ways which is not the Thor::Runner.
|
244
|
+
def banner
|
245
|
+
"#{basename} #{self_task.formatted_usage(self, false)}"
|
246
|
+
end
|
247
|
+
|
248
|
+
# Represents the whole class as a task.
|
249
|
+
def self_task #:nodoc:
|
250
|
+
Thor::DynamicTask.new(self.namespace, class_options)
|
251
|
+
end
|
252
|
+
|
253
|
+
def baseclass #:nodoc:
|
254
|
+
Thor::Group
|
255
|
+
end
|
256
|
+
|
257
|
+
def create_task(meth) #:nodoc:
|
258
|
+
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, nil)
|
259
|
+
true
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
include Thor::Base
|
264
|
+
|
265
|
+
protected
|
266
|
+
|
267
|
+
# Shortcut to invoke with padding and block handling. Use internally by
|
268
|
+
# invoke and invoke_from_option class methods.
|
269
|
+
def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
|
270
|
+
with_padding do
|
271
|
+
if block
|
272
|
+
case block.arity
|
273
|
+
when 3
|
274
|
+
block.call(self, klass, task)
|
275
|
+
when 2
|
276
|
+
block.call(self, klass)
|
277
|
+
when 1
|
278
|
+
instance_exec(klass, &block)
|
279
|
+
end
|
280
|
+
else
|
281
|
+
invoke klass, task, *args
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
class Thor
|
2
|
+
module Invocation
|
3
|
+
def self.included(base) #:nodoc:
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
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
|
+
def prepare_for_invocation(key, name) #:nodoc:
|
12
|
+
case name
|
13
|
+
when Symbol, String
|
14
|
+
Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
|
15
|
+
else
|
16
|
+
name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Make initializer aware of invocations and the initialization args.
|
22
|
+
def initialize(args=[], options={}, config={}, &block) #:nodoc:
|
23
|
+
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
|
24
|
+
@_initializer = [ args, options, config ]
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
# Receives a name and invokes it. The name can be a string (either "task" or
|
29
|
+
# "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
|
30
|
+
# cannot be guessed by name, it can also be supplied as second argument.
|
31
|
+
#
|
32
|
+
# You can also supply the arguments, options and configuration values for
|
33
|
+
# the task to be invoked, if none is given, the same values used to
|
34
|
+
# initialize the invoker are used to initialize the invoked.
|
35
|
+
#
|
36
|
+
# When no name is given, it will invoke the default task of the current class.
|
37
|
+
#
|
38
|
+
# ==== Examples
|
39
|
+
#
|
40
|
+
# class A < Thor
|
41
|
+
# def foo
|
42
|
+
# invoke :bar
|
43
|
+
# invoke "b:hello", ["José"]
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def bar
|
47
|
+
# invoke "b:hello", ["José"]
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# class B < Thor
|
52
|
+
# def hello(name)
|
53
|
+
# puts "hello #{name}"
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# You can notice that the method "foo" above invokes two tasks: "bar",
|
58
|
+
# which belongs to the same class and "hello" which belongs to the class B.
|
59
|
+
#
|
60
|
+
# By using an invocation system you ensure that a task is invoked only once.
|
61
|
+
# In the example above, invoking "foo" will invoke "b:hello" just once, even
|
62
|
+
# if it's invoked later by "bar" method.
|
63
|
+
#
|
64
|
+
# When class A invokes class B, all arguments used on A initialization are
|
65
|
+
# supplied to B. This allows lazy parse of options. Let's suppose you have
|
66
|
+
# some rspec tasks:
|
67
|
+
#
|
68
|
+
# class Rspec < Thor::Group
|
69
|
+
# class_option :mock_framework, :type => :string, :default => :rr
|
70
|
+
#
|
71
|
+
# def invoke_mock_framework
|
72
|
+
# invoke "rspec:#{options[:mock_framework]}"
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# As you noticed, it invokes the given mock framework, which might have its
|
77
|
+
# own options:
|
78
|
+
#
|
79
|
+
# class Rspec::RR < Thor::Group
|
80
|
+
# class_option :style, :type => :string, :default => :mock
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Since it's not rspec concern to parse mock framework options, when RR
|
84
|
+
# is invoked all options are parsed again, so RR can extract only the options
|
85
|
+
# that it's going to use.
|
86
|
+
#
|
87
|
+
# If you want Rspec::RR to be initialized with its own set of options, you
|
88
|
+
# have to do that explicitly:
|
89
|
+
#
|
90
|
+
# invoke "rspec:rr", [], :style => :foo
|
91
|
+
#
|
92
|
+
# Besides giving an instance, you can also give a class to invoke:
|
93
|
+
#
|
94
|
+
# invoke Rspec::RR, [], :style => :foo
|
95
|
+
#
|
96
|
+
def invoke(name=nil, *args)
|
97
|
+
if name.nil?
|
98
|
+
warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
|
99
|
+
return invoke_all
|
100
|
+
end
|
101
|
+
|
102
|
+
args.unshift(nil) if Array === args.first || NilClass === args.first
|
103
|
+
task, args, opts, config = args
|
104
|
+
|
105
|
+
klass, task = _retrieve_class_and_task(name, task)
|
106
|
+
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
107
|
+
|
108
|
+
args, opts, config = _parse_initialization_options(args, opts, config)
|
109
|
+
klass.send(:dispatch, task, args, opts, config) do |instance|
|
110
|
+
instance.parent_options = options
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Invoke the given task if the given args.
|
115
|
+
def invoke_task(task, *args) #:nodoc:
|
116
|
+
current = @_invocations[self.class]
|
117
|
+
|
118
|
+
unless current.include?(task.name)
|
119
|
+
current << task.name
|
120
|
+
task.run(self, *args)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Invoke all tasks for the current instance.
|
125
|
+
def invoke_all #:nodoc:
|
126
|
+
self.class.all_tasks.map { |_, task| invoke_task(task) }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Invokes using shell padding.
|
130
|
+
def invoke_with_padding(*args)
|
131
|
+
with_padding { invoke(*args) }
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
# Configuration values that are shared between invocations.
|
137
|
+
def _shared_configuration #:nodoc:
|
138
|
+
{ :invocations => @_invocations }
|
139
|
+
end
|
140
|
+
|
141
|
+
# This method simply retrieves the class and task to be invoked.
|
142
|
+
# If the name is nil or the given name is a task in the current class,
|
143
|
+
# use the given name and return self as class. Otherwise, call
|
144
|
+
# prepare_for_invocation in the current class.
|
145
|
+
def _retrieve_class_and_task(name, sent_task=nil) #:nodoc:
|
146
|
+
case
|
147
|
+
when name.nil?
|
148
|
+
[self.class, nil]
|
149
|
+
when self.class.all_tasks[name.to_s]
|
150
|
+
[self.class, name.to_s]
|
151
|
+
else
|
152
|
+
klass, task = self.class.prepare_for_invocation(nil, name)
|
153
|
+
[klass, task || sent_task]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Initialize klass using values stored in the @_initializer.
|
158
|
+
def _parse_initialization_options(args, opts, config) #:nodoc:
|
159
|
+
stored_args, stored_opts, stored_config = @_initializer
|
160
|
+
|
161
|
+
args ||= stored_args.dup
|
162
|
+
opts ||= stored_opts.dup
|
163
|
+
|
164
|
+
config ||= {}
|
165
|
+
config = stored_config.merge(_shared_configuration).merge!(config)
|
166
|
+
|
167
|
+
[ args, opts, config ]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|