bundler 1.2.5 → 1.3.0.pre

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.

Files changed (124) hide show
  1. data/.gitignore +10 -7
  2. data/.travis.yml +12 -3
  3. data/CHANGELOG.md +26 -19
  4. data/CONTRIBUTE.md +97 -0
  5. data/README.md +4 -2
  6. data/Rakefile +17 -59
  7. data/bundler.gemspec +2 -1
  8. data/lib/bundler.rb +23 -20
  9. data/lib/bundler/cli.rb +68 -22
  10. data/lib/bundler/definition.rb +3 -2
  11. data/lib/bundler/deprecate.rb +15 -0
  12. data/lib/bundler/dsl.rb +14 -16
  13. data/lib/bundler/environment.rb +0 -5
  14. data/lib/bundler/fetcher.rb +23 -78
  15. data/lib/bundler/friendly_errors.rb +4 -5
  16. data/lib/bundler/gem_helper.rb +14 -16
  17. data/lib/bundler/injector.rb +64 -0
  18. data/lib/bundler/installer.rb +1 -7
  19. data/lib/bundler/lazy_specification.rb +6 -3
  20. data/lib/bundler/lockfile_parser.rb +25 -13
  21. data/lib/bundler/resolver.rb +0 -1
  22. data/lib/bundler/rubygems_integration.rb +83 -17
  23. data/lib/bundler/settings.rb +4 -2
  24. data/lib/bundler/similarity_detector.rb +63 -0
  25. data/lib/bundler/source.rb +3 -886
  26. data/lib/bundler/source/git.rb +267 -0
  27. data/lib/bundler/source/git/git_proxy.rb +142 -0
  28. data/lib/bundler/source/path.rb +209 -0
  29. data/lib/bundler/source/path/installer.rb +33 -0
  30. data/lib/bundler/source/rubygems.rb +261 -0
  31. data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -0
  32. data/lib/bundler/templates/newgem/rspec.tt +2 -0
  33. data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +9 -0
  34. data/lib/bundler/templates/newgem/spec/spec_helper.rb.tt +2 -0
  35. data/lib/bundler/templates/newgem/test/minitest_helper.rb.tt +4 -0
  36. data/lib/bundler/templates/newgem/test/test_newgem.rb.tt +11 -0
  37. data/lib/bundler/ui.rb +20 -5
  38. data/lib/bundler/vendor/.document +0 -0
  39. data/lib/bundler/vendor/thor.rb +74 -5
  40. data/lib/bundler/vendor/thor/actions.rb +5 -5
  41. data/lib/bundler/vendor/thor/actions/directory.rb +1 -0
  42. data/lib/bundler/vendor/thor/actions/file_manipulation.rb +7 -1
  43. data/lib/bundler/vendor/thor/base.rb +44 -11
  44. data/lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb +5 -0
  45. data/lib/bundler/vendor/thor/parser/argument.rb +14 -7
  46. data/lib/bundler/vendor/thor/parser/arguments.rb +7 -1
  47. data/lib/bundler/vendor/thor/parser/option.rb +8 -8
  48. data/lib/bundler/vendor/thor/parser/options.rb +62 -24
  49. data/lib/bundler/vendor/thor/runner.rb +1 -1
  50. data/lib/bundler/vendor/thor/shell/basic.rb +2 -2
  51. data/lib/bundler/vendor/thor/task.rb +2 -2
  52. data/lib/bundler/vendor/thor/version.rb +1 -1
  53. data/lib/bundler/vendored_persistent.rb +3 -15
  54. data/lib/bundler/version.rb +1 -1
  55. data/man/bundle-exec.ronn +1 -1
  56. data/man/bundle-update.ronn +1 -1
  57. data/man/bundle.ronn +4 -1
  58. data/spec/bundler/bundler_spec.rb +2 -28
  59. data/spec/bundler/cli_rspec.rb +9 -0
  60. data/spec/bundler/definition_spec.rb +1 -1
  61. data/spec/bundler/dsl_spec.rb +15 -8
  62. data/spec/bundler/gem_helper_spec.rb +38 -21
  63. data/spec/bundler/psyched_yaml_spec.rb +1 -0
  64. data/spec/bundler/source_spec.rb +3 -3
  65. data/spec/cache/gems_spec.rb +24 -24
  66. data/spec/cache/git_spec.rb +21 -23
  67. data/spec/cache/path_spec.rb +11 -11
  68. data/spec/cache/platform_spec.rb +6 -6
  69. data/spec/install/deploy_spec.rb +38 -38
  70. data/spec/install/gems/c_ext_spec.rb +2 -2
  71. data/spec/install/gems/dependency_api_spec.rb +23 -116
  72. data/spec/install/gems/env_spec.rb +1 -1
  73. data/spec/install/gems/flex_spec.rb +7 -8
  74. data/spec/install/gems/groups_spec.rb +10 -10
  75. data/spec/install/gems/packed_spec.rb +4 -4
  76. data/spec/install/gems/platform_spec.rb +3 -3
  77. data/spec/install/gems/post_install_spec.rb +9 -9
  78. data/spec/install/gems/resolving_spec.rb +2 -2
  79. data/spec/install/gems/simple_case_spec.rb +50 -53
  80. data/spec/install/gems/standalone_spec.rb +19 -19
  81. data/spec/install/gems/sudo_spec.rb +31 -16
  82. data/spec/install/gems/win32_spec.rb +1 -1
  83. data/spec/install/gemspec_spec.rb +6 -6
  84. data/spec/install/git_spec.rb +34 -34
  85. data/spec/install/invalid_spec.rb +3 -3
  86. data/spec/install/path_spec.rb +71 -8
  87. data/spec/install/upgrade_spec.rb +2 -2
  88. data/spec/integration/inject.rb +78 -0
  89. data/spec/lock/git_spec.rb +2 -2
  90. data/spec/lock/lockfile_spec.rb +14 -14
  91. data/spec/other/check_spec.rb +29 -29
  92. data/spec/other/clean_spec.rb +47 -48
  93. data/spec/other/config_spec.rb +20 -20
  94. data/spec/other/console_spec.rb +5 -5
  95. data/spec/other/exec_spec.rb +48 -28
  96. data/spec/other/ext_spec.rb +3 -3
  97. data/spec/other/help_spec.rb +6 -6
  98. data/spec/other/init_spec.rb +8 -8
  99. data/spec/other/newgem_spec.rb +95 -15
  100. data/spec/other/open_spec.rb +10 -5
  101. data/spec/other/outdated_spec.rb +8 -8
  102. data/spec/other/platform_spec.rb +45 -45
  103. data/spec/other/show_spec.rb +10 -10
  104. data/spec/quality_spec.rb +2 -2
  105. data/spec/realworld/dependency_api_spec.rb +61 -0
  106. data/spec/realworld/edgecases_spec.rb +8 -8
  107. data/spec/runtime/executable_spec.rb +13 -13
  108. data/spec/runtime/load_spec.rb +12 -12
  109. data/spec/runtime/platform_spec.rb +1 -1
  110. data/spec/runtime/require_spec.rb +24 -24
  111. data/spec/runtime/setup_spec.rb +113 -56
  112. data/spec/runtime/with_clean_env_spec.rb +11 -13
  113. data/spec/spec_helper.rb +6 -0
  114. data/spec/support/artifice/endpoint.rb +28 -13
  115. data/spec/support/artifice/endpoint_extra.rb +4 -0
  116. data/spec/support/builders.rb +1 -1
  117. data/spec/support/helpers.rb +2 -7
  118. data/spec/support/indexes.rb +3 -3
  119. data/spec/support/matchers.rb +6 -6
  120. data/spec/update/gems_spec.rb +19 -8
  121. data/spec/update/git_spec.rb +10 -10
  122. data/spec/update/source_spec.rb +1 -1
  123. metadata +86 -55
  124. data/.rspec +0 -2
@@ -0,0 +1,33 @@
1
+ module Bundler
2
+ module Source
3
+ class Path
4
+
5
+ class Installer < Bundler::GemInstaller
6
+ def initialize(spec, options = {})
7
+ @spec = spec
8
+ @bin_dir = Bundler.requires_sudo? ? "#{Bundler.tmp}/bin" : "#{Bundler.rubygems.gem_dir}/bin"
9
+ @gem_dir = Bundler.rubygems.path(spec.full_gem_path)
10
+ @wrappers = options[:wrappers] || true
11
+ @env_shebang = options[:env_shebang] || true
12
+ @format_executable = options[:format_executable] || false
13
+ end
14
+
15
+ def generate_bin
16
+ return if spec.executables.nil? || spec.executables.empty?
17
+
18
+ if Bundler.requires_sudo?
19
+ FileUtils.mkdir_p("#{Bundler.tmp}/bin") unless File.exist?("#{Bundler.tmp}/bin")
20
+ end
21
+ super
22
+ if Bundler.requires_sudo?
23
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/bin"
24
+ spec.executables.each do |exe|
25
+ Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.rubygems.gem_dir}/bin/"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,261 @@
1
+ require 'uri'
2
+ require 'rubygems/user_interaction'
3
+ require 'rubygems/spec_fetcher'
4
+
5
+ module Bundler
6
+ module Source
7
+ # TODO: Refactor this class
8
+ class Rubygems
9
+ FORCE_MODERN_INDEX_LIMIT = 100 # threshold for switching back to the modern index instead of fetching every spec
10
+
11
+ attr_reader :remotes, :caches
12
+ attr_accessor :dependency_names
13
+
14
+ def initialize(options = {})
15
+ @options = options
16
+ @remotes = (options["remotes"] || []).map { |r| normalize_uri(r) }
17
+ @fetchers = {}
18
+ @allow_remote = false
19
+ @allow_cached = false
20
+
21
+ @caches = [ Bundler.app_cache ] +
22
+ Bundler.rubygems.gem_path.map{|p| File.expand_path("#{p}/cache") }
23
+ end
24
+
25
+ def remote!
26
+ @allow_remote = true
27
+ end
28
+
29
+ def cached!
30
+ @allow_cached = true
31
+ end
32
+
33
+ def hash
34
+ Rubygems.hash
35
+ end
36
+
37
+ def eql?(o)
38
+ Rubygems === o
39
+ end
40
+
41
+ alias == eql?
42
+
43
+ def options
44
+ { "remotes" => @remotes.map { |r| r.to_s } }
45
+ end
46
+
47
+ def self.from_lock(options)
48
+ s = new(options)
49
+ Array(options["remote"]).each { |r| s.add_remote(r) }
50
+ s
51
+ end
52
+
53
+ def to_lock
54
+ out = "GEM\n"
55
+ out << remotes.map {|r| " remote: #{r}\n" }.join
56
+ out << " specs:\n"
57
+ end
58
+
59
+ def to_s
60
+ remote_names = self.remotes.map { |r| r.to_s }.join(', ')
61
+ "rubygems repository #{remote_names}"
62
+ end
63
+ alias_method :name, :to_s
64
+
65
+ def specs
66
+ @specs ||= fetch_specs
67
+ end
68
+
69
+ def install(spec)
70
+ if installed_specs[spec].any?
71
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) "
72
+ return
73
+ end
74
+
75
+ Bundler.ui.info "Installing #{spec.name} (#{spec.version}) "
76
+ path = cached_gem(spec)
77
+ if Bundler.requires_sudo?
78
+ install_path = Bundler.tmp
79
+ bin_path = install_path.join("bin")
80
+ else
81
+ install_path = Bundler.rubygems.gem_dir
82
+ bin_path = Bundler.system_bindir
83
+ end
84
+
85
+ installed_spec = nil
86
+ Bundler.rubygems.preserve_paths do
87
+ installed_spec = Bundler::GemInstaller.new(path,
88
+ :install_dir => install_path.to_s,
89
+ :bin_dir => bin_path.to_s,
90
+ :ignore_dependencies => true,
91
+ :wrappers => true,
92
+ :env_shebang => true
93
+ ).install
94
+ end
95
+
96
+ if spec.post_install_message
97
+ Installer.post_install_messages[spec.name] = spec.post_install_message
98
+ end
99
+
100
+ # SUDO HAX
101
+ if Bundler.requires_sudo?
102
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/gems"
103
+ Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/specifications"
104
+ Bundler.sudo "cp -R #{Bundler.tmp}/gems/#{spec.full_name} #{Bundler.rubygems.gem_dir}/gems/"
105
+ Bundler.sudo "cp -R #{Bundler.tmp}/specifications/#{spec.full_name}.gemspec #{Bundler.rubygems.gem_dir}/specifications/"
106
+ spec.executables.each do |exe|
107
+ Bundler.mkdir_p Bundler.system_bindir
108
+ Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.system_bindir}"
109
+ end
110
+ end
111
+ installed_spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
112
+ spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
113
+ end
114
+
115
+ def cache(spec)
116
+ cached_path = cached_gem(spec)
117
+ raise GemNotFound, "Missing gem file '#{spec.full_name}.gem'." unless cached_path
118
+ return if File.dirname(cached_path) == Bundler.app_cache.to_s
119
+ Bundler.ui.info " * #{File.basename(cached_path)}"
120
+ FileUtils.cp(cached_path, Bundler.app_cache)
121
+ end
122
+
123
+ def add_remote(source)
124
+ @remotes << normalize_uri(source)
125
+ end
126
+
127
+ def replace_remotes(source)
128
+ return false if source.remotes == @remotes
129
+
130
+ @remotes = []
131
+ source.remotes.each do |r|
132
+ add_remote r.to_s
133
+ end
134
+
135
+ true
136
+ end
137
+
138
+ private
139
+
140
+ def cached_gem(spec)
141
+ possibilities = @caches.map { |p| "#{p}/#{spec.file_name}" }
142
+ cached_gem = possibilities.find { |p| File.exist?(p) }
143
+ unless cached_gem
144
+ raise Bundler::GemNotFound, "Could not find #{spec.file_name} for installation"
145
+ end
146
+ cached_gem
147
+ end
148
+
149
+ def normalize_uri(uri)
150
+ uri = uri.to_s
151
+ uri = "#{uri}/" unless uri =~ %r'/$'
152
+ uri = URI(uri)
153
+ raise ArgumentError, "The source must be an absolute URI" unless uri.absolute?
154
+ uri
155
+ end
156
+
157
+ def fetch_specs
158
+ # remote_specs usually generates a way larger Index than the other
159
+ # sources, and large_idx.use small_idx is way faster than
160
+ # small_idx.use large_idx.
161
+ if @allow_remote
162
+ idx = remote_specs.dup
163
+ else
164
+ idx = Index.new
165
+ end
166
+ idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
167
+ idx.use(installed_specs, :override_dupes)
168
+ idx
169
+ end
170
+
171
+ def installed_specs
172
+ @installed_specs ||= begin
173
+ idx = Index.new
174
+ have_bundler = false
175
+ Bundler.rubygems.all_specs.reverse.each do |spec|
176
+ next if spec.name == 'bundler' && spec.version.to_s != VERSION
177
+ have_bundler = true if spec.name == 'bundler'
178
+ spec.source = self
179
+ idx << spec
180
+ end
181
+
182
+ # Always have bundler locally
183
+ unless have_bundler
184
+ # We're running bundler directly from the source
185
+ # so, let's create a fake gemspec for it (it's a path)
186
+ # gemspec
187
+ bundler = Gem::Specification.new do |s|
188
+ s.name = 'bundler'
189
+ s.version = VERSION
190
+ s.platform = Gem::Platform::RUBY
191
+ s.source = self
192
+ s.authors = ["bundler team"]
193
+ s.loaded_from = File.expand_path("..", __FILE__)
194
+ end
195
+ idx << bundler
196
+ end
197
+ idx
198
+ end
199
+ end
200
+
201
+ def cached_specs
202
+ @cached_specs ||= begin
203
+ idx = installed_specs.dup
204
+
205
+ path = Bundler.app_cache
206
+ Dir["#{path}/*.gem"].each do |gemfile|
207
+ next if gemfile =~ /^bundler\-[\d\.]+?\.gem/
208
+ s ||= Bundler.rubygems.spec_from_gem(gemfile)
209
+ s.source = self
210
+ idx << s
211
+ end
212
+ end
213
+
214
+ idx
215
+ end
216
+
217
+ def remote_specs
218
+ @remote_specs ||= begin
219
+ idx = Index.new
220
+ old = Bundler.rubygems.sources
221
+
222
+ sources = {}
223
+ remotes.each do |uri|
224
+ fetcher = Bundler::Fetcher.new(uri)
225
+ specs = fetcher.specs(dependency_names, self)
226
+ sources[fetcher] = specs.size
227
+
228
+ idx.use specs
229
+ end
230
+
231
+ # don't need to fetch all specifications for every gem/version on
232
+ # the rubygems repo if there's no api endpoints to search over
233
+ # or it has too many specs to fetch
234
+ fetchers = sources.keys
235
+ api_fetchers = fetchers.select {|fetcher| fetcher.has_api }
236
+ modern_index_fetchers = fetchers - api_fetchers
237
+ if api_fetchers.any? && modern_index_fetchers.all? {|fetcher| sources[fetcher] < FORCE_MODERN_INDEX_LIMIT }
238
+ # this will fetch all the specifications on the rubygems repo
239
+ unmet_dependency_names = idx.unmet_dependency_names
240
+ unmet_dependency_names -= ['bundler'] # bundler will always be unmet
241
+
242
+ Bundler.ui.debug "Unmet Dependencies: #{unmet_dependency_names}"
243
+ if unmet_dependency_names.any?
244
+ api_fetchers.each do |fetcher|
245
+ idx.use fetcher.specs(unmet_dependency_names, self)
246
+ end
247
+ end
248
+ else
249
+ Bundler::Fetcher.disable_endpoint = true
250
+ api_fetchers.each {|fetcher| idx.use fetcher.specs([], self) }
251
+ end
252
+
253
+ idx
254
+ ensure
255
+ Bundler.rubygems.sources = old
256
+ end
257
+ end
258
+ end
259
+
260
+ end
261
+ end
@@ -11,9 +11,12 @@ Gem::Specification.new do |gem|
11
11
  gem.description = %q{TODO: Write a gem description}
12
12
  gem.summary = %q{TODO: Write a gem summary}
13
13
  gem.homepage = ""
14
+ gem.license = "MIT"
14
15
 
15
16
  gem.files = `git ls-files`.split($/)
16
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
19
  gem.require_paths = ["lib"]
20
+
21
+ gem.add_development_dependency "rake"
19
22
  end
@@ -0,0 +1,2 @@
1
+ --format specdoc
2
+ --color
@@ -0,0 +1,9 @@
1
+ describe <%= config[:constant_name] %> do
2
+ it 'should have a version number' do
3
+ <%= config[:constant_name] %>::VERSION.should_not be_nil
4
+ end
5
+
6
+ it 'should do something useful' do
7
+ false.should be_true
8
+ end
9
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require '<%= config[:name] %>'
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require '<%= config[:name] %>'
3
+
4
+ require 'minitest/autorun'
@@ -0,0 +1,11 @@
1
+ require './minitest_helper'
2
+
3
+ class Test<%= config[:constant_name] %> < MiniTest::Unit::TestCase
4
+ def test_that_it_has_a_version_number
5
+ refute_nil ::<%= config[:constant_name] %>::VERSION
6
+ end
7
+
8
+ def test_it_does_something_useful
9
+ assert false
10
+ end
11
+ end
@@ -8,6 +8,9 @@ module Bundler
8
8
  def debug(message, newline = nil)
9
9
  end
10
10
 
11
+ def trace(message, newline = nil)
12
+ end
13
+
11
14
  def error(message, newline = nil)
12
15
  end
13
16
 
@@ -22,13 +25,16 @@ module Bundler
22
25
  end
23
26
 
24
27
  class Shell < UI
25
- attr_reader :quiet
26
28
  attr_writer :shell
27
29
 
28
- def initialize(shell)
29
- @shell = shell
30
+ def initialize(options = {})
31
+ if options["no-color"] || !STDOUT.tty?
32
+ Thor::Base.shell = Thor::Shell::Basic
33
+ end
34
+ @shell = Thor::Base.shell.new
30
35
  @quiet = false
31
36
  @debug = ENV['DEBUG']
37
+ @trace = ENV['TRACE']
32
38
  end
33
39
 
34
40
  def info(msg, newline = nil)
@@ -47,8 +53,8 @@ module Bundler
47
53
  tell_me(msg, :red, newline)
48
54
  end
49
55
 
50
- def quiet=(value)
51
- @quiet = value
56
+ def be_quiet!
57
+ @quiet = true
52
58
  end
53
59
 
54
60
  def debug?
@@ -64,6 +70,15 @@ module Bundler
64
70
  tell_me(msg, nil, newline) if debug?
65
71
  end
66
72
 
73
+ def trace(e, newline = nil)
74
+ msg = ["#{e.class}: #{e.message}", *e.backtrace].join("\n")
75
+ if debug?
76
+ tell_me(msg, nil, newline)
77
+ elsif @trace
78
+ STDERR.puts "#{msg}#{newline}"
79
+ end
80
+ end
81
+
67
82
  private
68
83
  # valimism
69
84
  def tell_me(msg, color = nil, newline = nil)
File without changes
@@ -1,3 +1,4 @@
1
+ require 'set'
1
2
  require 'thor/base'
2
3
 
3
4
  class Thor
@@ -210,7 +211,7 @@ class Thor
210
211
 
211
212
  define_method(subcommand) do |*args|
212
213
  args, opts = Thor::Arguments.split(args)
213
- invoke subcommand_class, args, opts
214
+ invoke subcommand_class, args, opts, :invoked_via_subcommand => true
214
215
  end
215
216
  end
216
217
 
@@ -251,15 +252,83 @@ class Thor
251
252
  end
252
253
  end
253
254
 
255
+ # Stop parsing of options as soon as an unknown option or a regular
256
+ # argument is encountered. All remaining arguments are passed to the task.
257
+ # This is useful if you have a task that can receive arbitrary additional
258
+ # options, and where those additional options should not be handled by
259
+ # Thor.
260
+ #
261
+ # ==== Example
262
+ #
263
+ # To better understand how this is useful, let's consider a task that calls
264
+ # an external command. A user may want to pass arbitrary options and
265
+ # arguments to that command. The task itself also accepts some options,
266
+ # which should be handled by Thor.
267
+ #
268
+ # class_option "verbose", :type => :boolean
269
+ # stop_on_unknown_option! :exec
270
+ # check_unknown_options! :except => :exec
271
+ #
272
+ # desc "exec", "Run a shell command"
273
+ # def exec(*args)
274
+ # puts "diagnostic output" if options[:verbose]
275
+ # Kernel.exec(*args)
276
+ # end
277
+ #
278
+ # Here +exec+ can be called with +--verbose+ to get diagnostic output,
279
+ # e.g.:
280
+ #
281
+ # $ thor exec --verbose echo foo
282
+ # diagnostic output
283
+ # foo
284
+ #
285
+ # But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
286
+ #
287
+ # $ thor exec echo --verbose foo
288
+ # --verbose foo
289
+ #
290
+ # ==== Parameters
291
+ # Symbol ...:: A list of tasks that should be affected.
292
+ def stop_on_unknown_option!(*task_names)
293
+ @stop_on_unknown_option ||= Set.new
294
+ @stop_on_unknown_option.merge(task_names)
295
+ end
296
+
297
+ def stop_on_unknown_option?(task) #:nodoc:
298
+ !!@stop_on_unknown_option && @stop_on_unknown_option.include?(task.name.to_sym)
299
+ end
300
+
254
301
  protected
255
302
 
256
303
  # The method responsible for dispatching given the args.
257
304
  def dispatch(meth, given_args, given_opts, config) #:nodoc:
258
- meth ||= retrieve_task_name(given_args)
259
- task = all_tasks[normalize_task_name(meth)]
305
+ # There is an edge case when dispatching from a subcommand.
306
+ # A problem occurs invoking the default task. This case occurs
307
+ # when arguments are passed and a default task is defined, and
308
+ # the first given_args does not match the default task.
309
+ # Thor use "help" by default so we skip that case.
310
+ # Note the call to retrieve_task_name. It's called with
311
+ # given_args.dup since that method calls args.shift. Then lookup
312
+ # the task normally. If the first item in given_args is not
313
+ # a task then use the default task. The given_args will be
314
+ # intact later since dup was used.
315
+ if config[:invoked_via_subcommand] && given_args.size >= 1 && default_task != "help" && given_args.first != default_task
316
+ meth ||= retrieve_task_name(given_args.dup)
317
+ task = all_tasks[normalize_task_name(meth)]
318
+ task ||= all_tasks[normalize_task_name(default_task)]
319
+ else
320
+ meth ||= retrieve_task_name(given_args)
321
+ task = all_tasks[normalize_task_name(meth)]
322
+ end
260
323
 
261
324
  if task
262
325
  args, opts = Thor::Options.split(given_args)
326
+ if stop_on_unknown_option?(task) && !args.empty?
327
+ # given_args starts with a non-option, so we treat everything as
328
+ # ordinary arguments
329
+ args.concat opts
330
+ opts.clear
331
+ end
263
332
  else
264
333
  args, opts = given_args, nil
265
334
  task = Thor::DynamicTask.new(meth)
@@ -321,7 +390,7 @@ class Thor
321
390
 
322
391
  # receives a (possibly nil) task name and returns a name that is in
323
392
  # the tasks hash. In addition to normalizing aliases, this logic
324
- # will determine if a shortened command is an unambiguous prefix of
393
+ # will determine if a shortened command is an unambiguous substring of
325
394
  # a task or alias.
326
395
  #
327
396
  # +normalize_task_name+ also converts names like +animal-prison+
@@ -344,7 +413,7 @@ class Thor
344
413
  end
345
414
 
346
415
  # this is the logic that takes the task name passed in by the user
347
- # and determines whether it is an unambiguous prefix of a task or
416
+ # and determines whether it is an unambiguous substrings of a task or
348
417
  # alias name.
349
418
  def find_task_possibilities(meth)
350
419
  len = meth.to_s.length