bundler 1.1.pre.5 → 1.1.pre.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bundler might be problematic. Click here for more details.
- data/CHANGELOG.md +52 -1
- data/ISSUES.md +1 -0
- data/Rakefile +3 -3
- data/UPGRADING.md +2 -2
- data/lib/bundler/cli.rb +6 -5
- data/lib/bundler/definition.rb +13 -2
- data/lib/bundler/endpoint_specification.rb +19 -0
- data/lib/bundler/fetcher.rb +34 -2
- data/lib/bundler/gem_helper.rb +6 -4
- data/lib/bundler/index.rb +29 -4
- data/lib/bundler/installer.rb +2 -1
- data/lib/bundler/lazy_specification.rb +4 -2
- data/lib/bundler/resolver.rb +14 -16
- data/lib/bundler/rubygems_ext.rb +1 -0
- data/lib/bundler/rubygems_integration.rb +22 -2
- data/lib/bundler/runtime.rb +2 -0
- data/lib/bundler/source.rb +52 -44
- data/lib/bundler/spec_set.rb +10 -9
- data/lib/bundler/templates/newgem/Rakefile.tt +1 -1
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +2 -2
- data/lib/bundler/vendor/thor.rb +43 -4
- data/lib/bundler/vendor/thor/actions.rb +28 -11
- data/lib/bundler/vendor/thor/actions/create_file.rb +2 -2
- data/lib/bundler/vendor/thor/actions/create_link.rb +57 -0
- data/lib/bundler/vendor/thor/actions/directory.rb +2 -2
- data/lib/bundler/vendor/thor/actions/empty_directory.rb +0 -0
- data/lib/bundler/vendor/thor/actions/file_manipulation.rb +56 -15
- data/lib/bundler/vendor/thor/actions/inject_into_file.rb +13 -8
- data/lib/bundler/vendor/thor/base.rb +24 -4
- data/lib/bundler/vendor/thor/core_ext/file_binary_read.rb +0 -0
- data/lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb +0 -0
- data/lib/bundler/vendor/thor/core_ext/ordered_hash.rb +0 -0
- data/lib/bundler/vendor/thor/error.rb +0 -0
- data/lib/bundler/vendor/thor/group.rb +273 -0
- data/lib/bundler/vendor/thor/invocation.rb +0 -0
- data/lib/bundler/vendor/thor/parser.rb +0 -0
- data/lib/bundler/vendor/thor/parser/argument.rb +0 -0
- data/lib/bundler/vendor/thor/parser/arguments.rb +2 -2
- data/lib/bundler/vendor/thor/parser/option.rb +0 -0
- data/lib/bundler/vendor/thor/parser/options.rb +17 -16
- data/lib/bundler/vendor/thor/rake_compat.rb +66 -0
- data/lib/bundler/vendor/thor/runner.rb +309 -0
- data/lib/bundler/vendor/thor/shell.rb +0 -0
- data/lib/bundler/vendor/thor/shell/basic.rb +40 -13
- data/lib/bundler/vendor/thor/shell/color.rb +0 -0
- data/lib/bundler/vendor/thor/task.rb +3 -4
- data/lib/bundler/vendor/thor/util.rb +2 -2
- data/lib/bundler/vendor/thor/version.rb +1 -1
- data/lib/bundler/version.rb +1 -1
- data/man/gemfile.5.ronn +3 -0
- data/spec/cache/git_spec.rb +5 -2
- data/spec/install/gems/dependency_api_spec.rb +69 -0
- data/spec/install/gems/simple_case_spec.rb +7 -0
- data/spec/install/gems/standalone_spec.rb +62 -0
- data/spec/install/git_spec.rb +63 -1
- data/spec/other/clean_spec.rb +22 -26
- data/spec/other/exec_spec.rb +2 -2
- data/spec/other/gem_helper_spec.rb +1 -1
- data/spec/runtime/setup_spec.rb +1 -1
- data/spec/support/artifice/endpoint.rb +2 -2
- data/spec/support/artifice/endpoint_api_missing.rb +16 -0
- data/spec/support/artifice/endpoint_extra.rb +27 -0
- data/spec/support/artifice/endpoint_extra_missing.rb +15 -0
- data/spec/support/helpers.rb +7 -6
- metadata +73 -40
- data/spec/pack/gems_spec.rb +0 -22
@@ -18,7 +18,7 @@ class Thor
|
|
18
18
|
# "vhost.name = #{hostname}"
|
19
19
|
# end
|
20
20
|
#
|
21
|
-
# create_file "config/
|
21
|
+
# create_file "config/apache.conf", "your apache config"
|
22
22
|
#
|
23
23
|
def create_file(destination, *args, &block)
|
24
24
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
@@ -27,7 +27,7 @@ class Thor
|
|
27
27
|
end
|
28
28
|
alias :add_file :create_file
|
29
29
|
|
30
|
-
#
|
30
|
+
# CreateFile is a subset of Template, which instead of rendering a file with
|
31
31
|
# ERB, it gets the content from the user.
|
32
32
|
#
|
33
33
|
class CreateFile < EmptyDirectory #:nodoc:
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'thor/actions/create_file'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
# Create a new file relative to the destination root from the given source.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# destination<String>:: the relative path to the destination root.
|
10
|
+
# source<String|NilClass>:: the relative path to the source root.
|
11
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
12
|
+
# :: give :symbolic => false for hard link.
|
13
|
+
#
|
14
|
+
# ==== Examples
|
15
|
+
#
|
16
|
+
# create_link "config/apache.conf", "/etc/apache.conf"
|
17
|
+
#
|
18
|
+
def create_link(destination, *args, &block)
|
19
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
20
|
+
source = args.first
|
21
|
+
action CreateLink.new(self, destination, source, config)
|
22
|
+
end
|
23
|
+
alias :add_link :create_link
|
24
|
+
|
25
|
+
# CreateLink is a subset of CreateFile, which instead of taking a block of
|
26
|
+
# data, just takes a source string from the user.
|
27
|
+
#
|
28
|
+
class CreateLink < CreateFile #:nodoc:
|
29
|
+
attr_reader :data
|
30
|
+
|
31
|
+
# Checks if the content of the file at the destination is identical to the rendered result.
|
32
|
+
#
|
33
|
+
# ==== Returns
|
34
|
+
# Boolean:: true if it is identical, false otherwise.
|
35
|
+
#
|
36
|
+
def identical?
|
37
|
+
exists? && File.identical?(render, destination)
|
38
|
+
end
|
39
|
+
|
40
|
+
def invoke!
|
41
|
+
invoke_with_conflict_check do
|
42
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
43
|
+
# Create a symlink by default
|
44
|
+
config[:symbolic] ||= true
|
45
|
+
File.unlink(destination) if exists?
|
46
|
+
if config[:symbolic]
|
47
|
+
File.symlink(render, destination)
|
48
|
+
else
|
49
|
+
File.link(render, destination)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
given_destination
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -21,7 +21,7 @@ class Thor
|
|
21
21
|
# directory "doc"
|
22
22
|
#
|
23
23
|
# It will create a doc directory in the destination with the following
|
24
|
-
# files (assuming that the app_name
|
24
|
+
# files (assuming that the `app_name` method returns the value "blog"):
|
25
25
|
#
|
26
26
|
# doc/
|
27
27
|
# components/
|
@@ -70,7 +70,7 @@ class Thor
|
|
70
70
|
lookup = config[:recursive] ? File.join(source, '**') : source
|
71
71
|
lookup = File.join(lookup, '{*,.[a-z]*}')
|
72
72
|
|
73
|
-
Dir[lookup].each do |file_source|
|
73
|
+
Dir[lookup].sort.each do |file_source|
|
74
74
|
next if File.directory?(file_source)
|
75
75
|
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
|
76
76
|
file_destination.gsub!('/./', '/')
|
File without changes
|
@@ -30,6 +30,28 @@ class Thor
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
# Links the file from the relative source to the relative destination. If
|
34
|
+
# the destination is not given it's assumed to be equal to the source.
|
35
|
+
#
|
36
|
+
# ==== Parameters
|
37
|
+
# source<String>:: the relative path to the source root.
|
38
|
+
# destination<String>:: the relative path to the destination root.
|
39
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
40
|
+
#
|
41
|
+
# ==== Examples
|
42
|
+
#
|
43
|
+
# link_file "README", "doc/README"
|
44
|
+
#
|
45
|
+
# link_file "doc/README"
|
46
|
+
#
|
47
|
+
def link_file(source, *args, &block)
|
48
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
49
|
+
destination = args.first || source
|
50
|
+
source = File.expand_path(find_in_source_paths(source.to_s))
|
51
|
+
|
52
|
+
create_link destination, source, config
|
53
|
+
end
|
54
|
+
|
33
55
|
# Gets the content at the given address and places it at the given relative
|
34
56
|
# destination. If a block is given instead of destination, the content of
|
35
57
|
# the url is yielded and used as location.
|
@@ -51,7 +73,7 @@ class Thor
|
|
51
73
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
52
74
|
destination = args.first
|
53
75
|
|
54
|
-
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^
|
76
|
+
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^https?\:\/\//
|
55
77
|
render = open(source) {|input| input.binmode.read }
|
56
78
|
|
57
79
|
destination ||= if block_given?
|
@@ -80,13 +102,13 @@ class Thor
|
|
80
102
|
#
|
81
103
|
def template(source, *args, &block)
|
82
104
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
83
|
-
destination = args.first || source
|
105
|
+
destination = args.first || source.sub(/\.tt$/, '')
|
84
106
|
|
85
107
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
86
108
|
context = instance_eval('binding')
|
87
109
|
|
88
110
|
create_file destination, nil, config do
|
89
|
-
content = ERB.new(::File.binread(source), nil, '-').result(context)
|
111
|
+
content = ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
|
90
112
|
content = block.call(content) if block
|
91
113
|
content
|
92
114
|
end
|
@@ -110,7 +132,7 @@ class Thor
|
|
110
132
|
FileUtils.chmod_R(mode, path) unless options[:pretend]
|
111
133
|
end
|
112
134
|
|
113
|
-
# Prepend text to a file. Since it depends on
|
135
|
+
# Prepend text to a file. Since it depends on insert_into_file, it's reversible.
|
114
136
|
#
|
115
137
|
# ==== Parameters
|
116
138
|
# path<String>:: path of the file to be changed
|
@@ -119,19 +141,20 @@ class Thor
|
|
119
141
|
#
|
120
142
|
# ==== Example
|
121
143
|
#
|
122
|
-
#
|
144
|
+
# prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
|
123
145
|
#
|
124
|
-
#
|
146
|
+
# prepend_to_file 'config/environments/test.rb' do
|
125
147
|
# 'config.gem "rspec"'
|
126
148
|
# end
|
127
149
|
#
|
128
|
-
def
|
150
|
+
def prepend_to_file(path, *args, &block)
|
129
151
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
130
152
|
config.merge!(:after => /\A/)
|
131
|
-
|
153
|
+
insert_into_file(path, *(args << config), &block)
|
132
154
|
end
|
155
|
+
alias_method :prepend_file, :prepend_to_file
|
133
156
|
|
134
|
-
# Append text to a file. Since it depends on
|
157
|
+
# Append text to a file. Since it depends on insert_into_file, it's reversible.
|
135
158
|
#
|
136
159
|
# ==== Parameters
|
137
160
|
# path<String>:: path of the file to be changed
|
@@ -140,20 +163,21 @@ class Thor
|
|
140
163
|
#
|
141
164
|
# ==== Example
|
142
165
|
#
|
143
|
-
#
|
166
|
+
# append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
|
144
167
|
#
|
145
|
-
#
|
168
|
+
# append_to_file 'config/environments/test.rb' do
|
146
169
|
# 'config.gem "rspec"'
|
147
170
|
# end
|
148
171
|
#
|
149
|
-
def
|
172
|
+
def append_to_file(path, *args, &block)
|
150
173
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
151
174
|
config.merge!(:before => /\z/)
|
152
|
-
|
175
|
+
insert_into_file(path, *(args << config), &block)
|
153
176
|
end
|
177
|
+
alias_method :append_file, :append_to_file
|
154
178
|
|
155
179
|
# Injects text right after the class definition. Since it depends on
|
156
|
-
#
|
180
|
+
# insert_into_file, it's reversible.
|
157
181
|
#
|
158
182
|
# ==== Parameters
|
159
183
|
# path<String>:: path of the file to be changed
|
@@ -172,7 +196,7 @@ class Thor
|
|
172
196
|
def inject_into_class(path, klass, *args, &block)
|
173
197
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
174
198
|
config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
|
175
|
-
|
199
|
+
insert_into_file(path, *(args << config), &block)
|
176
200
|
end
|
177
201
|
|
178
202
|
# Run a regular expression replacement on a file.
|
@@ -225,5 +249,22 @@ class Thor
|
|
225
249
|
end
|
226
250
|
alias :remove_dir :remove_file
|
227
251
|
|
252
|
+
private
|
253
|
+
attr_accessor :output_buffer
|
254
|
+
def concat(string)
|
255
|
+
@output_buffer.concat(string)
|
256
|
+
end
|
257
|
+
|
258
|
+
def capture(*args, &block)
|
259
|
+
with_output_buffer { block.call(*args) }
|
260
|
+
end
|
261
|
+
|
262
|
+
def with_output_buffer(buf = '') #:nodoc:
|
263
|
+
self.output_buffer, old_buffer = buf, output_buffer
|
264
|
+
yield
|
265
|
+
output_buffer
|
266
|
+
ensure
|
267
|
+
self.output_buffer = old_buffer
|
268
|
+
end
|
228
269
|
end
|
229
270
|
end
|
@@ -15,14 +15,14 @@ class Thor
|
|
15
15
|
#
|
16
16
|
# ==== Examples
|
17
17
|
#
|
18
|
-
#
|
18
|
+
# insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
|
19
19
|
#
|
20
|
-
#
|
20
|
+
# insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
|
21
21
|
# gems = ask "Which gems would you like to add?"
|
22
22
|
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
def
|
25
|
+
def insert_into_file(destination, *args, &block)
|
26
26
|
if block_given?
|
27
27
|
data, config = block, args.shift
|
28
28
|
else
|
@@ -30,6 +30,7 @@ class Thor
|
|
30
30
|
end
|
31
31
|
action InjectIntoFile.new(self, destination, data, config)
|
32
32
|
end
|
33
|
+
alias_method :inject_into_file, :insert_into_file
|
33
34
|
|
34
35
|
class InjectIntoFile < EmptyDirectory #:nodoc:
|
35
36
|
attr_reader :replacement, :flag, :behavior
|
@@ -76,12 +77,16 @@ class Thor
|
|
76
77
|
protected
|
77
78
|
|
78
79
|
def say_status(behavior)
|
79
|
-
status = if
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
status = if behavior == :invoke
|
81
|
+
if flag == /\A/
|
82
|
+
:prepend
|
83
|
+
elsif flag == /\z/
|
84
|
+
:append
|
85
|
+
else
|
86
|
+
:insert
|
87
|
+
end
|
83
88
|
else
|
84
|
-
|
89
|
+
:subtract
|
85
90
|
end
|
86
91
|
|
87
92
|
super(status, config[:verbose])
|
@@ -94,8 +94,6 @@ class Thor
|
|
94
94
|
end
|
95
95
|
|
96
96
|
module ClassMethods
|
97
|
-
attr_accessor :debugging
|
98
|
-
|
99
97
|
def attr_reader(*) #:nodoc:
|
100
98
|
no_tasks { super }
|
101
99
|
end
|
@@ -384,14 +382,29 @@ class Thor
|
|
384
382
|
# script.invoke(:task, first_arg, second_arg, third_arg)
|
385
383
|
#
|
386
384
|
def start(given_args=ARGV, config={})
|
387
|
-
self.debugging = given_args.delete("--debug")
|
388
385
|
config[:shell] ||= Thor::Base.shell.new
|
389
386
|
dispatch(nil, given_args.dup, nil, config)
|
390
387
|
rescue Thor::Error => e
|
391
|
-
|
388
|
+
ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
|
392
389
|
exit(1) if exit_on_failure?
|
393
390
|
end
|
394
391
|
|
392
|
+
# Allows to use private methods from parent in child classes as tasks.
|
393
|
+
#
|
394
|
+
# ==== Paremeters
|
395
|
+
# names<Array>:: Method names to be used as tasks
|
396
|
+
#
|
397
|
+
# ==== Examples
|
398
|
+
#
|
399
|
+
# public_task :foo
|
400
|
+
# public_task :foo, :bar, :baz
|
401
|
+
#
|
402
|
+
def public_task(*names)
|
403
|
+
names.each do |name|
|
404
|
+
class_eval "def #{name}(*); super end"
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
395
408
|
def handle_no_task_error(task) #:nodoc:
|
396
409
|
if $thor_runner
|
397
410
|
raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
|
@@ -531,6 +544,13 @@ class Thor
|
|
531
544
|
false
|
532
545
|
end
|
533
546
|
|
547
|
+
#
|
548
|
+
# The basename of the program invoking the thor class.
|
549
|
+
#
|
550
|
+
def basename
|
551
|
+
File.basename($0).split(' ').first
|
552
|
+
end
|
553
|
+
|
534
554
|
# SIGNATURE: Sets the baseclass. This is where the superclass lookup
|
535
555
|
# finishes.
|
536
556
|
def baseclass #:nodoc:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,273 @@
|
|
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 giong 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, task = 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 |option|
|
191
|
+
base_options[option.name.to_sym].nil? && option.group.nil? &&
|
192
|
+
!group_options.values.flatten.any? { |i| i.name == 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) #:nodoc:
|
208
|
+
raise error, "#{task.name.inspect} was called incorrectly. Are you sure it has arity equals to 0?"
|
209
|
+
end
|
210
|
+
|
211
|
+
protected
|
212
|
+
|
213
|
+
# The method responsible for dispatching given the args.
|
214
|
+
def dispatch(task, given_args, given_opts, config) #:nodoc:
|
215
|
+
if Thor::HELP_MAPPINGS.include?(given_args.first)
|
216
|
+
help(config[:shell])
|
217
|
+
return
|
218
|
+
end
|
219
|
+
|
220
|
+
args, opts = Thor::Options.split(given_args)
|
221
|
+
opts = given_opts || opts
|
222
|
+
|
223
|
+
if task
|
224
|
+
new(args, opts, config).invoke_task(all_tasks[task])
|
225
|
+
else
|
226
|
+
new(args, opts, config).invoke_all
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# The banner for this class. You can customize it if you are invoking the
|
231
|
+
# thor class by another ways which is not the Thor::Runner.
|
232
|
+
def banner
|
233
|
+
"#{basename} #{self_task.formatted_usage(self, false)}"
|
234
|
+
end
|
235
|
+
|
236
|
+
# Represents the whole class as a task.
|
237
|
+
def self_task #:nodoc:
|
238
|
+
Thor::DynamicTask.new(self.namespace, class_options)
|
239
|
+
end
|
240
|
+
|
241
|
+
def baseclass #:nodoc:
|
242
|
+
Thor::Group
|
243
|
+
end
|
244
|
+
|
245
|
+
def create_task(meth) #:nodoc:
|
246
|
+
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, nil)
|
247
|
+
true
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
include Thor::Base
|
252
|
+
|
253
|
+
protected
|
254
|
+
|
255
|
+
# Shortcut to invoke with padding and block handling. Use internally by
|
256
|
+
# invoke and invoke_from_option class methods.
|
257
|
+
def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
|
258
|
+
with_padding do
|
259
|
+
if block
|
260
|
+
case block.arity
|
261
|
+
when 3
|
262
|
+
block.call(self, klass, task)
|
263
|
+
when 2
|
264
|
+
block.call(self, klass)
|
265
|
+
when 1
|
266
|
+
instance_exec(klass, &block)
|
267
|
+
end
|
268
|
+
else
|
269
|
+
invoke klass, task, *args
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|