bundler 1.0.0.rc.2 → 1.0.0.rc.3

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 CHANGED
@@ -1,3 +1,21 @@
1
+ ## 1.0.0.rc.2 (August 3, 2010)
2
+
3
+ Features:
4
+
5
+ - Deprecate --production flag for --deployment, since the former
6
+ was causing confusion with the :production group
7
+ - Add --gemfile option to `bundle check`
8
+ - Reduce memory usage of `bundle install` by 2-4x
9
+ - Improve message from `bundle check` under various conditions
10
+ - Better error when a changed Gemfile conflicts with Gemfile.lock
11
+
12
+ Bugfixes:
13
+
14
+ - Create bin/ directory if it is missing, then install binstubs
15
+ - Error nicely on the edge case of a pinned gem with no spec
16
+ - Do not require gems for other platforms
17
+ - Update git sources along with the gems they contain
18
+
1
19
  ## 1.0.0.rc.2 (July 29, 2010)
2
20
 
3
21
  - `bundle install path` was causing confusion, so we now print
data/README.md CHANGED
@@ -135,11 +135,13 @@ If you are still having problems, please report bugs to the github issue tracker
135
135
 
136
136
  The best possible scenario is a ticket with a fix for the bug and a test for the fix. If that's not possible, instructions to reproduce the issue are vitally important. If you're not sure exactly how to reproduce the issue that you are seeing, create a gist of the following information and include it in your ticket:
137
137
 
138
- - Whether you have locked or not
139
138
  - What version of bundler you are using
140
139
  - What version of Ruby you are using
141
140
  - Whether you are using RVM, and if so what version
142
141
  - Your Gemfile
142
+ - Your Gemfile.lock
143
+ - If you are on 0.9, whether you have locked or not
144
+ - If you are on 1.0, the result of `bundle config`
143
145
  - The command you ran to generate exception(s)
144
146
  - The exception backtrace(s)
145
147
 
data/lib/bundler.rb CHANGED
@@ -60,6 +60,7 @@ module Bundler
60
60
 
61
61
  class << self
62
62
  attr_writer :ui, :bundle_path
63
+ attr_accessor :deployment
63
64
 
64
65
  def configure
65
66
  @configured ||= begin
@@ -68,14 +69,6 @@ module Bundler
68
69
  end
69
70
  end
70
71
 
71
- def production?
72
- @production
73
- end
74
-
75
- def production=(value)
76
- @production = value
77
- end
78
-
79
72
  def ui
80
73
  @ui ||= UI.new
81
74
  end
@@ -127,8 +120,7 @@ module Bundler
127
120
  @definition ||= begin
128
121
  configure
129
122
  upgrade_lockfile
130
- lockfile = root.join("Gemfile.lock")
131
- Definition.build(default_gemfile, lockfile, unlock)
123
+ Definition.build(default_gemfile, default_lockfile, unlock)
132
124
  end
133
125
  end
134
126
 
@@ -184,6 +176,10 @@ module Bundler
184
176
  SharedHelpers.default_gemfile
185
177
  end
186
178
 
179
+ def default_lockfile
180
+ SharedHelpers.default_lockfile
181
+ end
182
+
187
183
  WINDOWS = Config::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)!
188
184
  NULL = WINDOWS ? "NUL" : "/dev/null"
189
185
 
@@ -223,7 +219,7 @@ module Bundler
223
219
  end
224
220
 
225
221
  def upgrade_lockfile
226
- lockfile = root.join("Gemfile.lock")
222
+ lockfile = default_lockfile
227
223
  if lockfile.exist? && lockfile.read(3) == "---"
228
224
  Bundler.ui.warn "Detected Gemfile.lock generated by 0.9, deleting..."
229
225
  lockfile.rmtree
data/lib/bundler/cli.rb CHANGED
@@ -58,8 +58,17 @@ module Bundler
58
58
  all gems are found, Bundler prints a success message and exits with a status of 0.
59
59
  If not, the first missing gem is listed and Bundler exits status 1.
60
60
  D
61
+ method_option "gemfile", :type => :string, :banner =>
62
+ "Use the specified gemfile instead of Gemfile"
61
63
  def check
62
- not_installed = Bundler.definition.missing_specs
64
+ ENV['BUNDLE_GEMFILE'] = File.expand_path(options[:gemfile]) if options[:gemfile]
65
+ begin
66
+ not_installed = Bundler.definition.missing_specs
67
+ rescue GemNotFound, VersionConflict
68
+ Bundler.ui.error "Your Gemfile's dependencies could not be satisfied"
69
+ Bundler.ui.warn "Install missing gems with `bundle install`"
70
+ exit 1
71
+ end
63
72
 
64
73
  if not_installed.any?
65
74
  Bundler.ui.error "The following gems are missing"
@@ -103,20 +112,28 @@ module Bundler
103
112
  "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine"
104
113
  method_option "system", :type => :boolean, :banner =>
105
114
  "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application"
106
- method_option "production", :type => :boolean, :banner =>
115
+ method_option "deployment", :type => :boolean, :banner =>
107
116
  "Install using defaults tuned for deployment environments"
117
+ method_option "production", :type => :boolean, :banner =>
118
+ "Deprecated, please use --deployment instead"
108
119
  def install(path = nil)
109
120
  opts = options.dup
110
121
  opts[:without] ||= []
111
122
  opts[:without].map! { |g| g.to_sym }
112
123
 
113
- if (path || options[:path] || options[:production]) && options[:system]
124
+ if opts[:production]
125
+ opts[:deployment] = true
126
+ Bundler.ui.warn "The --production option is deprecated, and will be removed in " \
127
+ "the final release of Bundler 1.0. Please use --deployment instead."
128
+ end
129
+
130
+ if (path || opts[:path] || opts[:deployment]) && opts[:system]
114
131
  Bundler.ui.error "You have specified both a path to install your gems to, \n" \
115
132
  "as well as --system. Please choose."
116
133
  exit 1
117
134
  end
118
135
 
119
- if path && options[:path]
136
+ if path && opts[:path]
120
137
  Bundler.ui.error "You have specified a path via `bundle install #{path}` as well as\n" \
121
138
  "by `bundle install --path #{options[:path]}`. These options are\n" \
122
139
  "equivalent, so please use one or the other."
@@ -132,26 +149,26 @@ module Bundler
132
149
  exit 1
133
150
  end
134
151
 
135
- if opts[:production]
136
- Bundler.production = true
152
+ if opts[:deployment]
153
+ Bundler.deployment = true
137
154
 
138
- unless Bundler.root.join("Gemfile.lock").exist?
139
- raise ProductionError, "The --production flag requires a Gemfile.lock. Please\n" \
140
- "make sure you have checked your Gemfile.lock into version\n" \
141
- "control before deploying."
155
+ unless Bundler.default_lockfile.exist?
156
+ raise ProductionError, "The --deployment flag requires a Gemfile.lock. Please make " \
157
+ "sure you have checked your Gemfile.lock into version control " \
158
+ "before deploying."
142
159
  end
143
160
 
144
161
  if Bundler.root.join("vendor/cache").exist?
145
- opts["local"] = true
162
+ opts[:local] = true
146
163
  end
147
164
  end
148
165
 
149
166
  # Can't use Bundler.settings for this because settings needs gemfile.dirname
150
- ENV['BUNDLE_GEMFILE'] = opts[:gemfile] if opts[:gemfile]
151
- Bundler.settings[:path] = nil if options[:system]
152
- Bundler.settings[:path] = "vendor/bundle" if options[:production]
167
+ ENV['BUNDLE_GEMFILE'] = File.expand_path(opts[:gemfile]) if opts[:gemfile]
168
+ Bundler.settings[:path] = nil if opts[:system]
169
+ Bundler.settings[:path] = "vendor/bundle" if opts[:deployment]
153
170
  Bundler.settings[:path] = path if path
154
- Bundler.settings[:path] = options[:path] if options[:path]
171
+ Bundler.settings[:path] = opts[:path] if opts[:path]
155
172
  Bundler.settings[:bin] = opts["binstubs"] if opts[:binstubs]
156
173
  Bundler.settings[:disable_shared_gems] = '1' if Bundler.settings[:path]
157
174
  Bundler.settings.without = opts[:without]
@@ -65,11 +65,10 @@ module Bundler
65
65
  @unlock[:sources] ||= []
66
66
 
67
67
  current_platform = Gem.platforms.map { |p| generic(p) }.compact.last
68
+ @new_platform = !@platforms.include?(current_platform)
68
69
  @platforms |= [current_platform]
69
70
 
70
- if Bundler.production?
71
- ensure_equivalent_gemfile_and_lockfile
72
- end
71
+ ensure_equivalent_gemfile_and_lockfile if Bundler.deployment
73
72
 
74
73
  converge_sources
75
74
  converge_dependencies
@@ -101,6 +100,10 @@ module Bundler
101
100
  end
102
101
  end
103
102
 
103
+ def new_platform?
104
+ @new_platform
105
+ end
106
+
104
107
  def missing_specs
105
108
  missing = []
106
109
  resolve.materialize(requested_dependencies, missing)
@@ -128,18 +131,14 @@ module Bundler
128
131
  def resolve
129
132
  @resolve ||= begin
130
133
  converge_locked_specs
131
- if @last_resolve.valid_for?(expanded_dependencies)
132
- @last_resolve
133
- else
134
- source_requirements = {}
135
- dependencies.each do |dep|
136
- next unless dep.source
137
- source_requirements[dep.name] = dep.source.specs
138
- end
139
-
140
- # Run a resolve against the locally available gems
141
- @last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, @last_resolve)
134
+ source_requirements = {}
135
+ dependencies.each do |dep|
136
+ next unless dep.source
137
+ source_requirements[dep.name] = dep.source.specs
142
138
  end
139
+
140
+ # Run a resolve against the locally available gems
141
+ @last_resolve.merge Resolver.resolve(expanded_dependencies, index, source_requirements, @last_resolve)
143
142
  end
144
143
  end
145
144
 
@@ -305,6 +304,11 @@ module Bundler
305
304
  if in_locked_deps?(dep, locked_dep) || satisfies_locked_spec?(dep)
306
305
  deps << dep
307
306
  elsif dep.source.is_a?(Source::Path) && (!locked_dep || dep.source != locked_dep.source)
307
+ @last_resolve.each do |s|
308
+ @unlock[:gems] << s.name if s.source == dep.source
309
+ end
310
+
311
+ dep.source.unlock! if dep.source.respond_to?(:unlock!)
308
312
  dep.source.specs.each { |s| @unlock[:gems] << s.name }
309
313
  end
310
314
  end
@@ -335,6 +339,17 @@ module Bundler
335
339
 
336
340
  resolve = SpecSet.new(converged)
337
341
  resolve = resolve.for(expand_dependencies(deps, true), @unlock[:gems])
342
+ diff = @last_resolve.to_a - resolve.to_a
343
+
344
+ # Now, we unlock any sources that do not have anymore gems pinned to it
345
+ @sources.each do |source|
346
+ next unless source.respond_to?(:unlock!)
347
+
348
+ unless resolve.any? { |s| s.source == source }
349
+ source.unlock! if diff.any? { |s| s.source == source }
350
+ end
351
+ end
352
+
338
353
  @last_resolve = resolve
339
354
  end
340
355
 
@@ -1,5 +1,6 @@
1
1
  require 'rubygems/dependency'
2
2
  require 'bundler/shared_helpers'
3
+ require 'bundler/rubygems_ext'
3
4
 
4
5
  module Bundler
5
6
  class Dependency < Gem::Dependency
@@ -16,7 +17,7 @@ module Bundler
16
17
  :mri_19 => Gem::Platform::RUBY,
17
18
  :jruby => Gem::Platform::JAVA,
18
19
  :mswin => Gem::Platform::MSWIN
19
- }
20
+ }.freeze
20
21
 
21
22
  def initialize(name, version, options = {}, &blk)
22
23
  super(name, version)
data/lib/bundler/dsl.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'bundler/dependency'
2
+
1
3
  module Bundler
2
4
  class Dsl
3
5
  def self.evaluate(gemfile)
@@ -6,7 +8,7 @@ module Bundler
6
8
  builder.to_definition
7
9
  end
8
10
 
9
- VALID_PLATFORMS = [:ruby, :ruby_18, :ruby_19, :mri, :mri_18, :mri_19, :jruby, :mswin]
11
+ VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze
10
12
 
11
13
  def initialize
12
14
  @rubygems_source = Source::Rubygems.new
@@ -36,7 +36,7 @@ module Bundler
36
36
  end
37
37
 
38
38
  def lock
39
- @definition.lock(root.join('Gemfile.lock'))
39
+ @definition.lock(Bundler.default_lockfile)
40
40
  end
41
41
 
42
42
  def update(*gems)
@@ -15,10 +15,10 @@ module Bundler
15
15
  return
16
16
  end
17
17
 
18
- if Bundler.root.join("Gemfile.lock").exist? && !options["update"]
18
+ if Bundler.default_lockfile.exist? && !options["update"]
19
19
  begin
20
- missing_specs = Definition.build(Bundler.default_gemfile, Bundler.root.join("Gemfile.lock"), nil).missing_specs
21
- local = true unless missing_specs.any?
20
+ tmpdef = Definition.build(Bundler.default_gemfile, Bundler.default_lockfile, nil)
21
+ local = true unless tmpdef.new_platform? || tmpdef.missing_specs.any?
22
22
  rescue BundlerError
23
23
  end
24
24
  end
@@ -66,7 +66,8 @@ module Bundler
66
66
  dep = Bundler::Dependency.new(name, version)
67
67
 
68
68
  if pinned && dep.name != 'bundler'
69
- dep.source = @specs.find { |s| s.name == dep.name }.source
69
+ spec = @specs.find { |s| s.name == dep.name }
70
+ dep.source = spec.source if spec
70
71
 
71
72
  # Path sources need to know what the default name / version
72
73
  # to use in the case that there are no gemspecs present. A fake
@@ -254,7 +254,13 @@ module Bundler
254
254
 
255
255
  if matching_versions.empty?
256
256
  if current.required_by.empty?
257
- if current.source
257
+ if base = @base[current.name] and !base.empty?
258
+ version = base.first.version
259
+ message = "You have requested:\n" \
260
+ " #{current.name} #{current.requirement}\n\n" \
261
+ "The bundle currently has #{current.name} locked at #{version}.\n" \
262
+ "Try running `bundle update #{current.name}`"
263
+ elsif current.source
258
264
  name = current.name
259
265
  versions = @source_requirements[name][name].map { |s| s.version }
260
266
  message = "Could not find gem '#{current}' in #{current.source}.\n"
@@ -51,7 +51,7 @@ module Bundler
51
51
  @definition.dependencies.each do |dep|
52
52
  # Skip the dependency if it is not in any of the requested
53
53
  # groups
54
- next unless (dep.groups & groups).any?
54
+ next unless ((dep.groups & groups).any? && dep.current_platform?)
55
55
 
56
56
  required_file = nil
57
57
 
@@ -22,6 +22,10 @@ module Bundler
22
22
  Pathname.new(gemfile)
23
23
  end
24
24
 
25
+ def default_lockfile
26
+ Pathname.new("#{default_gemfile}.lock")
27
+ end
28
+
25
29
  def in_bundle?
26
30
  find_gemfile
27
31
  end
@@ -71,8 +71,12 @@ module Bundler
71
71
  end
72
72
 
73
73
  def fetch(spec)
74
- action = @spec_fetch_map[spec.full_name]
75
- action.call if action
74
+ spec, uri = @spec_fetch_map[spec.full_name]
75
+ if spec
76
+ path = download_gem_from_uri(spec, uri)
77
+ s = Gem::Format.from_file_by_path(path).spec
78
+ spec.__swap__(s)
79
+ end
76
80
  end
77
81
 
78
82
  def install(spec)
@@ -101,7 +105,8 @@ module Bundler
101
105
  sudo "cp -R #{Bundler.tmp}/gems/#{spec.full_name} #{Gem.dir}/gems/"
102
106
  sudo "cp -R #{Bundler.tmp}/specifications/#{spec.full_name}.gemspec #{Gem.dir}/specifications/"
103
107
  spec.executables.each do |exe|
104
- Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Gem.dir}/bin/"
108
+ sudo "mkdir -p #{Gem.dir}/bin"
109
+ sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Gem.dir}/bin/"
105
110
  end
106
111
  end
107
112
 
@@ -195,7 +200,6 @@ module Bundler
195
200
  def remote_specs
196
201
  @remote_specs ||= begin
197
202
  idx = Index.new
198
- remotes = self.remotes.map { |uri| uri.to_s }
199
203
  old = Gem.sources
200
204
 
201
205
  remotes.each do |uri|
@@ -206,12 +210,7 @@ module Bundler
206
210
  next if name == 'bundler'
207
211
  spec = RemoteSpecification.new(name, version, platform, uri)
208
212
  spec.source = self
209
- # Temporary hack until this can be figured out better
210
- @spec_fetch_map[spec.full_name] = lambda do
211
- path = download_gem_from_uri(spec, uri)
212
- s = Gem::Format.from_file_by_path(path).spec
213
- spec.__swap__(s)
214
- end
213
+ @spec_fetch_map[spec.full_name] = [spec, uri]
215
214
  idx << spec
216
215
  end
217
216
  end
@@ -332,9 +331,17 @@ module Bundler
332
331
  rescue ArgumentError, SyntaxError, Gem::EndOfYAMLException, Gem::Exception
333
332
  begin
334
333
  eval(File.read(file.basename), TOPLEVEL_BINDING, file.expand_path.to_s)
335
- rescue LoadError
336
- raise GemspecError, "There was a LoadError while evaluating #{file.basename}.\n" +
337
- "Does it try to require a relative path? That doesn't work in Ruby 1.9."
334
+ rescue LoadError => e
335
+ original_line = e.backtrace.find { |line| line.include?(file.to_s) }
336
+ msg = "There was a LoadError while evaluating #{file.basename}:\n #{e.message}"
337
+ msg << " from\n #{original_line}" if original_line
338
+ msg << "\n"
339
+
340
+ if RUBY_VERSION >= "1.9.0"
341
+ msg << "\nDoes it try to require a relative path? That doesn't work in Ruby 1.9."
342
+ end
343
+
344
+ raise GemspecError, msg
338
345
  end
339
346
  end
340
347
  end
@@ -126,42 +126,6 @@ class Thor
126
126
  build_option(name, options, scope)
127
127
  end
128
128
 
129
- # Parses the task and options from the given args, instantiate the class
130
- # and invoke the task. This method is used when the arguments must be parsed
131
- # from an array. If you are inside Ruby and want to use a Thor class, you
132
- # can simply initialize it:
133
- #
134
- # script = MyScript.new(args, options, config)
135
- # script.invoke(:task, first_arg, second_arg, third_arg)
136
- #
137
- def start(original_args=ARGV, config={})
138
- @@original_args = original_args
139
-
140
- super do |given_args|
141
- meth = given_args.first.to_s
142
-
143
- if !meth.empty? && (map[meth] || meth !~ /^\-/)
144
- given_args.shift
145
- else
146
- meth = nil
147
- end
148
-
149
- meth = normalize_task_name(meth)
150
- task = all_tasks[meth]
151
-
152
- if task
153
- args, opts = Thor::Options.split(given_args)
154
- config.merge!(:task_options => task.options)
155
- else
156
- args, opts = given_args, {}
157
- end
158
-
159
- task ||= Thor::DynamicTask.new(meth)
160
- trailing = args[Range.new(arguments.size, -1)]
161
- new(args, opts, config).invoke(task, trailing || [])
162
- end
163
- end
164
-
165
129
  # Prints help information for the given task.
166
130
  #
167
131
  # ==== Parameters
@@ -215,25 +179,81 @@ class Thor
215
179
  end
216
180
 
217
181
  def subcommands
218
- @@subcommands ||= {}
182
+ @subcommands ||= from_superclass(:subcommands, [])
219
183
  end
220
184
 
221
185
  def subcommand(subcommand, subcommand_class)
222
- subcommand = subcommand.to_s
223
- subcommands[subcommand] = subcommand_class
186
+ self.subcommands << subcommand.to_s
224
187
  subcommand_class.subcommand_help subcommand
225
- define_method(subcommand) { |*_| subcommand_class.start(subcommand_args) }
188
+ define_method(subcommand) { |*args| invoke subcommand_class, args }
189
+ end
190
+
191
+ # Extend check unknown options to accept a hash of conditions.
192
+ #
193
+ # === Parameters
194
+ # options<Hash>: A hash containing :only and/or :except keys
195
+ def check_unknown_options!(options={})
196
+ @check_unknown_options ||= Hash.new
197
+ options.each do |key, value|
198
+ if value
199
+ @check_unknown_options[key] = Array(value)
200
+ else
201
+ @check_unknown_options.delete(key)
202
+ end
203
+ end
204
+ @check_unknown_options
205
+ end
206
+
207
+ # Overwrite check_unknown_options? to take subcommands and options into account.
208
+ def check_unknown_options?(config) #:nodoc:
209
+ options = check_unknown_options
210
+ return false unless options
211
+
212
+ task = config[:current_task]
213
+ return true unless task
214
+
215
+ name = task.name
216
+
217
+ if subcommands.include?(name)
218
+ false
219
+ elsif options[:except]
220
+ !options[:except].include?(name.to_sym)
221
+ elsif options[:only]
222
+ options[:only].include?(name.to_sym)
223
+ else
224
+ true
225
+ end
226
226
  end
227
227
 
228
228
  protected
229
229
 
230
+ # The method responsible for dispatching given the args.
231
+ def dispatch(meth, given_args, given_opts, config) #:nodoc:
232
+ meth ||= retrieve_task_name(given_args)
233
+ task = all_tasks[normalize_task_name(meth)]
234
+
235
+ if task
236
+ args, opts = Thor::Options.split(given_args)
237
+ else
238
+ args, opts = given_args, nil
239
+ task = Thor::DynamicTask.new(meth)
240
+ end
241
+
242
+ opts = given_opts || opts || []
243
+ config.merge!(:current_task => task, :task_options => task.options)
244
+
245
+ trailing = args[Range.new(arguments.size, -1)]
246
+ new(args, opts, config).invoke_task(task, trailing || [])
247
+ end
248
+
230
249
  # The banner for this class. You can customize it if you are invoking the
231
250
  # thor class by another ways which is not the Thor::Runner. It receives
232
251
  # the task that is going to be invoked and a boolean which indicates if
233
252
  # the namespace should be displayed as arguments.
234
253
  #
235
254
  def banner(task, namespace = nil, subcommand = false)
236
- "#{$0} #{task.formatted_usage(self, $thor_runner, subcommand)}"
255
+ base = File.basename($0).split(" ").first
256
+ "#{base} #{task.formatted_usage(self, $thor_runner, subcommand)}"
237
257
  end
238
258
 
239
259
  def baseclass #:nodoc:
@@ -243,10 +263,10 @@ class Thor
243
263
  def create_task(meth) #:nodoc:
244
264
  if @usage && @desc
245
265
  base_class = @hide ? Thor::HiddenTask : Thor::Task
246
- tasks[meth.to_s] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
266
+ tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
247
267
  @usage, @desc, @long_desc, @method_options, @hide = nil
248
268
  true
249
- elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
269
+ elsif self.all_tasks[meth] || meth == "method_missing"
250
270
  true
251
271
  else
252
272
  puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
@@ -261,9 +281,19 @@ class Thor
261
281
  @method_options = nil
262
282
  end
263
283
 
284
+ # Retrieve the task name from given args.
285
+ def retrieve_task_name(args) #:nodoc:
286
+ meth = args.first.to_s unless args.empty?
287
+
288
+ if meth && (map[meth] || meth !~ /^\-/)
289
+ args.shift
290
+ else
291
+ nil
292
+ end
293
+ end
294
+
264
295
  # Receives a task name (can be nil), and try to get a map from it.
265
296
  # If a map can't be found use the sent name or the default task.
266
- #
267
297
  def normalize_task_name(meth) #:nodoc:
268
298
  meth = map[meth.to_s] || meth || default_task
269
299
  meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
@@ -275,11 +305,6 @@ class Thor
275
305
  def help(task = nil, subcommand = true); super; end
276
306
  RUBY
277
307
  end
278
-
279
- end
280
-
281
- def subcommand_args
282
- @@original_args[1..-1]
283
308
  end
284
309
 
285
310
  include Thor::Base
@@ -53,7 +53,7 @@ class Thor
53
53
 
54
54
  opts = Thor::Options.new(parse_options, hash_options)
55
55
  self.options = opts.parse(array_options)
56
- opts.check_unknown! if self.class.check_unknown_options?
56
+ opts.check_unknown! if self.class.check_unknown_options?(config)
57
57
  end
58
58
 
59
59
  class << self
@@ -114,8 +114,12 @@ class Thor
114
114
  @check_unknown_options = true
115
115
  end
116
116
 
117
- def check_unknown_options? #:nodoc:
118
- @check_unknown_options || false
117
+ def check_unknown_options #:nodoc:
118
+ @check_unknown_options ||= from_superclass(:check_unknown_options, false)
119
+ end
120
+
121
+ def check_unknown_options?(config) #:nodoc:
122
+ !!check_unknown_options
119
123
  end
120
124
 
121
125
  # Adds an argument to the class and creates an attr_accessor for it.
@@ -364,19 +368,25 @@ class Thor
364
368
  #
365
369
  def namespace(name=nil)
366
370
  case name
367
- when nil
368
- @namespace ||= Thor::Util.namespace_from_thor_class(self)
369
- else
370
- @namespace = name.to_s
371
+ when nil
372
+ @namespace ||= Thor::Util.namespace_from_thor_class(self)
373
+ else
374
+ @namespace = name.to_s
371
375
  end
372
376
  end
373
377
 
374
- # Default way to start generators from the command line.
378
+ # Parses the task and options from the given args, instantiate the class
379
+ # and invoke the task. This method is used when the arguments must be parsed
380
+ # from an array. If you are inside Ruby and want to use a Thor class, you
381
+ # can simply initialize it:
382
+ #
383
+ # script = MyScript.new(args, options, config)
384
+ # script.invoke(:task, first_arg, second_arg, third_arg)
375
385
  #
376
386
  def start(given_args=ARGV, config={})
377
- self.debugging = given_args.include?("--debug")
387
+ self.debugging = given_args.delete("--debug")
378
388
  config[:shell] ||= Thor::Base.shell.new
379
- yield(given_args.dup)
389
+ dispatch(nil, given_args.dup, nil, config)
380
390
  rescue Thor::Error => e
381
391
  debugging ? (raise e) : config[:shell].error(e.message)
382
392
  exit(1) if exit_on_failure?
@@ -535,6 +545,12 @@ class Thor
535
545
  # class.
536
546
  def initialize_added #:nodoc:
537
547
  end
548
+
549
+ # SIGNATURE: The hook invoked by start.
550
+ def dispatch(task, given_args, given_opts, config) #:nodoc:
551
+ raise NotImplementedError
552
+ end
553
+
538
554
  end
539
555
  end
540
556
  end
@@ -10,10 +10,10 @@ class Thor
10
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
- when Symbol, String
14
- Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
15
- else
16
- name
13
+ when Symbol, String
14
+ Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
15
+ else
16
+ name
17
17
  end
18
18
  end
19
19
  end
@@ -94,29 +94,34 @@ class Thor
94
94
  # invoke Rspec::RR, [], :style => :foo
95
95
  #
96
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
+
97
102
  args.unshift(nil) if Array === args.first || NilClass === args.first
98
103
  task, args, opts, config = args
99
104
 
100
- object, task = _prepare_for_invocation(name, task)
101
- klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
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)
110
+ end
102
111
 
103
- method_args = []
104
- current = @_invocations[klass]
112
+ # Invoke the given task if the given args.
113
+ def invoke_task(task, *args) #:nodoc:
114
+ current = @_invocations[self.class]
105
115
 
106
- iterator = proc do |_, task|
107
- unless current.include?(task.name)
108
- current << task.name
109
- task.run(instance, method_args)
110
- end
116
+ unless current.include?(task.name)
117
+ current << task.name
118
+ task.run(self, *args)
111
119
  end
120
+ end
112
121
 
113
- if task
114
- args ||= []
115
- method_args = args[Range.new(klass.arguments.size, -1)] || []
116
- iterator.call(nil, task)
117
- else
118
- klass.all_tasks.map(&iterator)
119
- end
122
+ # Invoke all tasks for the current instance.
123
+ def invoke_all #:nodoc:
124
+ self.class.all_tasks.map { |_, task| invoke_task(task) }
120
125
  end
121
126
 
122
127
  # Invokes using shell padding.
@@ -131,50 +136,33 @@ class Thor
131
136
  { :invocations => @_invocations }
132
137
  end
133
138
 
134
- # This method can receive several different types of arguments and it's then
135
- # responsible to normalize them by returning the object where the task should
136
- # be invoked and a Thor::Task object.
137
- def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
138
- if name.is_a?(Thor::Task)
139
- task = name
140
- elsif task = self.class.all_tasks[name.to_s]
141
- object = self
139
+ # This method simply retrieves the class and task to be invoked.
140
+ # If the name is nil or the given name is a task in the current class,
141
+ # use the given name and return self as class. Otherwise, call
142
+ # prepare_for_invocation in the current class.
143
+ def _retrieve_class_and_task(name, sent_task=nil) #:nodoc:
144
+ case
145
+ when name.nil?
146
+ [self.class, nil]
147
+ when self.class.all_tasks[name.to_s]
148
+ [self.class, name.to_s]
142
149
  else
143
- object, task = self.class.prepare_for_invocation(nil, name)
144
- task ||= sent_task
150
+ klass, task = self.class.prepare_for_invocation(nil, name)
151
+ [klass, task || sent_task]
145
152
  end
146
-
147
- # If the object was not set, use self and use the name as task.
148
- object, task = self, name unless object
149
- return object, _validate_task(object, task)
150
- end
151
-
152
- # Check if the object given is a Thor class object and get a task object
153
- # for it.
154
- def _validate_task(object, task) #:nodoc:
155
- klass = object.is_a?(Class) ? object : object.class
156
- raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
157
-
158
- task ||= klass.default_task if klass.respond_to?(:default_task)
159
- task = klass.all_tasks[task.to_s] || Thor::DynamicTask.new(task) if task && !task.is_a?(Thor::Task)
160
- task
161
153
  end
162
154
 
163
155
  # Initialize klass using values stored in the @_initializer.
164
- def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
165
- if object.is_a?(Class)
166
- klass = object
156
+ def _parse_initialization_options(args, opts, config) #:nodoc:
157
+ stored_args, stored_opts, stored_config = @_initializer
167
158
 
168
- stored_args, stored_opts, stored_config = @_initializer
169
- args ||= stored_args.dup
170
- opts ||= stored_opts.dup
159
+ args ||= stored_args.dup
160
+ opts ||= stored_opts.dup
171
161
 
172
- config ||= {}
173
- config = stored_config.merge(_shared_configuration).merge!(config)
174
- [ klass, klass.new(args, opts, config) ]
175
- else
176
- [ object.class, object ]
177
- end
162
+ config ||= {}
163
+ config = stored_config.merge(_shared_configuration).merge!(config)
164
+
165
+ [ args, opts, config ]
178
166
  end
179
167
  end
180
168
  end
@@ -151,6 +151,8 @@ class Thor
151
151
  elsif option.string? && !option.required?
152
152
  # Return the default if there is one, else the human name
153
153
  return option.lazy_default || option.default || option.human_name
154
+ elsif option.lazy_default
155
+ return option.lazy_default
154
156
  else
155
157
  raise MalformattedArgumentError, "No value provided for option '#{switch}'"
156
158
  end
@@ -27,7 +27,7 @@ class Thor
27
27
 
28
28
  autoload :Basic, 'thor/shell/basic'
29
29
  autoload :Color, 'thor/shell/color'
30
- autoload :HTML, 'thor/shell/HTML'
30
+ autoload :HTML, 'thor/shell/html'
31
31
 
32
32
  # Add shell to initialize config values.
33
33
  #
@@ -35,8 +35,8 @@ class Thor
35
35
  # say("I know you knew that.")
36
36
  #
37
37
  def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
38
- message = message.to_s
39
- message = set_color(message, color) if color
38
+ message = message.to_s
39
+ message = set_color(message, color) if color
40
40
 
41
41
  spaces = " " * padding
42
42
 
@@ -60,7 +60,9 @@ class Thor
60
60
 
61
61
  status = status.to_s.rjust(12)
62
62
  status = set_color status, color, true if color
63
- say "#{status}#{spaces}#{message}", nil, true
63
+
64
+ $stdout.puts "#{status}#{spaces}#{message}"
65
+ $stdout.flush
64
66
  end
65
67
 
66
68
  # Make a question the to user and returns true if the user replies "y" or
@@ -1,3 +1,3 @@
1
1
  class Thor
2
- VERSION = "0.13.8".freeze
2
+ VERSION = "0.14.0".freeze
3
3
  end
@@ -2,5 +2,5 @@ module Bundler
2
2
  # We're doing this because we might write tests that deal
3
3
  # with other versions of bundler and we are unsure how to
4
4
  # handle this better.
5
- VERSION = "1.0.0.rc.2" unless defined?(::Bundler::VERSION)
5
+ VERSION = "1.0.0.rc.3" unless defined?(::Bundler::VERSION)
6
6
  end
metadata CHANGED
@@ -7,8 +7,8 @@ version: !ruby/object:Gem::Version
7
7
  - 0
8
8
  - 0
9
9
  - rc
10
- - 2
11
- version: 1.0.0.rc.2
10
+ - 3
11
+ version: 1.0.0.rc.3
12
12
  platform: ruby
13
13
  authors:
14
14
  - Carl Lerche
@@ -18,7 +18,7 @@ autorequire:
18
18
  bindir: bin
19
19
  cert_chain: []
20
20
 
21
- date: 2010-07-29 00:00:00 -07:00
21
+ date: 2010-08-03 00:00:00 -07:00
22
22
  default_executable:
23
23
  dependencies:
24
24
  - !ruby/object:Gem::Dependency