summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--CHANGELOG.md23
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--DEVELOPMENT.md (renamed from CONTRIBUTE.md)43
-rw-r--r--ISSUES.md2
-rw-r--r--LICENSE.md2
-rw-r--r--README.md10
-rw-r--r--Rakefile19
-rwxr-xr-xbin/bundle13
-rw-r--r--lib/bundler.rb63
-rw-r--r--lib/bundler/cli.rb41
-rw-r--r--lib/bundler/constants.rb5
-rw-r--r--lib/bundler/current_ruby.rb88
-rw-r--r--lib/bundler/definition.rb46
-rw-r--r--lib/bundler/dependency.rb85
-rw-r--r--lib/bundler/dsl.rb11
-rw-r--r--lib/bundler/fetcher.rb54
-rw-r--r--lib/bundler/gem_helper.rb4
-rw-r--r--lib/bundler/gem_installer.rb9
-rw-r--r--lib/bundler/installer.rb81
-rw-r--r--lib/bundler/parallel_workers.rb18
-rw-r--r--lib/bundler/parallel_workers/thread_worker.rb27
-rw-r--r--lib/bundler/parallel_workers/unix_worker.rb88
-rw-r--r--lib/bundler/parallel_workers/worker.rb68
-rw-r--r--lib/bundler/resolver.rb28
-rw-r--r--lib/bundler/rubygems_ext.rb2
-rw-r--r--lib/bundler/rubygems_integration.rb36
-rw-r--r--lib/bundler/runtime.rb9
-rw-r--r--lib/bundler/safe_catch.rb101
-rw-r--r--lib/bundler/shared_helpers.rb28
-rw-r--r--lib/bundler/source/git.rb3
-rw-r--r--lib/bundler/source/git/git_proxy.rb6
-rw-r--r--lib/bundler/source/path.rb5
-rw-r--r--lib/bundler/source/rubygems.rb10
-rw-r--r--lib/bundler/spec_set.rb17
-rw-r--r--lib/bundler/vendor/net/http/persistent.rb174
-rw-r--r--lib/bundler/vendor/thor.rb399
-rw-r--r--lib/bundler/vendor/thor/actions.rb38
-rw-r--r--lib/bundler/vendor/thor/actions/create_link.rb3
-rw-r--r--lib/bundler/vendor/thor/actions/directory.rb40
-rw-r--r--lib/bundler/vendor/thor/actions/empty_directory.rb22
-rw-r--r--lib/bundler/vendor/thor/actions/file_manipulation.rb9
-rw-r--r--lib/bundler/vendor/thor/base.rb198
-rw-r--r--lib/bundler/vendor/thor/command.rb (renamed from lib/bundler/vendor/thor/task.rb)30
-rw-r--r--lib/bundler/vendor/thor/core_ext/dir_escape.rb0
-rw-r--r--lib/bundler/vendor/thor/core_ext/file_binary_read.rb9
-rw-r--r--lib/bundler/vendor/thor/core_ext/io_binary_read.rb12
-rw-r--r--lib/bundler/vendor/thor/error.rb19
-rw-r--r--lib/bundler/vendor/thor/group.rb73
-rw-r--r--lib/bundler/vendor/thor/invocation.rb54
-rw-r--r--lib/bundler/vendor/thor/parser/options.rb40
-rw-r--r--lib/bundler/vendor/thor/rake_compat.rb5
-rw-r--r--lib/bundler/vendor/thor/runner.rb43
-rw-r--r--lib/bundler/vendor/thor/shell/basic.rb66
-rw-r--r--lib/bundler/vendor/thor/shell/color.rb22
-rw-r--r--lib/bundler/vendor/thor/shell/html.rb22
-rw-r--r--lib/bundler/vendor/thor/util.rb424
-rw-r--r--lib/bundler/vendor/thor/version.rb2
-rw-r--r--man/bundle-install.ronn6
-rw-r--r--man/gemfile.5.ronn9
-rw-r--r--spec/bundler/cli_rspec.rb6
-rw-r--r--spec/bundler/dsl_spec.rb14
-rw-r--r--spec/bundler/gem_helper_spec.rb40
-rw-r--r--spec/bundler/safe_catch_spec.rb37
-rw-r--r--spec/install/gems/dependency_api_spec.rb3
-rw-r--r--spec/install/gems/packed_spec.rb6
-rw-r--r--spec/install/gems/post_install_spec.rb6
-rw-r--r--spec/install/gems/resolving_spec.rb37
-rw-r--r--spec/install/gems/simple_case_spec.rb18
-rw-r--r--spec/install/gems/win32_spec.rb2
-rw-r--r--spec/install/security_policy_spec.rb21
-rw-r--r--spec/integration/inject.rb2
-rw-r--r--spec/lock/git_spec.rb1
-rw-r--r--spec/lock/lockfile_spec.rb34
-rw-r--r--spec/other/binstubs_spec.rb30
-rw-r--r--spec/other/bundle_ruby_spec.rb2
-rw-r--r--spec/other/check_spec.rb2
-rw-r--r--spec/other/clean_spec.rb4
-rw-r--r--spec/other/exec_spec.rb21
-rw-r--r--spec/other/init_spec.rb1
-rw-r--r--spec/other/outdated_spec.rb8
-rw-r--r--spec/other/platform_spec.rb4
-rw-r--r--spec/quality_spec.rb28
-rw-r--r--spec/realworld/dependency_api_spec.rb3
-rw-r--r--spec/realworld/edgecases_spec.rb6
-rw-r--r--spec/realworld/parallel_install_spec.rb19
-rw-r--r--spec/resolver/basic_spec.rb13
-rw-r--r--spec/runtime/require_spec.rb9
-rw-r--r--spec/runtime/with_clean_env_spec.rb1
-rw-r--r--spec/support/helpers.rb4
-rw-r--r--spec/support/indexes.rb18
-rw-r--r--spec/support/streams.rb13
-rw-r--r--spec/update/gems_spec.rb6
-rw-r--r--spec/update/git_spec.rb4
-rw-r--r--spec/update/source_spec.rb1
95 files changed, 2105 insertions, 1095 deletions
diff --git a/.travis.yml b/.travis.yml
index 842d42ba21..501f70a267 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,8 +10,10 @@ branches:
- 1-0-stable
notifications:
email:
- - mail@arko.net
- - hone02@gmail.com
+ # andre
+ - secure: "bCcvqJT7YrBawtkXXwHhT+jOFth7r2Qv/30PkkbhQxk6Jb3xambjCOJ3U6vJ\ngYmiL50exi5lUp3oc3SEbHN5t2CrZqOZDQ6o7P8EAmB5c0oH2RrYaFOkI5Gt\nul/jGH/96A9sj0aMwG7JfdMSfhqj1DUKAm2PnnbXPL853VfmT24="
+ # terence
+ - secure: "MQ8eA5Jb8YzEpAo58DRGfVJklAPcEbAulpBZnTxp0am6ldneDtJHbQk21w6R\nj5GsDHlzr/lMp/GHIimtUZ7rLohfND8fj/W7fs1Dkd4eN02/ERt98x3pHlqv\nvZgSnZ39uVYv+OcphraE24QaRaGWLhWZAMYQTVe/Yz50NyG8g1U="
irc:
on_success: change
on_failure: always
@@ -31,7 +33,7 @@ env:
# we need to know if changes to rubygems will break bundler on release
- RGV=master
# test the latest rubygems release with all of our supported rubies
- - RGV=v2.0.2
+ - RGV=v2.0.3
matrix:
allow_failures:
# we want to know how we're doing with head, but not fail the build
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6be0940e1..e76b1008c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,25 @@
+## 1.4.0
+
+Features:
+
+ - add `ENV['DEBUG_RESOLVER_TREE']` outputs resolver tree (@dblock)
+ - set $MANPATH so `bundle exec man name` works (#1624, @sunaku)
+ - add Gemfile dependency info to bundle outdated output (#2487, @rahearn)
+ - faster installs using gemspecs from the local system cache (#2497, @mipearson)
+ - add `bundle install -jN` for N parallel gem installations (#2481, @eagletmt)
+ - allow `require: true` as an alias for `require: <name>` (#2538, @ndbroadbent)
+
+Bugfixes:
+
+ - reduce stack size while resolving, helping JRuby overflow less (#2510, @headius)
+
+## 1.3.6
+
+Bugfixes:
+
+ - set --no-cache when bundle install --local is called (@TimMoore)
+ - make gemspec path option preserve relative paths in lock file (@bwillis)
+
## 1.3.5 (3 April 2013)
Features:
@@ -111,6 +133,7 @@ Features:
- `binstubs` lists child gem bins if a gem has no binstubs
- `bundle gem --edit` will open the new gemspec (@ndbroadbent)
- `bundle gem --test rspec` now makes working tests (@tricknotes)
+ - `bundle env` prints info about bundler's environment (@peeja)
- add `BUNDLE_IGNORE_CONFIG` environment variable support (@richo)
Bugfixes:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 759b2dec97..89e8e5f9a3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Creating Issues
-If you're having a problem, please see [ISSUES](https://github.com/carlhuda/bundler/blob/master/ISSUES.md) for troubleshooting steps and a guide for how to submit a ticket that will help us solve the problem you are having as quickly as possible.
+If you're having a problem, please see [ISSUES](https://github.com/bundler/bundler/blob/master/ISSUES.md) for troubleshooting steps and a guide for how to submit a ticket that will help us solve the problem you are having as quickly as possible.
# Discussing Bundler
@@ -8,6 +8,6 @@ If you'd like to discuss features, ask questions, or just engage in general Bund
# Helping Out
-If you'd like to help make Bundler better, you totally rock! Please check out the [CONTRIBUTE](https://github.com/carlhuda/bundler/blob/master/CONTRIBUTE.md) file for an introduction to the project, guidelines for contributing, and suggestions for things anyone can do that would be helpful.
+If you'd like to help make Bundler better, you totally rock! Please check out the [DEVELOPMENT](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md) file for an introduction to the project, guidelines for contributing, and suggestions for things anyone can do that would be helpful.
Thanks for helping us make Bundler better.
diff --git a/CONTRIBUTE.md b/DEVELOPMENT.md
index a12d541e1f..1e80b2d21a 100644
--- a/CONTRIBUTE.md
+++ b/DEVELOPMENT.md
@@ -1,6 +1,6 @@
-Great to have you here! Here are a few ways you can help out with [Bundler](http://github.com/carlhuda/bundler).
+Great to have you here! Here are a few ways you can help out with [Bundler](http://github.com/bundler/bundler).
-# Learn & listen
+# Where should I start?
You can start learning about Bundler by reading [the documentation](http://gembundler.com). If you want, you can also read a (lengthy) explanation of [why Bundler exists and what it does](http://gembundler.com/v1.2/rationale.html). You can also check out discussions about Bundler on the [Bundler mailing list](https://groups.google.com/group/ruby-bundler) and in the [Bundler IRC channel](irc://irc.freenode.net/#bundler), which is #bundler on Freenode.
@@ -8,14 +8,33 @@ You can start learning about Bundler by reading [the documentation](http://gembu
The Bundler core team consists of André Arko ([@indirect](http://github.com/indirect)), Terence Lee ([@hone](http://github.com/hone)), and Jessica Lynn Suttles ([@jlsuttles](http://github.com/jlsuttles)), with support and advice from original Bundler author Yehuda Katz ([@wycats](http://github.com/wycats)).
+# Development setup
+
+Bundler doesn't use a Gemfile to list development dependencies, because when we tried it we couldn't tell if we were awake or it was just another level of dreams. To work on Bundler, you'll probably want to do a couple of things.
+
+ 1. Install Bundler's development dependencies
+
+ $ rake spec:deps
+
+ 2. Run the test suite, to make sure things are working
+
+ $ rake spec
+
+ 3. Set up a shell alias to run Bundler from your clone, e.g. a Bash alias:
+
+ $ alias dbundle='ruby -I /path/to/bundler/lib /path/to/bundler/bin/bundle'
+
+ With that set up, you can test changes you've made to Bundler by running `dbundle`, without interfering with the regular `bundle` command.
+
+
# Adding new features
-When adding a new feature to Bundler, please follow these steps:
+If you would like to add a new feature to Bundler, please follow these steps:
- 1. [Create an issue](https://github.com/carlhuda/bundler/issues/new) to discuss your feature.
+ 1. [Create an issue](https://github.com/bundler/bundler/issues/new) to discuss your feature.
2. Base your commits on the master branch, since we follow [SemVer](http://semver.org) and don't add new features to old releases.
3. Commit the code and at least one test covering your changes to a feature branch in your fork.
- 4. Put a line in the [CHANGELOG](https://github.com/carlhuda/bundler/blob/master/CHANGELOG.md) summarizing your changes under the next release under the "Features" heading.
+ 4. Put a line in the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md) summarizing your changes under the next release under the "Features" heading.
5. Send us a [pull request](https://help.github.com/articles/using-pull-requests) from your feature branch.
If you don't hear back immediately, don’t get discouraged! We all have day jobs, but we respond to most tickets within a day or two.
@@ -24,9 +43,9 @@ If you don't hear back immediately, don’t get discouraged! We all have day job
Triage is the work of processing tickets that have been opened into actionable issues, feature requests, or bug reports. That includes verifying bugs, categorizing the ticket, and ensuring there's enough information to reproduce the bug for anyone who wants to try to fix it.
-We've created an [issues guide](https://github.com/carlhuda/bundler/blob/master/ISSUES.md) to walk Bundler users through the process of troubleshooting issues and reporting bugs.
+We've created an [issues guide](https://github.com/bundler/bundler/blob/master/ISSUES.md) to walk Bundler users through the process of troubleshooting issues and reporting bugs.
-If you'd like to help, awesome! You can [report a new bug](https://github.com/carlhuda/bundler/issues/new) or browse our [existing open tickets](https://github.com/carlhuda/bundler/issues).
+If you'd like to help, awesome! You can [report a new bug](https://github.com/bundler/bundler/issues/new) or browse our [existing open tickets](https://github.com/bundler/bundler/issues).
Not every ticket will point to a bug in Bundler's code, but open tickets usually mean that there is something we could improve to help that user. Sometimes that means writing additional documentation, sometimes that means making error messages clearer, and sometimes that means explaining to a user that they need to install git to use git gems.
@@ -45,7 +64,7 @@ If you can reproduce an issue, you're well on your way to fixing it. :) Fixing i
1. Discuss the fix on the existing issue. Coordinating with everyone else saves duplicate work and serves as a great way to get suggestions and ideas if you need any.
2. Base your commits on the correct branch. Bugfixes for 1.x versions of Bundler should be based on the matching 1-x-stable branch.
3. Commit the code and at least one test covering your changes to a named branch in your fork.
- 4. Put a line in the [CHANGELOG](https://github.com/carlhuda/bundler/blob/master/CHANGELOG.md) summarizing your changes under the next release under the “Bugfixes” heading.
+ 4. Put a line in the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md) summarizing your changes under the next release under the “Bugfixes” heading.
5. Send us a [pull request](https://help.github.com/articles/using-pull-requests) from your bugfix branch.
Finally, the ticket may be a duplicate of another older ticket. If you notice a ticket is a duplicate, simply comment on the ticket noting the original ticket’s number. For example, you could say “This is a duplicate of issue #42, and can be closed”.
@@ -71,16 +90,16 @@ Bundler has two main sources of documentation: the built-in help (including usag
If you’d like to submit a patch to the man pages, follow the steps for adding a feature above. All of the man pages are located in the `man` directory. Just use the “Documentation” heading when you describe what you did in the changelog.
-If you have a suggestion or proposed change for [gembundler.com](http://gembundler.com), please open an issue or send a pull request to the [bundler-site-middleman](https://github.com/bundler/bundler-site-middleman) repository.
+If you have a suggestion or proposed change for [gembundler.com](http://gembundler.com), please open an issue or send a pull request to the [bundler-site](https://github.com/bundler/bundler-site) repository.
# Community
Community is an important part of all we do. If you’d like to be part of the Bundler community, you can jump right in and start helping make Bundler better for everyone who uses it.
-It would be tremendously helpful to have more people answering questions about Bundler (and often simply about Rubygems or Ruby itself) in our [issue tracker](https://github.com/carlhuda/bundler/issues) or on [Stack Overflow](http://stackoverflow.com/questions/tagged/bundler).
+It would be tremendously helpful to have more people answering questions about Bundler (and often simply about Rubygems or Ruby itself) in our [issue tracker](https://github.com/bundler/bundler/issues) or on [Stack Overflow](http://stackoverflow.com/questions/tagged/bundler).
-Additional documentation and explanation is always helpful, too. If you have any suggestions for the Bundler website [gembundler.com](http://www.gembundler.com), we would absolutely love it if you opened an issue or pull request on the [bundler-site-middleman](https://github.com/bundler/bundler-site-middleman) repository.
+Additional documentation and explanation is always helpful, too. If you have any suggestions for the Bundler website [gembundler.com](http://www.gembundler.com), we would absolutely love it if you opened an issue or pull request on the [bundler-site](https://github.com/bundler/bundler-site) repository.
Finally, sharing your experiences and discoveries by writing them up is a valuable way to help others who have similar problems or experiences in the future. You can write a blog post, create an example and commit it to Github, take screenshots, or make videos.
@@ -89,7 +108,7 @@ Examples of how Bundler is used help everyone, and we’ve discovered that peopl
If you let someone on the core team know you wrote about Bundler, we will add your post to the list of Bundler resources on the Github project wiki.
-# Your first bugfix
+# Your first commits
If you’re interested in contributing to Bundler, that’s awesome! We’d love your help.
diff --git a/ISSUES.md b/ISSUES.md
index e0233c131c..10a7953fda 100644
--- a/ISSUES.md
+++ b/ISSUES.md
@@ -78,6 +78,6 @@ If you are using Rails 2.3, please also include:
If you have either `rubygems-bundler` or `open_gem` installed, please try removing them and then following the troubleshooting steps above before opening a new ticket.
-[Create a gist](https://gist.github.com) containing all of that information, then visit the [Bundler issue tracker](https://github.com/carlhuda/bundler/issues) and [create a ticket](https://github.com/carlhuda/bundler/issues/new) describing your problem and linking to your gist.
+[Create a gist](https://gist.github.com) containing all of that information, then visit the [Bundler issue tracker](https://github.com/bundler/bundler/issues) and [create a ticket](https://github.com/bundler/bundler/issues/new) describing your problem and linking to your gist.
Thanks for reporting issues and helping make Bundler better!
diff --git a/LICENSE.md b/LICENSE.md
index 5e89f93c05..e356f59f94 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,4 +1,4 @@
-Portions copyright (c) 2010 Andre Arko
+Portions copyright (c) 2010 Andre Arko
Portions copyright (c) 2009 Engine Yard
MIT License
diff --git a/README.md b/README.md
index 70e5271d2d..9f9b3e7e48 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
-# Bundler: a gem to bundle gems [![Build Status](https://secure.travis-ci.org/carlhuda/bundler.png?branch=1-3-stable)](http://travis-ci.org/carlhuda/bundler)
+[![Code Climate](https://codeclimate.com/github/bundler/bundler.png)](https://codeclimate.com/github/bundler/bundler)
+[![Build Status](https://secure.travis-ci.org/bundler/bundler.png?branch=1-3-stable)](http://travis-ci.org/bundler/bundler)
+# Bundler: a gem to bundle gems
Bundler keeps ruby applications running the same code on every machine.
It does this by managing the gems that the application depends on. Given a list of gems, it can automatically download and install those gems, as well as any other gems needed by the gems that are listed. Before installing gems, it checks the versions of every gem to make sure that they are compatible, and can all be loaded at the same time. After the gems have been installed, Bundler can help you update some or all of them when new versions become available. Finally, it records the exact versions that have been installed, so that others can install the exact same gems.
@@ -18,15 +20,15 @@ See [gembundler.com](http://gembundler.com) for the full documentation.
### Troubleshooting
-For help with common problems, see [ISSUES](https://github.com/carlhuda/bundler/blob/master/ISSUES.md).
+For help with common problems, see [ISSUES](https://github.com/bundler/bundler/blob/master/ISSUES.md).
### Contributing
-If you'd like to contribute to Bundler, that's awesome, and we <3 you. There's a guide to contributing to Bundler (both code and general help) over in [CONTRIBUTE](https://github.com/carlhuda/bundler/blob/master/CONTRIBUTE.md)
+If you'd like to contribute to Bundler, that's awesome, and we <3 you. There's a guide to contributing to Bundler (both code and general help) over in [DEVELOPMENT](https://github.com/bundler/bundler/blob/master/DEVELOPMENT.md)
### Development
-To see what has changed in recent versions of Bundler, see the [CHANGELOG](https://github.com/carlhuda/bundler/blob/master/CHANGELOG.md).
+To see what has changed in recent versions of Bundler, see the [CHANGELOG](https://github.com/bundler/bundler/blob/master/CHANGELOG.md).
The `master` branch contains our current progress towards version 1.3. Versions 1.0 to 1.2 each have their own stable branches. Please submit bugfixes as pull requests to the stable branch for the version you would like to fix.
diff --git a/Rakefile b/Rakefile
index b7897680b7..e15adced54 100644
--- a/Rakefile
+++ b/Rakefile
@@ -85,10 +85,11 @@ begin
system "sudo rm -rf #{File.expand_path('../tmp/sudo_gem_home', __FILE__)}"
end
+ # Rubygems specs by version
namespace :rubygems do
- # Rubygems specs by version
rubyopt = ENV["RUBYOPT"]
- %w(master v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.25 v2.0.2).each do |rg|
+ # When editing this list, also edit .travis.yml!
+ %w(master v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.25 v2.0.3).each do |rg|
desc "Run specs with Rubygems #{rg}"
RSpec::Core::RakeTask.new(rg) do |t|
t.rspec_opts = %w(-fs --color)
@@ -219,18 +220,4 @@ end
task :build => ["man:clean", "man:build"]
task :release => ["man:clean", "man:build"]
-namespace :vendor do
- desc "Build the vendor dir"
- task :build => :clean do
- sh "git clone git://github.com/wycats/thor.git lib/bundler/vendor/tmp"
- sh "mv lib/bundler/vendor/tmp/lib/* lib/bundler/vendor/"
- rm_rf "lib/bundler/vendor/tmp"
- end
-
- desc "Clean the vendor dir"
- task :clean do
- rm_rf "lib/bundler/vendor"
- end
-end
-
task :default => :spec
diff --git a/bin/bundle b/bin/bundle
index fc2aca105a..a6088f83b7 100755
--- a/bin/bundle
+++ b/bin/bundle
@@ -1,7 +1,4 @@
#!/usr/bin/env ruby
-
-# Trap interrupts to quit cleanly. See
-# https://twitter.com/mitchellh/status/283014103189053442
Signal.trap("INT") { exit 1 }
require 'bundler'
@@ -16,10 +13,8 @@ $:.each do |path|
end
require 'bundler/cli'
+# Force Thor to raise exceptions so we can exit non-zero.
+ENV["THOR_DEBUG"] = "1"
+
require 'bundler/friendly_errors'
-Bundler.with_friendly_errors {
- # Set debug flag so we can rescue Thor::error's
- # and set the correct exit code.
- ENV["THOR_DEBUG"] = "1"
- Bundler::CLI.start
-}
+Bundler.with_friendly_errors { Bundler::CLI.start }
diff --git a/lib/bundler.rb b/lib/bundler.rb
index 5e9527a2ac..883917cb3a 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -5,6 +5,8 @@ require 'bundler/gem_path_manipulation'
require 'bundler/rubygems_ext'
require 'bundler/rubygems_integration'
require 'bundler/version'
+require 'bundler/constants'
+require 'bundler/current_ruby'
module Bundler
preserve_gem_path
@@ -48,25 +50,22 @@ module Bundler
end
end
- class GemfileNotFound < BundlerError; status_code(10) ; end
- class GemNotFound < BundlerError; status_code(7) ; end
- class GemfileError < BundlerError; status_code(4) ; end
- class InstallError < BundlerError; status_code(5) ; end
- class InstallHookError < BundlerError; status_code(6) ; end
- class PathError < BundlerError; status_code(13) ; end
- class GitError < BundlerError; status_code(11) ; end
- class DeprecatedError < BundlerError; status_code(12) ; end
- class GemspecError < BundlerError; status_code(14) ; end
- class InvalidOption < BundlerError; status_code(15) ; end
- class ProductionError < BundlerError; status_code(16) ; end
- class HTTPError < BundlerError; status_code(17) ; end
- class RubyVersionMismatch < BundlerError; status_code(18) ; end
- class SecurityError < BundlerError; status_code(19) ; end
- class LockfileError < BundlerError; status_code(20) ; end
-
- WINDOWS = RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)!
- FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
- NULL = WINDOWS ? "NUL" : "/dev/null"
+ class GemfileNotFound < BundlerError; status_code(10) ; end
+ class GemNotFound < BundlerError; status_code(7) ; end
+ class GemfileError < BundlerError; status_code(4) ; end
+ class InstallError < BundlerError; status_code(5) ; end
+ class InstallHookError < BundlerError; status_code(6) ; end
+ class PathError < BundlerError; status_code(13) ; end
+ class GitError < BundlerError; status_code(11) ; end
+ class DeprecatedError < BundlerError; status_code(12) ; end
+ class GemspecError < BundlerError; status_code(14) ; end
+ class InvalidOption < BundlerError; status_code(15) ; end
+ class ProductionError < BundlerError; status_code(16) ; end
+ class HTTPError < BundlerError; status_code(17) ; end
+ class RubyVersionMismatch < BundlerError; status_code(18) ; end
+ class SecurityError < BundlerError; status_code(19) ; end
+ class LockfileError < BundlerError; status_code(20) ; end
+ class CyclicDependencyError < BundlerError; status_code(21) ; end
# Internal errors, should be rescued
class VersionConflict < BundlerError
@@ -80,8 +79,7 @@ module Bundler
status_code(6)
end
- class InvalidSpecSet < StandardError; end
- class MarshalError < StandardError; end
+ class MarshalError < StandardError; end
class << self
attr_writer :ui, :bundle_path
@@ -140,6 +138,11 @@ module Bundler
Bundler::Environment.new(root, definition)
end
+ # Returns an instance of Bundler::Definition for given Gemfile and lockfile
+ #
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
+ # to be updated or true if all gems should be updated
+ # @return [Bundler::Definition]
def definition(unlock = nil)
@definition = nil if unlock
@definition ||= begin
@@ -262,7 +265,7 @@ module Bundler
bin_dir = bin_dir.parent until bin_dir.exist?
# if any directory is not writable, we need sudo
- dirs = [path, bin_dir] | Dir[path.join('*')]
+ dirs = [path, bin_dir] | Dir[path.join('*').to_s]
sudo_needed = dirs.find{|d| !File.writable?(d) }
end
@@ -290,7 +293,19 @@ module Bundler
end
def sudo(str)
- `sudo -p 'Enter your password to install the bundled RubyGems to your system: ' #{str}`
+ prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, '').strip + " "
+ Your user account isn't allowed to install to the system Rubygems.
+ You can cancel this installation and run:
+
+ bundle install --path vendor/bundle
+
+ to install the gems into ./vendor/bundle/, or you can enter your password
+ and install the bundled gems to Rubygems using sudo.
+
+ Password:
+ PROMPT
+
+ `sudo -p "#{prompt}" #{str}`
end
def read_file(file)
@@ -316,7 +331,7 @@ module Bundler
path = Pathname.new(file)
# Eval the gemspec from its parent directory, because some gemspecs
# depend on "./" relative paths.
- Dir.chdir(path.dirname.to_s) do
+ SharedHelpers.chdir(path.dirname.to_s) do
contents = path.read
if contents[0..2] == "---" # YAML header
eval_yaml_gemspec(path, contents)
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 210313ae1d..5953f5a025 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -12,6 +12,7 @@ module Bundler
rescue UnknownArgumentError => e
raise InvalidOption, e.message
ensure
+ options ||= {}
Bundler.ui = UI::Shell.new(options)
Bundler.ui.level = "debug" if options["verbose"]
end
@@ -173,6 +174,9 @@ module Bundler
"Gem trust policy (like gem install -P). Must be one of " +
Bundler.rubygems.security_policies.keys.join('|') unless
Bundler.rubygems.security_policies.empty?
+ method_option "jobs", :aliases => "-j", :type => :numeric, :banner =>
+ "Specify the number of jobs to run in parallel"
+
def install
opts = options.dup
if opts[:without]
@@ -224,6 +228,8 @@ module Bundler
opts[:system] = true
end
+ opts["no-cache"] ||= opts[:local]
+
# Can't use Bundler.settings for this because settings needs gemfile.dirname
Bundler.settings[:path] = nil if opts[:system]
Bundler.settings[:path] = "vendor/bundle" if opts[:deployment]
@@ -245,7 +251,7 @@ module Bundler
definition = Bundler.definition
definition.validate_ruby!
Installer.install(Bundler.root, definition, opts)
- Bundler.load.cache if Bundler.root.join("vendor/cache").exist? && !options["no-cache"]
+ Bundler.load.cache if Bundler.root.join("vendor/cache").exist? && !opts["no-cache"]
if Bundler.settings[:path]
absolute_path = File.expand_path(Bundler.settings[:path])
@@ -373,18 +379,26 @@ module Bundler
"binstub destination directory (default bin)"
method_option "force", :type => :boolean, :default => false, :banner =>
"overwrite existing binstubs if they exist"
- def binstubs(gem_name)
+ def binstubs(*gems)
Bundler.definition.validate_ruby!
Bundler.settings[:bin] = options["path"] if options["path"]
Bundler.settings[:bin] = nil if options["path"] && options["path"].empty?
installer = Installer.new(Bundler.root, Bundler.definition)
- spec = installer.specs.find{|s| s.name == gem_name }
- raise GemNotFound, not_found_message(gem_name, Bundler.definition.specs) unless spec
- if spec.name == "bundler"
- Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems."
- else
- installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true)
+ if gems.empty?
+ Bundler.ui.error "`bundle binstubs` needs at least one gem to run."
+ exit 1
+ end
+
+ gems.each do |gem_name|
+ spec = installer.specs.find{|s| s.name == gem_name }
+ raise GemNotFound, not_found_message(gem_name, Bundler.definition.specs) unless spec
+
+ if spec.name == "bundler"
+ Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems."
+ else
+ installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true)
+ end
end
end
@@ -404,6 +418,8 @@ module Bundler
Bundler.definition.validate_ruby!
current_specs = Bundler.ui.silence { Bundler.load.specs }
+ current_dependencies = {}
+ Bundler.ui.silence { Bundler.load.dependencies.each { |dep| current_dependencies[dep.name] = dep } }
if gems.empty? && sources.empty?
# We're doing a full update
@@ -417,7 +433,8 @@ module Bundler
out_count = 0
# Loop through the current specs
- current_specs.sort_by { |s| s.name }.each do |current_spec|
+ gemfile_specs, dependency_specs = current_specs.partition { |spec| current_dependencies.has_key? spec.name }
+ [gemfile_specs.sort_by(&:name), dependency_specs.sort_by(&:name)].flatten.each do |current_spec|
next if !gems.empty? && !gems.include?(current_spec.name)
active_spec = definition.index[current_spec.name].sort_by { |b| b.version }
@@ -442,7 +459,9 @@ module Bundler
spec_version = "#{active_spec.version}#{active_spec.git_version}"
current_version = "#{current_spec.version}#{current_spec.git_version}"
- Bundler.ui.info " * #{active_spec.name} (#{spec_version} > #{current_version})"
+ dependency = current_dependencies[current_spec.name]
+ dependency_version = %|Gemfile specifies "#{dependency.requirement}"| if dependency && dependency.specific?
+ Bundler.ui.info " * #{active_spec.name} (#{spec_version} > #{current_version}) #{dependency_version}".rstrip
out_count += 1
end
Bundler.ui.debug "from #{active_spec.loaded_from}"
@@ -474,6 +493,7 @@ module Bundler
desc "package", "Locks and then caches all of the gems into vendor/cache"
method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache."
method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)."
+ method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors."
long_desc <<-D
The package command will copy the .gem files for every gem in the bundle into the
directory ./vendor/cache. If you then check that directory into your source
@@ -481,6 +501,7 @@ module Bundler
bundle without having to download any additional gems.
D
def package
+ Bundler.ui.level = "warn" if options[:quiet]
setup_cache_all
install
# TODO: move cache contents here now that all bundles are locked
diff --git a/lib/bundler/constants.rb b/lib/bundler/constants.rb
new file mode 100644
index 0000000000..d9bf6830d4
--- /dev/null
+++ b/lib/bundler/constants.rb
@@ -0,0 +1,5 @@
+module Bundler
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)!
+ FREEBSD = RbConfig::CONFIG["host_os"] =~ /bsd/
+ NULL = WINDOWS ? "NUL" : "/dev/null"
+end
diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb
new file mode 100644
index 0000000000..bbb9e41303
--- /dev/null
+++ b/lib/bundler/current_ruby.rb
@@ -0,0 +1,88 @@
+module Bundler
+ # Returns current version of Ruby
+ #
+ # @return [CurrentRuby] Current version of Ruby
+ def self.current_ruby
+ @current_ruby ||= CurrentRuby.new
+ end
+
+ class CurrentRuby
+ def on_18?
+ RUBY_VERSION =~ /^1\.8/
+ end
+
+ def on_19?
+ RUBY_VERSION =~ /^1\.9/
+ end
+
+ def on_20?
+ RUBY_VERSION =~ /^2\.0/
+ end
+
+ def ruby?
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev")
+ end
+
+ def ruby_18?
+ ruby? && on_18?
+ end
+
+ def ruby_19?
+ ruby? && on_19?
+ end
+
+ def ruby_20?
+ ruby? && on_20?
+ end
+
+ def mri?
+ !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
+ end
+
+ def mri_18?
+ mri? && on_18?
+ end
+
+ def mri_19?
+ mri? && on_19?
+ end
+
+
+ def mri_20?
+ mri? && on_20?
+ end
+
+ def rbx?
+ ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
+ end
+
+ def jruby?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
+ end
+
+ def maglev?
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
+ end
+
+ def mswin?
+ Bundler::WINDOWS
+ end
+
+ def mingw?
+ Bundler::WINDOWS && Gem::Platform.local.os == "mingw32"
+ end
+
+ def mingw_18?
+ mingw? && on_18?
+ end
+
+ def mingw_19?
+ mingw? && on_19?
+ end
+
+ def mingw_20?
+ mingw? && on_20?
+ end
+
+ end
+end
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 430182b054..be0246fbfe 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -8,6 +8,13 @@ module Bundler
attr_reader :dependencies, :platforms, :sources, :ruby_version,
:locked_deps
+ # Given a gemfile and lockfile creates a Bundler definition
+ #
+ # @param gemfile [Pathname] Path to Gemfile
+ # @param lockfile [Pathname,nil] Path to Gemfile.lock
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
+ # to be updated or true if all gems should be updated
+ # @return [Bundler::Definition]
def self.build(gemfile, lockfile, unlock)
unlock ||= {}
gemfile = Pathname.new(gemfile).expand_path
@@ -19,18 +26,24 @@ module Bundler
Dsl.evaluate(gemfile, lockfile, unlock)
end
-=begin
- How does the new system work?
- ===
- * Load information from Gemfile and Lockfile
- * Invalidate stale locked specs
- * All specs from stale source are stale
- * All specs that are reachable only through a stale
- dependency are stale.
- * If all fresh dependencies are satisfied by the locked
- specs, then we can try to resolve locally.
-=end
+ #
+ # How does the new system work?
+ #
+ # * Load information from Gemfile and Lockfile
+ # * Invalidate stale locked specs
+ # * All specs from stale source are stale
+ # * All specs that are reachable only through a stale
+ # dependency are stale.
+ # * If all fresh dependencies are satisfied by the locked
+ # specs, then we can try to resolve locally.
+ #
+ # @param lockfile [Pathname] Path to Gemfile.lock
+ # @param dependencies [Array(Bundler::Dependency)] array of dependencies from Gemfile
+ # @param sources [Array(Bundler::Source::Rubygems)]
+ # @param unlock [Hash, Boolean, nil] Gems that have been requested
+ # to be updated or true if all gems should be updated
+ # @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version
def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil)
@unlocking = unlock == true || !unlock.empty?
@@ -109,6 +122,12 @@ module Bundler
specs
end
+ # For given dependency list returns a SpecSet with Gemspec of all the required
+ # dependencies.
+ # 1. The method first resolves the dependencies specified in Gemfile
+ # 2. After that it tries and fetches gemspec of resolved dependencies
+ #
+ # @return [Bundler::SpecSet]
def specs
@specs ||= begin
specs = resolve.materialize(requested_dependencies)
@@ -159,6 +178,11 @@ module Bundler
specs.for(expand_dependencies(deps))
end
+ # Resolve all the dependencies specified in Gemfile. It ensures that
+ # dependencies that have been already resolved via locked file and are fresh
+ # are reused when resolving dependencies
+ #
+ # @return [SpecSet] resolved dependencies
def resolve
@resolve ||= begin
if Bundler.settings[:frozen] || (!@unlocking && nothing_changed?)
diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb
index 7f153f236c..1fb6d8e1d4 100644
--- a/lib/bundler/dependency.rb
+++ b/lib/bundler/dependency.rb
@@ -70,7 +70,9 @@ module Bundler
def current_platform?
return true if @platforms.empty?
- @platforms.any? { |p| send("#{p}?") }
+ @platforms.any? { |p|
+ Bundler.current_ruby.send("#{p}?")
+ }
end
def to_lock
@@ -79,84 +81,11 @@ module Bundler
out << "\n"
end
- private
- def on_18?
- RUBY_VERSION =~ /^1\.8/
+ def specific?
+ super
+ rescue NoMethodError
+ requirement != ">= 0"
end
-
- def on_19?
- RUBY_VERSION =~ /^1\.9/
- end
-
- def on_20?
- RUBY_VERSION =~ /^2\.0/
- end
-
- def ruby?
- !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev")
- end
-
- def ruby_18?
- ruby? && on_18?
- end
-
- def ruby_19?
- ruby? && on_19?
- end
-
- def ruby_20?
- ruby? && on_20?
- end
-
- def mri?
- !mswin? && (!defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby")
- end
-
- def mri_18?
- mri? && on_18?
- end
-
- def mri_19?
- mri? && on_19?
- end
-
-
- def mri_20?
- mri? && on_20?
- end
-
- def rbx?
- ruby? && defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
- end
-
- def jruby?
- defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
- end
-
- def maglev?
- defined?(RUBY_ENGINE) && RUBY_ENGINE == "maglev"
- end
-
- def mswin?
- Bundler::WINDOWS
- end
-
- def mingw?
- Bundler::WINDOWS && Gem::Platform.local.os == "mingw32"
- end
-
- def mingw_18?
- mingw? && on_18?
- end
-
- def mingw_19?
- mingw? && on_19?
- end
-
- def mingw_20?
- mingw? && on_20?
- end
-
end
end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index d959eea6d3..7f88a1bc5d 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -42,8 +42,9 @@ module Bundler
path = opts && opts[:path] || '.'
name = opts && opts[:name] || '{,*}'
development_group = opts && opts[:development_group] || :development
- path = File.expand_path(path, Bundler.default_gemfile.dirname)
- gemspecs = Dir[File.join(path, "#{name}.gemspec")]
+ expanded_path = File.expand_path(path, Bundler.default_gemfile.dirname)
+
+ gemspecs = Dir[File.join(expanded_path, "#{name}.gemspec")]
case gemspecs.size
when 1
@@ -56,9 +57,9 @@ module Bundler
end
end
when 0
- raise InvalidOption, "There are no gemspecs at #{path}."
+ raise InvalidOption, "There are no gemspecs at #{expanded_path}."
else
- raise InvalidOption, "There are multiple gemspecs at #{path}. Please use the :name option to specify which one."
+ raise InvalidOption, "There are multiple gemspecs at #{expanded_path}. Please use the :name option to specify which one."
end
end
@@ -229,7 +230,7 @@ module Bundler
if github = opts.delete("github")
github = "#{github}/#{github}" unless github.include?("/")
- opts["git"] = "git://github.com/#{github}.git"
+ opts["git"] = "https://github.com/#{github}.git"
end
if gist = opts.delete("gist")
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index f5ad0347e7..627d7d4ade 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -4,11 +4,6 @@ module Bundler
# Handles all the fetching with the rubygems server
class Fetcher
- # How many redirects to allew in one request
- REDIRECT_LIMIT = 5
- # how long to wait for each gemcutter API call
- API_TIMEOUT = 10
-
# This error is raised if the API returns a 413 (only printed in verbose)
class FallbackError < HTTPError; end
# This is the error raised if OpenSSL fails the cert verification
@@ -33,7 +28,7 @@ module Bundler
end
class << self
- attr_accessor :disable_endpoint
+ attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries
@@spec_fetch_map ||= {}
@@ -65,6 +60,13 @@ module Bundler
end
def initialize(remote_uri)
+ # How many redirects to allew in one request
+ @redirect_limit = 5
+ # How long to wait for each gemcutter API call
+ @api_timeout = 10
+ # How many retries for the gemcutter API call
+ @max_retries = 3
+
@remote_uri = remote_uri
@public_uri = remote_uri.dup
@public_uri.user, @public_uri.password = nil, nil # don't print these
@@ -77,7 +79,7 @@ module Bundler
raise SSLError if @remote_uri.scheme == "https"
@connection = Net::HTTP.new(@remote_uri.host, @remote_uri.port)
end
- @connection.read_timeout = API_TIMEOUT
+ @connection.read_timeout = @api_timeout
Socket.do_not_reverse_lookup = true
end
@@ -85,17 +87,28 @@ module Bundler
# fetch a gem specification
def fetch_spec(spec)
spec = spec - [nil, 'ruby', '']
- spec_file_name = "#{spec.join '-'}.gemspec.rz"
-
- uri = URI.parse("#{@remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}")
+ spec_file_name = "#{spec.join '-'}.gemspec"
- spec_rz = (uri.scheme == "file") ? Gem.read_binary(uri.path) : fetch(uri)
- Bundler.load_marshal Gem.inflate(spec_rz)
+ uri = URI.parse("#{@remote_uri}#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}.rz")
+ if uri.scheme == 'file'
+ Bundler.load_marshal Gem.inflate(Gem.read_binary(uri.path))
+ elsif cached_spec_path = gemspec_cached_path(spec_file_name)
+ Bundler.load_gemspec(cached_spec_path)
+ else
+ Bundler.load_marshal Gem.inflate(fetch(uri))
+ end
rescue MarshalError => e
raise HTTPError, "Gemspec #{spec} contained invalid data.\n" \
"Your network or your gem server is probably having issues right now."
end
+ # cached gem specification path, if one exists
+ def gemspec_cached_path spec_file_name
+ paths = Bundler.rubygems.spec_cache_dirs.map { |dir| File.join(dir, spec_file_name) }
+ paths = paths.select {|path| File.file? path }
+ paths.first
+ end
+
# return the specs in the bundler format as an index
def specs(gem_names, source)
index = Index.new
@@ -157,14 +170,19 @@ module Bundler
# 2. Marshal blob doesn't load properly
# 3. One of the YAML gemspecs has the Syck::DefaultKey problem
rescue HTTPError, MarshalError, GemspecError => e
- @use_api = false
-
# new line now that the dots are over
Bundler.ui.info "" unless Bundler.ui.debug?
Bundler.ui.debug "Error during API request. #{e.class}: #{e.message}"
Bundler.ui.debug e.backtrace.join(" ")
+ @current_retries ||= 0
+ if @current_retries < @max_retries
+ @current_retries += 1
+ retry
+ end
+
+ @use_api = false
return nil
end
@@ -194,15 +212,15 @@ module Bundler
HTTP_ERRORS << Net::HTTP::Persistent::Error if defined?(Net::HTTP::Persistent)
def fetch(uri, counter = 0)
- raise HTTPError, "Too many redirects" if counter >= REDIRECT_LIMIT
+ raise HTTPError, "Too many redirects" if counter >= @redirect_limit
begin
Bundler.ui.debug "Fetching from: #{uri}"
+ req = Net::HTTP::Get.new uri.request_uri
+ req.basic_auth(uri.user, uri.password) if uri.user && uri.password
if defined?(Net::HTTP::Persistent)
- response = @connection.request(uri)
+ response = @connection.request(uri, req)
else
- req = Net::HTTP::Get.new uri.request_uri
- req.basic_auth(uri.user, uri.password) if uri.user && uri.password
response = @connection.request(req)
end
rescue OpenSSL::SSL::SSLError
diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb
index 9f3763db92..6b7a1760ca 100644
--- a/lib/bundler/gem_helper.rb
+++ b/lib/bundler/gem_helper.rb
@@ -24,7 +24,7 @@ module Bundler
def initialize(base = nil, name = nil)
Bundler.ui = UI::Shell.new
- @base = (base ||= Dir.pwd)
+ @base = (base ||= SharedHelpers.pwd)
gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "{,*}.gemspec")]
raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1
@spec_path = gemspecs.first
@@ -153,7 +153,7 @@ module Bundler
cmd << " 2>&1"
outbuf = ''
Bundler.ui.debug(cmd)
- Dir.chdir(base) {
+ SharedHelpers.chdir(base) {
outbuf = `#{cmd}`
if $? == 0
block.call(outbuf) if block
diff --git a/lib/bundler/gem_installer.rb b/lib/bundler/gem_installer.rb
index 7d84939853..749ff53653 100644
--- a/lib/bundler/gem_installer.rb
+++ b/lib/bundler/gem_installer.rb
@@ -5,5 +5,14 @@ module Bundler
def check_executable_overwrite(filename)
# Bundler needs to install gems regardless of binstub overwriting
end
+
+ if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby?
+ def build_extensions
+ # Gain the lock because rubygems use Dir.chdir
+ SharedHelpers.chdir('.') do
+ super
+ end
+ end
+ end
end
end
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
index 7f19e94d19..f6c1eaefc7 100644
--- a/lib/bundler/installer.rb
+++ b/lib/bundler/installer.rb
@@ -1,5 +1,6 @@
require 'erb'
require 'rubygems/dependency_installer'
+require 'bundler/parallel_workers'
module Bundler
class Installer < Environment
@@ -87,8 +88,14 @@ module Bundler
# as dependencies might actually affect the installation of
# the gem.
Installer.post_install_messages = {}
- specs.each do |spec|
- install_gem_from_spec(spec, options[:standalone])
+
+ size = options[:jobs] || 1
+ size = [size, 1].max
+
+ if size > 1 && can_install_parallely?
+ install_in_parallel size, options[:standalone]
+ else
+ install_sequentially options[:standalone]
end
lock
@@ -102,13 +109,12 @@ module Bundler
# Fetch the build settings, if there are any
settings = Bundler.settings["build.#{spec.name}"]
+ message = nil
Bundler.rubygems.with_build_args [settings] do
- spec.source.install(spec)
- Bundler.ui.debug "from #{spec.loaded_from} "
+ message = spec.source.install(spec)
+ Bundler.ui.debug " #{spec.name} (#{spec.version}) from #{spec.loaded_from}"
end
- # newline comes after installing, some gems say "with native extensions"
- Bundler.ui.info ""
if Bundler.settings[:bin] && standalone
generate_standalone_bundler_executable_stubs(spec)
elsif Bundler.settings[:bin]
@@ -116,6 +122,7 @@ module Bundler
end
FileUtils.rm_rf(Bundler.tmp)
+ message
rescue Exception => e
# install hook failed
raise e if e.is_a?(Bundler::InstallHookError) || e.is_a?(Bundler::SecurityError)
@@ -184,6 +191,16 @@ module Bundler
end
private
+ def can_install_parallely?
+ if Bundler.current_ruby.mri? || Bundler.rubygems.provides?(">= 2.1.0.rc")
+ true
+ else
+ Bundler.ui.warn "Rubygems #{Gem::VERSION} is not threadsafe, so your "\
+ "gems must be installed one at a time. Upgrade to Rubygems 2.1 or "\
+ "higher to enable parallel gem installation."
+ false
+ end
+ end
def generate_standalone_bundler_executable_stubs(spec)
# double-assignment to avoid warnings about variables that will be used by ERB
@@ -236,5 +253,57 @@ module Bundler
end
end
end
+
+ def install_sequentially(standalone)
+ specs.each do |spec|
+ message = install_gem_from_spec spec, standalone
+ if message
+ Installer.post_install_messages[spec.name] = message
+ end
+ end
+ end
+
+ def install_in_parallel(size, standalone)
+ name2spec = {}
+ remains = {}
+ enqueued = {}
+ specs.each do |spec|
+ name2spec[spec.name] = spec
+ remains[spec.name] = true
+ end
+
+ worker_pool = ParallelWorkers.worker_pool size, lambda { |name|
+ spec = name2spec[name]
+ message = install_gem_from_spec spec, standalone
+ { :name => spec.name, :post_install => message }
+ }
+ specs.each do |spec|
+ deps = spec.dependencies.select { |dep| dep.type != :development }
+ if deps.empty?
+ worker_pool.enq spec.name
+ enqueued[spec.name] = true
+ end
+ end
+
+ until remains.empty?
+ message = worker_pool.deq
+ remains.delete message[:name]
+ if message[:post_install]
+ Installer.post_install_messages[message[:name]] = message[:post_install]
+ end
+ remains.keys.each do |name|
+ next if enqueued[name]
+ spec = name2spec[name]
+ deps = spec.dependencies.select { |dep| remains[dep.name] and dep.type != :development }
+ if deps.empty?
+ worker_pool.enq name
+ enqueued[name] = true
+ end
+ end
+ end
+ message
+ ensure
+ worker_pool && worker_pool.stop
+ end
end
end
diff --git a/lib/bundler/parallel_workers.rb b/lib/bundler/parallel_workers.rb
new file mode 100644
index 0000000000..3071b49ac1
--- /dev/null
+++ b/lib/bundler/parallel_workers.rb
@@ -0,0 +1,18 @@
+require 'thread'
+
+require "bundler/parallel_workers/worker"
+
+module Bundler
+ module ParallelWorkers
+ autoload :UnixWorker, "bundler/parallel_workers/unix_worker"
+ autoload :ThreadWorker, "bundler/parallel_workers/thread_worker"
+
+ def self.worker_pool(size, job)
+ if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby?
+ ThreadWorker.new(size, job)
+ else
+ UnixWorker.new(size, job)
+ end
+ end
+ end
+end
diff --git a/lib/bundler/parallel_workers/thread_worker.rb b/lib/bundler/parallel_workers/thread_worker.rb
new file mode 100644
index 0000000000..eed69dbd3e
--- /dev/null
+++ b/lib/bundler/parallel_workers/thread_worker.rb
@@ -0,0 +1,27 @@
+module Bundler
+ module ParallelWorkers
+ class ThreadWorker < Worker
+
+ private
+
+ # On platforms where fork is not available
+ # use Threads for parallely downloading gems
+ #
+ # @param size [Integer] Size of thread worker pool
+ # @param func [Proc] Job to be run inside thread worker pool
+ def prepare_workers(size, func)
+ @threads = size.times.map do |i|
+ Thread.start do
+ Thread.current.abort_on_exception = true
+ loop do
+ obj = @request_queue.deq
+ break if obj.equal? POISON
+ @response_queue.enq func.call(obj)
+ end
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/bundler/parallel_workers/unix_worker.rb b/lib/bundler/parallel_workers/unix_worker.rb
new file mode 100644
index 0000000000..1d5fee6697
--- /dev/null
+++ b/lib/bundler/parallel_workers/unix_worker.rb
@@ -0,0 +1,88 @@
+module Bundler
+ module ParallelWorkers
+ # UnixWorker is used only on platforms where fork is available. The way
+ # this code works is, it forks a preconfigured number of workers and then
+ # It starts preconfigured number of threads that write to the connected pipe.
+ class UnixWorker < Worker
+
+ class JobHandler < Struct.new(:pid, :io_r, :io_w)
+ def work(obj)
+ Marshal.dump obj, io_w
+ Marshal.load io_r
+ rescue IOError
+ nil
+ end
+ end
+
+ private
+
+ # Start forked workers for downloading gems. This version of worker
+ # is only used on platforms where fork is available.
+ #
+ # @param size [Integer] Size of worker pool
+ # @param func [Proc] Job that should be executed in the worker
+ def prepare_workers(size, func)
+ @workers = size.times.map do
+ child_read, parent_write = IO.pipe
+ parent_read, child_write = IO.pipe
+
+ pid = Process.fork do
+ begin
+ parent_read.close
+ parent_write.close
+
+ while !child_read.eof?
+ obj = Marshal.load child_read
+ Marshal.dump func.call(obj), child_write
+ end
+ rescue Exception => e
+ begin
+ Marshal.dump WrappedException.new(e), child_write
+ rescue Errno::EPIPE
+ nil
+ end
+ ensure
+ child_read.close
+ child_write.close
+ end
+ end
+
+ child_read.close
+ child_write.close
+ JobHandler.new pid, parent_read, parent_write
+ end
+ end
+
+ # Start the threads whose job is basically to wait for incoming messages
+ # on request queue and write that message to the connected pipe. Also retrieve
+ # messages from child worker via connected pipe and write the message to response queue
+ #
+ # @param size [Integer] Number of threads to be started
+ def prepare_threads(size)
+ @threads = size.times.map do |i|
+ Thread.start do
+ worker = @workers[i]
+ Thread.current.abort_on_exception = true
+ loop do
+ obj = @request_queue.deq
+ break if obj.equal? POISON
+ @response_queue.enq worker.work(obj)
+ end
+ end
+ end
+ end
+
+ # Kill the forked workers by sending SIGINT to them
+ def stop_workers
+ @workers.each do |worker|
+ worker.io_r.close
+ worker.io_w.close
+ Process.kill :INT, worker.pid
+ end
+ @workers.each do |worker|
+ Process.waitpid worker.pid
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/parallel_workers/worker.rb b/lib/bundler/parallel_workers/worker.rb
new file mode 100644
index 0000000000..975b6a3342
--- /dev/null
+++ b/lib/bundler/parallel_workers/worker.rb
@@ -0,0 +1,68 @@
+module Bundler
+ module ParallelWorkers
+ class Worker
+ POISON = Object.new
+
+ class WrappedException < StandardError
+ attr_reader :exception
+ def initialize(exn)
+ @exception = exn
+ end
+ end
+
+ # Creates a worker pool of specified size
+ #
+ # @param size [Integer] Size of pool
+ # @param func [Proc] job to run in inside the worker pool
+ def initialize(size, func)
+ @request_queue = Queue.new
+ @response_queue = Queue.new
+ prepare_workers size, func
+ prepare_threads size
+ end
+
+ # Enque a request to be executed in the worker pool
+ #
+ # @param obj [String] mostly it is name of spec that should be downloaded
+ def enq(obj)
+ @request_queue.enq obj
+ end
+
+ # Retrieves results of job function being executed in worker pool
+ def deq
+ result = @response_queue.deq
+ if WrappedException === result
+ raise result.exception
+ end
+ result
+ end
+
+ # Stop the forked workers and started threads
+ def stop
+ stop_workers
+ stop_threads
+ end
+
+ private
+ # Stop the worker threads by sending a poison object down the request queue
+ # so as worker threads after retrieving it, shut themselves down
+ def stop_threads
+ @threads.each do
+ @request_queue.enq POISON
+ end
+ @threads.each do |thread|
+ thread.join
+ end
+ end
+
+ # To be overridden by child classes
+ def prepare_threads(size)
+ end
+
+ # To be overridden by child classes
+ def stop_workers
+ end
+
+ end
+ end
+end
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index 60597e32a9..d2c2a0847f 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -1,4 +1,5 @@
require 'set'
+require 'bundler/safe_catch'
# This is the latest iteration of the gem dependency resolving algorithm. As of now,
# it can resolve (as a success or failure) any set of gem dependencies we throw at it
# in a reasonable amount of time. The most iterations I've seen it take is about 150.
@@ -21,6 +22,9 @@ end
module Bundler
class Resolver
+ include SafeCatch
+ extend SafeCatch
+
ALL = Bundler::Dependency::PLATFORM_MAP.values.uniq.freeze
class SpecGroup < Array
@@ -125,7 +129,7 @@ module Bundler
Bundler.ui.info "Resolving dependencies...", false
base = SpecSet.new(base) unless base.is_a?(SpecSet)
resolver = new(index, source_requirements, base)
- result = catch(:success) do
+ result = safe_catch(:success) do
resolver.start(requirements)
raise resolver.version_conflict
nil
@@ -168,10 +172,10 @@ module Bundler
resolve(reqs, activated)
end
- def resolve(reqs, activated)
+ def resolve(reqs, activated, depth = 0)
# If the requirements are empty, then we are in a success state. Aka, all
# gem dependencies have been resolved.
- throw :success, successify(activated) if reqs.empty?
+ safe_throw :success, successify(activated) if reqs.empty?
indicate_progress
@@ -197,6 +201,8 @@ module Bundler
# Pull off the first requirement so that we can resolve it
current = reqs.shift
+ $stderr.puts "#{' ' * depth}#{current}" if ENV['DEBUG_RESOLVER_TREE']
+
debug { "Attempting:\n #{current}"}
# Check if the gem has already been activated, if it has, we will make sure
@@ -228,7 +234,7 @@ module Bundler
@gems_size[dep] ||= gems_size(dep)
end
- resolve(reqs, activated)
+ resolve(reqs, activated, depth + 1)
else
debug { " * [FAIL] Already activated" }
@errors[existing.name] = [existing, current]
@@ -248,7 +254,7 @@ module Bundler
if parent && parent.name != 'bundler'
debug { " -> Jumping to: #{parent.name}" }
required_by = existing.respond_to?(:required_by) && existing.required_by.last
- throw parent.name, required_by && required_by.name
+ safe_throw parent.name, required_by && required_by.name
else
# The original set of dependencies conflict with the base set of specs
# passed to the resolver. This is by definition an impossible resolve.
@@ -301,7 +307,7 @@ module Bundler
end
matching_versions.reverse_each do |spec_group|
- conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup)
+ conflict = resolve_requirement(spec_group, current, reqs.dup, activated.dup, depth)
conflicts << conflict if conflict
end
@@ -315,7 +321,7 @@ module Bundler
# Choose the closest pivot in the stack that will affect the conflict
errorpivot = (@stack & [req_name, current.required_by.last.name]).last
debug { " -> Jumping to: #{errorpivot}" }
- throw errorpivot, req_name
+ safe_throw errorpivot, req_name
end
end
end
@@ -330,14 +336,14 @@ module Bundler
@stack.reverse_each do |savepoint|
if conflicts.include?(savepoint)
debug { " -> Jumping to: #{savepoint}" }
- throw savepoint
+ safe_throw savepoint
end
end
end
end
end
- def resolve_requirement(spec_group, requirement, reqs, activated)
+ def resolve_requirement(spec_group, requirement, reqs, activated, depth)
# We are going to try activating the spec. We need to keep track of stack of
# requirements that got us to the point of activating this gem.
spec_group.required_by.replace requirement.required_by
@@ -366,9 +372,9 @@ module Bundler
# jump back to this point and try another version of the gem.
length = @stack.length
@stack << requirement.name
- retval = catch(requirement.name) do
+ retval = safe_catch(requirement.name) do
# try to resolve the next option
- resolve(reqs, activated)
+ resolve(reqs, activated, depth)
end
# clear the search cache since the catch means we couldn't meet the
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index eeeed1ef16..7b808a69cc 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -52,7 +52,7 @@ module Gem
def git_version
if @loaded_from && File.exist?(File.join(full_gem_path, ".git"))
- sha = Dir.chdir(full_gem_path){ `git rev-parse HEAD`.strip }
+ sha = Bundler::SharedHelpers.chdir(full_gem_path){ `git rev-parse HEAD`.strip }
" #{sha[0..6]}"
end
end
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index fc38cd6cde..bcd36ca4b9 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -6,6 +6,22 @@ require 'rubygems/config_file'
module Bundler
class RubygemsIntegration
+ def self.version
+ @version ||= Gem::Version.new(Gem::VERSION)
+ end
+
+ def self.provides?(req_str)
+ Gem::Requirement.new(req_str).satisfied_by?(version)
+ end
+
+ def version
+ self.class.version
+ end
+
+ def provides?(req_str)
+ self.class.provides?(req_str)
+ end
+
def build_args
Gem::Command.build_args
end
@@ -79,6 +95,14 @@ module Bundler
Gem.path
end
+ def spec_cache_dirs
+ @spec_cache_dirs ||= begin
+ dirs = gem_path.map {|dir| File.join(dir, 'specifications')}
+ dirs << Gem.spec_cache_dir if Gem.respond_to?(:spec_cache_dir) # Not in Rubygems 2.0.3 or earlier
+ dirs.uniq.select {|dir| File.directory? dir}
+ end
+ end
+
def marshal_spec_dir
Gem::MARSHAL_SPEC_DIR
end
@@ -159,7 +183,7 @@ module Bundler
end
def build_gem(gem_dir, spec)
- Dir.chdir(gem_dir) { build(spec) }
+ SharedHelpers.chdir(gem_dir) { build(spec) }
end
def download_gem(spec, uri, path)
@@ -490,15 +514,15 @@ module Bundler
end
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.99.99')
+ if RubygemsIntegration.provides?(">= 1.99.99")
@rubygems = RubygemsIntegration::Future.new
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.5')
+ elsif RubygemsIntegration.provides?('>= 1.8.5')
@rubygems = RubygemsIntegration::Modern.new
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ elsif RubygemsIntegration.provides?('>= 1.8.0')
@rubygems = RubygemsIntegration::AlmostModern.new
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.7.0')
+ elsif RubygemsIntegration.provides?('>= 1.7.0')
@rubygems = RubygemsIntegration::Transitional.new
- elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.4.0')
+ elsif RubygemsIntegration.provides?('>= 1.4.0')
@rubygems = RubygemsIntegration::Legacy.new
else # Rubygems 1.3.6 and 1.3.7
@rubygems = RubygemsIntegration::Ancient.new
diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb
index 9dd6a5f4f0..a5e51b15c0 100644
--- a/lib/bundler/runtime.rb
+++ b/lib/bundler/runtime.rb
@@ -50,6 +50,7 @@ module Bundler
/^Missing \w+ (?:file\s*)?([^\s]+.rb)$/i,
/^Missing API definition file in (.+)$/i,
/^cannot load such file -- (.+)$/i,
+ /^dlopen\([^)]*\): Library not loaded: (.+)$/i,
]
def require(*groups)
@@ -68,6 +69,8 @@ module Bundler
# dependency. If there are none, use the dependency's name
# as the autorequire.
Array(dep.autorequire || dep.name).each do |file|
+ # Allow `require: true` as an alias for `require: <name>`
+ file = dep.name if file == true
required_file = file
Kernel.require file
end
@@ -224,9 +227,13 @@ module Bundler
rubyopt = [ENV["RUBYOPT"]].compact
if rubyopt.empty? || rubyopt.first !~ /-rbundler\/setup/
rubyopt.unshift %|-rbundler/setup|
- rubyopt.unshift %|-I#{File.expand_path('../..', __FILE__)}|
ENV["RUBYOPT"] = rubyopt.join(' ')
end
+
+ # Set RUBYLIB
+ rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
+ rubylib.unshift File.expand_path('../..', __FILE__)
+ ENV["RUBYLIB"] = rubylib.uniq.join(File::PATH_SEPARATOR)
end
private
diff --git a/lib/bundler/safe_catch.rb b/lib/bundler/safe_catch.rb
new file mode 100644
index 0000000000..b71e51ad12
--- /dev/null
+++ b/lib/bundler/safe_catch.rb
@@ -0,0 +1,101 @@
+# SafeCatch provides a mechanism to safely deepen the stack, performing
+# stack-unrolling similar to catch/throw, but using Fiber or Thread to avoid
+# deepening the stack too quickly.
+#
+# The API is the same as that of catch/throw: SafeCatch#safe_catch takes a "tag"
+# to be rescued when some code deeper in the process raises it. If the catch
+# block completes successfully, that value is returned. If the tag is "thrown"
+# by safe_throw, the tag's value is returned. Other exceptions propagate out as
+# normal.
+#
+# The implementation, however, uses fibers or threads along with raise/rescue to
+# handle "deepening" the stack and unrolling it. On implementations where Fiber
+# is available, it will be used. If Fiber is not available, Thread will be used.
+# If neither of these classes are available, Proc will be used, effectively
+# deepening the stack for each recursion as in normal catch/throw.
+#
+# In order to avoid causing a new issue of creating too many fibers or threads,
+# especially on implementations where fibers are actually backed by native
+# threads, the "safe" recursion mechanism is only used every 20 recursions.
+# Based on experiments with JRuby (which seems to suffer the most from
+# excessively deep stacks), this appears to be a sufficient granularity to
+# prevent stack overflow without spinning up excessive numbers of fibers or
+# threads. This value can be adjusted with the BUNDLER_SAFE_RECURSE_EVERY env
+# var; setting it to zero effectively disables safe recursion.
+
+module Bundler
+ module SafeCatch
+ def safe_catch(tag, &block)
+ if Bundler.current_ruby.jruby?
+ Internal.catch(tag, &block)
+ else
+ catch(tag, &block)
+ end
+ end
+
+ def safe_throw(tag, value = nil)
+ if Bundler.current_ruby.jruby?
+ Internal.throw(tag, value)
+ else
+ throw(tag, value)
+ end
+ end
+
+ module Internal
+ SAFE_RECURSE_EVERY = (ENV['BUNDLER_SAFE_RECURSE_EVERY'] || 20).to_i
+
+ SAFE_RECURSE_CLASS, SAFE_RECURSE_START = case
+ when defined?(Fiber)
+ [Fiber, :resume]
+ when defined?(Thread)
+ [Thread, :join]
+ else
+ [Proc, :call]
+ end
+
+ @recurse_count = 0
+
+ def self.catch(tag, &block)
+ @recurse_count += 1
+ if SAFE_RECURSE_EVERY >= 0 && @recurse_count % SAFE_RECURSE_EVERY == 0
+ SAFE_RECURSE_CLASS.new(&block).send(SAFE_RECURSE_START)
+ else
+ block.call
+ end
+ rescue Result.matcher(tag)
+ $!.value
+ end
+
+ def self.throw(tag, value = nil)
+ raise Result.new(tag, value)
+ end
+
+ class Result < StopIteration
+ def initialize(tag, value)
+ @tag = tag
+ @value = value
+ end
+
+ attr_reader :tag, :value
+
+ # The Matcher class is never instantiated; it is dup'ed and used as a
+ # rescue-clause argument to match Result exceptions based on their tags.
+ module Matcher
+ class << self
+ attr_accessor :tag
+
+ def ===(other)
+ other.respond_to? :tag and @tag.equal? other.tag
+ end
+ end
+ end
+
+ def self.matcher(tag)
+ matcher = Matcher.dup
+ matcher.tag = tag
+ matcher
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb
index e6f4899f06..14e76a6cdb 100644
--- a/lib/bundler/shared_helpers.rb
+++ b/lib/bundler/shared_helpers.rb
@@ -1,7 +1,9 @@
require 'pathname'
require 'rubygems'
+require 'bundler/constants'
require 'bundler/rubygems_integration'
+require 'bundler/current_ruby'
module Gem
class Dependency
@@ -31,6 +33,30 @@ module Bundler
find_gemfile
end
+ if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby?
+ require 'monitor'
+ @chdir_monitor = Monitor.new
+ def chdir(dir, &blk)
+ @chdir_monitor.synchronize do
+ Dir.chdir dir, &blk
+ end
+ end
+
+ def pwd
+ @chdir_monitor.synchronize do
+ Dir.pwd
+ end
+ end
+ else
+ def chdir(dir, &blk)
+ Dir.chdir dir, &blk
+ end
+
+ def pwd
+ Dir.pwd
+ end
+ end
+
private
def find_gemfile
@@ -38,7 +64,7 @@ module Bundler
return given if given && !given.empty?
previous = nil
- current = File.expand_path(Dir.pwd)
+ current = File.expand_path(SharedHelpers.pwd)
until !File.directory?(current) || current == previous
if ENV['BUNDLE_SPEC_RUN']
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index 242d919a3f..9f308881e7 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -151,7 +151,7 @@ module Bundler
end
def install(spec)
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s}"
if requires_checkout? && !@copied
Bundler.ui.debug " * Checking out revision: #{ref}"
git_proxy.copy_to(install_path, submodules)
@@ -159,6 +159,7 @@ module Bundler
@copied = true
end
generate_bin(spec)
+ nil
end
def cache(spec)
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index e480f15a69..800439a0fb 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -58,7 +58,7 @@ module Bundler
File.chmod((0777 & ~File.umask), destination)
end
- Dir.chdir(destination) do
+ SharedHelpers.chdir(destination) do
git %|fetch --force --quiet --tags "#{path}"|
git "reset --hard #{@revision}"
@@ -95,7 +95,7 @@ module Bundler
out
else
raise GitError, "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, " \
- "this error message could probably be more useful. Please submit a ticket at http://github.com/carlhuda/bundler/issues " \
+ "this error message could probably be more useful. Please submit a ticket at http://github.com/bundler/bundler/issues " \
"with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
end
end
@@ -127,7 +127,7 @@ module Bundler
def in_path(&blk)
checkout unless path.exist?
- Dir.chdir(path, &blk)
+ SharedHelpers.chdir(path, &blk)
end
def allowed_in_path
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 625ede4ef5..e0a818b5ab 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -70,8 +70,9 @@ module Bundler
end
def install(spec)
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} "
+ Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s}"
generate_bin(spec, :disable_extensions)
+ nil
end
def cache(spec)
@@ -189,7 +190,7 @@ module Bundler
Bundler.ui.warn "The validation message from Rubygems was:\n #{e.message}"
ensure
if gem_dir && gem_file
- Dir.chdir(gem_dir){ FileUtils.rm_rf(gem_file) if File.exist?(gem_file) }
+ FileUtils.rm_rf(gem_dir.join gem_file)
end
end
diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb
index 0b5d264118..76db202536 100644
--- a/lib/bundler/source/rubygems.rb
+++ b/lib/bundler/source/rubygems.rb
@@ -69,11 +69,11 @@ module Bundler
def install(spec)
if installed_specs[spec].any?
- Bundler.ui.info "Using #{spec.name} (#{spec.version}) "
+ Bundler.ui.info "Using #{spec.name} (#{spec.version})"
return
end
- Bundler.ui.info "Installing #{spec.name} (#{spec.version}) "
+ Bundler.ui.info "Installing #{spec.name} (#{spec.version})"
path = cached_gem(spec)
if Bundler.requires_sudo?
install_path = Bundler.tmp
@@ -94,10 +94,6 @@ module Bundler
).install
end
- if spec.post_install_message
- Installer.post_install_messages[spec.name] = spec.post_install_message
- end
-
# SUDO HAX
if Bundler.requires_sudo?
Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/gems"
@@ -109,8 +105,10 @@ module Bundler
Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.system_bindir}"
end
end
+ Bundler.ui.info "Installed #{spec.name} (#{spec.version})"
installed_spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec"
+ spec.post_install_message
end
def cache(spec)
diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb
index 2cdffdc83f..1746f6d0df 100644
--- a/lib/bundler/spec_set.rb
+++ b/lib/bundler/spec_set.rb
@@ -109,7 +109,22 @@ module Bundler
def sorted
rake = @specs.find { |s| s.name == 'rake' }
- @sorted ||= ([rake] + tsort).compact.uniq
+ begin
+ @sorted ||= ([rake] + tsort).compact.uniq
+ rescue TSort::Cyclic => error
+ cgems = extract_circular_gems(error)
+ raise CyclicDependencyError, "Your Gemfile requires gems that depend" \
+ " depend on each other, creating an infinite loop. Please remove" \
+ " either gem '#{cgems[1]}' or gem '#{cgems[0]}' and try again."
+ end
+ end
+
+ def extract_circular_gems(error)
+ if Bundler.current_ruby.mri? && Bundler.current_ruby.on_19?
+ error.message.scan(/(\w+) \([^)]/).flatten
+ else
+ error.message.scan(/@name="(.*?)"/).flatten
+ end
end
def lookup
diff --git a/lib/bundler/vendor/net/http/persistent.rb b/lib/bundler/vendor/net/http/persistent.rb
index dcdc5b53cb..f99f0625ed 100644
--- a/lib/bundler/vendor/net/http/persistent.rb
+++ b/lib/bundler/vendor/net/http/persistent.rb
@@ -1,5 +1,9 @@
require 'net/http'
-require 'net/https'
+begin
+ require 'net/https'
+rescue LoadError
+ # net/https or openssl
+end if RUBY_VERSION < '1.9' # but only for 1.8
require 'net/http/faster'
require 'uri'
require 'cgi' # for escaping
@@ -9,6 +13,8 @@ begin
rescue LoadError
end
+autoload :OpenSSL, 'openssl'
+
##
# Persistent connections for Net::HTTP
#
@@ -37,6 +43,11 @@ end
# # perform a GET
# response = http.request uri
#
+# # or
+#
+# get = Net::HTTP::Get.new uri.request_uri
+# response = http.request get
+#
# # create a POST
# post_uri = uri + 'create'
# post = Net::HTTP::Post.new post_uri.path
@@ -45,6 +56,10 @@ end
# # perform the POST, the URI is always required
# response http.request post_uri, post
#
+# Note that for GET, HEAD and other requests that do not have a body you want
+# to use URI#request_uri not URI#path. The request_uri contains the query
+# params which are sent in the body for other requests.
+#
# == SSL
#
# SSL connections are automatically created depending upon the scheme of the
@@ -105,6 +120,13 @@ end
# The amount of time allowed between reading two chunks from the socket. Set
# through #read_timeout
#
+# === Max Requests
+#
+# The number of requests that should be made before opening a new connection.
+# Typically many keep-alive capable servers tune this to 100 or less, so the
+# 101st request will fail with ECONNRESET. If unset (default), this value has no
+# effect, if set, connections will be reset on the request after max_requests.
+#
# === Open Timeout
#
# The amount of time to wait for a connection to be opened. Set through
@@ -174,9 +196,29 @@ class Net::HTTP::Persistent
EPOCH = Time.at 0 # :nodoc:
##
+ # Is OpenSSL available? This test works with autoload
+
+ HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc:
+
+ ##
# The version of Net::HTTP::Persistent you are using
- VERSION = '2.8'
+ VERSION = '2.9'
+
+ ##
+ # Exceptions rescued for automatic retry on ruby 2.0.0. This overlaps with
+ # the exception list for ruby 1.x.
+
+ RETRIED_EXCEPTIONS = [ # :nodoc:
+ (Net::ReadTimeout if Net.const_defined? :ReadTimeout),
+ IOError,
+ EOFError,
+ Errno::ECONNRESET,
+ Errno::ECONNABORTED,
+ Errno::EPIPE,
+ (OpenSSL::SSL::SSLError if HAVE_OPENSSL),
+ Timeout::Error,
+ ].compact
##
# Error class for errors raised by Net::HTTP::Persistent. Various
@@ -226,6 +268,8 @@ class Net::HTTP::Persistent
$stderr.puts "sleeping #{sleep_time}" if $DEBUG
sleep sleep_time
end
+ rescue
+ # ignore StandardErrors, we've probably found the idle timeout.
ensure
http.shutdown
@@ -288,6 +332,12 @@ class Net::HTTP::Persistent
attr_accessor :idle_timeout
##
+ # Maximum number of requests on a connection before it is considered expired
+ # and automatically closed.
+
+ attr_accessor :max_requests
+
+ ##
# The value sent in the Keep-Alive header. Defaults to 30. Not needed for
# HTTP/1.1 servers.
#
@@ -442,6 +492,7 @@ class Net::HTTP::Persistent
@open_timeout = nil
@read_timeout = nil
@idle_timeout = 5
+ @max_requests = nil
@socket_options = []
@socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
@@ -458,15 +509,22 @@ class Net::HTTP::Persistent
@private_key = nil
@ssl_version = nil
@verify_callback = nil
- @verify_mode = OpenSSL::SSL::VERIFY_PEER
+ @verify_mode = nil
@cert_store = nil
@generation = 0 # incremented when proxy URI changes
@ssl_generation = 0 # incremented when SSL session variables change
- @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
+
+ if HAVE_OPENSSL then
+ @verify_mode = OpenSSL::SSL::VERIFY_PEER
+ @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
+ end
@retry_change_requests = false
+ @ruby_1 = RUBY_VERSION < '2'
+ @retried_on_ruby_2 = !@ruby_1
+
self.proxy = proxy if proxy
end
@@ -536,6 +594,9 @@ class Net::HTTP::Persistent
use_ssl = uri.scheme.downcase == 'https'
if use_ssl then
+ raise Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless
+ HAVE_OPENSSL
+
ssl_generation = @ssl_generation
ssl_cleanup ssl_generation
@@ -606,10 +667,12 @@ class Net::HTTP::Persistent
end
##
- # Returns true if the connection should be reset due to an idle timeout,
- # false otherwise.
+ # Returns true if the connection should be reset due to an idle timeout, or
+ # maximum request count, false otherwise.
def expired? connection
+ requests = Thread.current[@request_key][connection.object_id]
+ return true if @max_requests && requests >= @max_requests
return false unless @idle_timeout
return true if @idle_timeout.zero?
@@ -679,10 +742,15 @@ class Net::HTTP::Persistent
end
##
- # Is the request idempotent or is retry_change_requests allowed
+ # Is the request +req+ idempotent or is retry_change_requests allowed.
+ #
+ # If +retried_on_ruby_2+ is true, true will be returned if we are on ruby,
+ # retry_change_requests is allowed and the request is not idempotent.
- def can_retry? req
- retry_change_requests or idempotent?(req)
+ def can_retry? req, retried_on_ruby_2 = false
+ return @retry_change_requests && !idempotent?(req) if retried_on_ruby_2
+
+ @retry_change_requests || idempotent?(req)
end
if RUBY_VERSION > '1.9' then
@@ -901,31 +969,14 @@ class Net::HTTP::Persistent
#
# +req+ must be a Net::HTTPRequest subclass (see Net::HTTP for a list).
#
- # If there is an error and the request is idempontent according to RFC 2616
+ # If there is an error and the request is idempotent according to RFC 2616
# it will be retried automatically.
def request uri, req = nil, &block
retried = false
bad_response = false
- req = Net::HTTP::Get.new uri.request_uri unless req
-
- @headers.each do |pair|
- req.add_field(*pair)
- end
-
- if uri.user or uri.password
- req.basic_auth uri.user, uri.password
- end
-
- @override_headers.each do |name, value|
- req[name] = value
- end
-
- unless req['Connection'] then
- req.add_field 'Connection', 'keep-alive'
- req.add_field 'Keep-Alive', @keep_alive
- end
+ req = request_setup req || uri
connection = connection_for uri
connection_id = connection.object_id
@@ -950,23 +1001,25 @@ class Net::HTTP::Persistent
bad_response = true
retry
- rescue IOError, EOFError, Timeout::Error,
- Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE,
- Errno::EINVAL, OpenSSL::SSL::SSLError => e
-
- if retried or not can_retry? req
- due_to = "(due to #{e.message} - #{e.class})"
- message = error_message connection
+ rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2
+ request_failed e, req, connection if
+ retried or not can_retry? req, @retried_on_ruby_2
- finish connection
+ reset connection
- raise Error, "too many connection resets #{due_to} #{message}"
- end
+ retried = true
+ retry
+ rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2
+ request_failed e, req, connection if retried or not can_retry? req
reset connection
retried = true
retry
+ rescue Exception => e
+ finish connection
+
+ raise
ensure
Thread.current[@timeout_key][connection_id] = Time.now
end
@@ -977,6 +1030,51 @@ class Net::HTTP::Persistent
end
##
+ # Raises an Error for +exception+ which resulted from attempting the request
+ # +req+ on the +connection+.
+ #
+ # Finishes the +connection+.
+
+ def request_failed exception, req, connection # :nodoc:
+ due_to = "(due to #{exception.message} - #{exception.class})"
+ message = "too many connection resets #{due_to} #{error_message connection}"
+
+ finish connection
+
+
+ raise Error, message, exception.backtrace
+ end
+
+ ##
+ # Creates a GET request if +req_or_uri+ is a URI and adds headers to the
+ # request.
+ #
+ # Returns the request.
+
+ def request_setup req_or_uri # :nodoc:
+ req = if URI === req_or_uri then
+ Net::HTTP::Get.new req_or_uri.request_uri
+ else
+ req_or_uri
+ end
+
+ @headers.each do |pair|
+ req.add_field(*pair)
+ end
+
+ @override_headers.each do |name, value|
+ req[name] = value
+ end
+
+ unless req['Connection'] then
+ req.add_field 'Connection', 'keep-alive'
+ req.add_field 'Keep-Alive', @keep_alive
+ end
+
+ req
+ end
+
+ ##
# Shuts down all connections for +thread+.
#
# Uses the current thread by default.
diff --git a/lib/bundler/vendor/thor.rb b/lib/bundler/vendor/thor.rb
index 88b907c153..6880b6d7ae 100644
--- a/lib/bundler/vendor/thor.rb
+++ b/lib/bundler/vendor/thor.rb
@@ -3,21 +3,32 @@ require 'thor/base'
class Thor
class << self
- # Sets the default task when thor is executed without an explicit task to be called.
+ # Allows for custom "Command" package naming.
+ #
+ # === Parameters
+ # name<String>
+ # options<Hash>
+ #
+ def package_name(name, options={})
+ @package_name = name.nil? || name == '' ? nil : name
+ end
+
+ # Sets the default command when thor is executed without an explicit command to be called.
#
# ==== Parameters
- # meth<Symbol>:: name of the default task
- #
- def default_task(meth=nil)
- case meth
- when :none
- @default_task = 'help'
- when nil
- @default_task ||= from_superclass(:default_task, 'help')
- else
- @default_task = meth.to_s
+ # meth<Symbol>:: name of the default command
+ #
+ def default_command(meth=nil)
+ @default_command = case meth
+ when :none
+ 'help'
+ when nil
+ @default_command || from_superclass(:default_command, 'help')
+ else
+ meth.to_s
end
end
+ alias default_task default_command
# Registers another Thor subclass as a command.
#
@@ -36,7 +47,7 @@ class Thor
end
end
- # Defines the usage and the description of the next task.
+ # Defines the usage and the description of the next command.
#
# ==== Parameters
# usage<String>
@@ -45,29 +56,29 @@ class Thor
#
def desc(usage, description, options={})
if options[:for]
- task = find_and_refresh_task(options[:for])
- task.usage = usage if usage
- task.description = description if description
+ command = find_and_refresh_command(options[:for])
+ command.usage = usage if usage
+ command.description = description if description
else
@usage, @desc, @hide = usage, description, options[:hide] || false
end
end
- # Defines the long description of the next task.
+ # Defines the long description of the next command.
#
# ==== Parameters
# long description<String>
#
def long_desc(long_description, options={})
if options[:for]
- task = find_and_refresh_task(options[:for])
- task.long_description = long_description if long_description
+ command = find_and_refresh_command(options[:for])
+ command.long_description = long_description if long_description
else
@long_desc = long_description
end
end
- # Maps an input to a task. If you define:
+ # Maps an input to a command. If you define:
#
# map "-T" => "list"
#
@@ -75,10 +86,10 @@ class Thor
#
# thor -T
#
- # Will invoke the list task.
+ # Will invoke the list command.
#
# ==== Parameters
- # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
#
def map(mappings=nil)
@map ||= from_superclass(:map, {})
@@ -96,7 +107,7 @@ class Thor
@map
end
- # Declares the options for the next task to be declared.
+ # Declares the options for the next command to be declared.
#
# ==== Parameters
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
@@ -112,15 +123,15 @@ class Thor
alias options method_options
# Adds an option to the set of method options. If :for is given as option,
- # it allows you to change the options from a previous defined task.
+ # it allows you to change the options from a previous defined command.
#
- # def previous_task
+ # def previous_command
# # magic
# end
#
- # method_option :foo => :bar, :for => :previous_task
+ # method_option :foo => :bar, :for => :previous_command
#
- # def next_task
+ # def next_command
# # magic
# end
#
@@ -139,38 +150,38 @@ class Thor
#
def method_option(name, options={})
scope = if options[:for]
- find_and_refresh_task(options[:for]).options
+ find_and_refresh_command(options[:for]).options
else
method_options
end
build_option(name, options, scope)
end
-
alias option method_option
- # Prints help information for the given task.
+ # Prints help information for the given command.
#
# ==== Parameters
# shell<Thor::Shell>
- # task_name<String>
+ # command_name<String>
#
- def task_help(shell, task_name)
- meth = normalize_task_name(task_name)
- task = all_tasks[meth]
- handle_no_task_error(meth) unless task
+ def command_help(shell, command_name)
+ meth = normalize_command_name(command_name)
+ command = all_commands[meth]
+ handle_no_command_error(meth) unless command
shell.say "Usage:"
- shell.say " #{banner(task)}"
+ shell.say " #{banner(command)}"
shell.say
- class_options_help(shell, nil => task.options.map { |_, o| o })
- if task.long_description
+ class_options_help(shell, nil => command.options.map { |_, o| o })
+ if command.long_description
shell.say "Description:"
- shell.print_wrapped(task.long_description, :indent => 2)
+ shell.print_wrapped(command.long_description, :indent => 2)
else
- shell.say task.description
+ shell.say command.description
end
end
+ alias task_help command_help
# Prints help information for this class.
#
@@ -178,32 +189,39 @@ class Thor
# shell<Thor::Shell>
#
def help(shell, subcommand = false)
- list = printable_tasks(true, subcommand)
+ list = printable_commands(true, subcommand)
Thor::Util.thor_classes_in(self).each do |klass|
- list += klass.printable_tasks(false)
+ list += klass.printable_commands(false)
end
list.sort!{ |a,b| a[0] <=> b[0] }
- shell.say "Tasks:"
+ if @package_name
+ shell.say "#{@package_name} commands:"
+ else
+ shell.say "Commands:"
+ end
+
shell.print_table(list, :indent => 2, :truncate => true)
shell.say
class_options_help(shell)
end
- # Returns tasks ready to be printed.
- def printable_tasks(all = true, subcommand = false)
- (all ? all_tasks : tasks).map do |_, task|
- next if task.hidden?
+ # Returns commands ready to be printed.
+ def printable_commands(all = true, subcommand = false)
+ (all ? all_commands : commands).map do |_, command|
+ next if command.hidden?
item = []
- item << banner(task, false, subcommand)
- item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
+ item << banner(command, false, subcommand)
+ item << (command.description ? "# #{command.description.gsub(/\s+/m,' ')}" : "")
item
end.compact
end
+ alias printable_tasks printable_commands
def subcommands
@subcommands ||= from_superclass(:subcommands, [])
end
+ alias subtasks subcommands
def subcommand(subcommand, subcommand_class)
self.subcommands << subcommand.to_s
@@ -211,9 +229,10 @@ class Thor
define_method(subcommand) do |*args|
args, opts = Thor::Arguments.split(args)
- invoke subcommand_class, args, opts, :invoked_via_subcommand => true
+ invoke subcommand_class, args, opts, :invoked_via_subcommand => true, :class_options => options
end
end
+ alias subtask subcommand
# Extend check unknown options to accept a hash of conditions.
#
@@ -236,10 +255,10 @@ class Thor
options = check_unknown_options
return false unless options
- task = config[:current_task]
- return true unless task
+ command = config[:current_command]
+ return true unless command
- name = task.name
+ name = command.name
if subcommands.include?(name)
false
@@ -253,16 +272,16 @@ class Thor
end
# Stop parsing of options as soon as an unknown option or a regular
- # argument is encountered. All remaining arguments are passed to the task.
- # This is useful if you have a task that can receive arbitrary additional
+ # argument is encountered. All remaining arguments are passed to the command.
+ # This is useful if you have a command that can receive arbitrary additional
# options, and where those additional options should not be handled by
# Thor.
#
# ==== Example
#
- # To better understand how this is useful, let's consider a task that calls
+ # To better understand how this is useful, let's consider a command that calls
# an external command. A user may want to pass arbitrary options and
- # arguments to that command. The task itself also accepts some options,
+ # arguments to that command. The command itself also accepts some options,
# which should be handled by Thor.
#
# class_option "verbose", :type => :boolean
@@ -288,163 +307,167 @@ class Thor
# --verbose foo
#
# ==== Parameters
- # Symbol ...:: A list of tasks that should be affected.
- def stop_on_unknown_option!(*task_names)
+ # Symbol ...:: A list of commands that should be affected.
+ def stop_on_unknown_option!(*command_names)
@stop_on_unknown_option ||= Set.new
- @stop_on_unknown_option.merge(task_names)
+ @stop_on_unknown_option.merge(command_names)
end
- def stop_on_unknown_option?(task) #:nodoc:
- !!@stop_on_unknown_option && @stop_on_unknown_option.include?(task.name.to_sym)
+ def stop_on_unknown_option?(command) #:nodoc:
+ command && !@stop_on_unknown_option.nil? && @stop_on_unknown_option.include?(command.name.to_sym)
end
- protected
-
- # The method responsible for dispatching given the args.
- def dispatch(meth, given_args, given_opts, config) #:nodoc:
- # There is an edge case when dispatching from a subcommand.
- # A problem occurs invoking the default task. This case occurs
- # when arguments are passed and a default task is defined, and
- # the first given_args does not match the default task.
- # Thor use "help" by default so we skip that case.
- # Note the call to retrieve_task_name. It's called with
- # given_args.dup since that method calls args.shift. Then lookup
- # the task normally. If the first item in given_args is not
- # a task then use the default task. The given_args will be
- # intact later since dup was used.
- if config[:invoked_via_subcommand] && given_args.size >= 1 && default_task != "help" && given_args.first != default_task
- meth ||= retrieve_task_name(given_args.dup)
- task = all_tasks[normalize_task_name(meth)]
- task ||= all_tasks[normalize_task_name(default_task)]
- else
- meth ||= retrieve_task_name(given_args)
- task = all_tasks[normalize_task_name(meth)]
- end
+ protected
+
+ # The method responsible for dispatching given the args.
+ def dispatch(meth, given_args, given_opts, config) #:nodoc:
+ # There is an edge case when dispatching from a subcommand.
+ # A problem occurs invoking the default command. This case occurs
+ # when arguments are passed and a default command is defined, and
+ # the first given_args does not match the default command.
+ # Thor use "help" by default so we skip that case.
+ # Note the call to retrieve_command_name. It's called with
+ # given_args.dup since that method calls args.shift. Then lookup
+ # the command normally. If the first item in given_args is not
+ # a command then use the default command. The given_args will be
+ # intact later since dup was used.
+ if config[:invoked_via_subcommand] && given_args.size >= 1 && default_command != "help" && given_args.first != default_command
+ meth ||= retrieve_command_name(given_args.dup)
+ command = all_commands[normalize_command_name(meth)]
+ command ||= all_commands[normalize_command_name(default_command)]
+ else
+ meth ||= retrieve_command_name(given_args)
+ command = all_commands[normalize_command_name(meth)]
+ end
- if task
- args, opts = Thor::Options.split(given_args)
- if stop_on_unknown_option?(task) && !args.empty?
- # given_args starts with a non-option, so we treat everything as
- # ordinary arguments
- args.concat opts
- opts.clear
- end
- else
- args, opts = given_args, nil
- task = Thor::DynamicTask.new(meth)
+ if command
+ args, opts = Thor::Options.split(given_args)
+ if stop_on_unknown_option?(command) && !args.empty?
+ # given_args starts with a non-option, so we treat everything as
+ # ordinary arguments
+ args.concat opts
+ opts.clear
end
-
- opts = given_opts || opts || []
- config.merge!(:current_task => task, :task_options => task.options)
-
- instance = new(args, opts, config)
- yield instance if block_given?
- args = instance.args
- trailing = args[Range.new(arguments.size, -1)]
- instance.invoke_task(task, trailing || [])
+ else
+ args, opts = given_args, nil
+ command = Thor::DynamicCommand.new(meth)
end
- # The banner for this class. You can customize it if you are invoking the
- # thor class by another ways which is not the Thor::Runner. It receives
- # the task that is going to be invoked and a boolean which indicates if
- # the namespace should be displayed as arguments.
- #
- def banner(task, namespace = nil, subcommand = false)
- "#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
- end
+ opts = given_opts || opts || []
+ config.merge!(:current_command => command, :command_options => command.options)
- def baseclass #:nodoc:
- Thor
- end
+ instance = new(args, opts, config)
+ yield instance if block_given?
+ args = instance.args
+ trailing = args[Range.new(arguments.size, -1)]
+ instance.invoke_command(command, trailing || [])
+ end
- def create_task(meth) #:nodoc:
- @long_desc ||= nil
- @usage ||= nil
- if @usage && @desc
- base_class = @hide ? Thor::HiddenTask : Thor::Task
- tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
- @usage, @desc, @long_desc, @method_options, @hide = nil
- true
- elsif self.all_tasks[meth] || meth == "method_missing"
- true
- else
- puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
- "Call desc if you want this method to be available as task or declare it inside a " <<
- "no_tasks{} block. Invoked from #{caller[1].inspect}."
- false
- end
- end
+ # The banner for this class. You can customize it if you are invoking the
+ # thor class by another ways which is not the Thor::Runner. It receives
+ # the command that is going to be invoked and a boolean which indicates if
+ # the namespace should be displayed as arguments.
+ #
+ def banner(command, namespace = nil, subcommand = false)
+ "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
+ end
- def initialize_added #:nodoc:
- class_options.merge!(method_options)
- @method_options = nil
- end
+ def baseclass #:nodoc:
+ Thor
+ end
- # Retrieve the task name from given args.
- def retrieve_task_name(args) #:nodoc:
- meth = args.first.to_s unless args.empty?
- if meth && (map[meth] || meth !~ /^\-/)
- args.shift
- else
- nil
- end
+ def create_command(meth) #:nodoc:
+ if @usage && @desc
+ base_class = @hide ? Thor::HiddenCommand : Thor::Command
+ commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
+ @usage, @desc, @long_desc, @method_options, @hide = nil
+ true
+ elsif self.all_commands[meth] || meth == "method_missing"
+ true
+ else
+ puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " <<
+ "Call desc if you want this method to be available as command or declare it inside a " <<
+ "no_commands{} block. Invoked from #{caller[1].inspect}."
+ false
end
+ end
+ alias create_task create_command
- # receives a (possibly nil) task name and returns a name that is in
- # the tasks hash. In addition to normalizing aliases, this logic
- # will determine if a shortened command is an unambiguous substring of
- # a task or alias.
- #
- # +normalize_task_name+ also converts names like +animal-prison+
- # into +animal_prison+.
- def normalize_task_name(meth) #:nodoc:
- return default_task.to_s.gsub('-', '_') unless meth
-
- possibilities = find_task_possibilities(meth)
- if possibilities.size > 1
- raise ArgumentError, "Ambiguous task #{meth} matches [#{possibilities.join(', ')}]"
- elsif possibilities.size < 1
- meth = meth || default_task
- elsif map[meth]
- meth = map[meth]
- else
- meth = possibilities.first
- end
+ def initialize_added #:nodoc:
+ class_options.merge!(method_options)
+ @method_options = nil
+ end
- meth.to_s.gsub('-','_') # treat foo-bar as foo_bar
+ # Retrieve the command name from given args.
+ def retrieve_command_name(args) #:nodoc:
+ meth = args.first.to_s unless args.empty?
+ if meth && (map[meth] || meth !~ /^\-/)
+ args.shift
+ else
+ nil
end
-
- # this is the logic that takes the task name passed in by the user
- # and determines whether it is an unambiguous substrings of a task or
- # alias name.
- def find_task_possibilities(meth)
- len = meth.to_s.length
- possibilities = all_tasks.merge(map).keys.select { |n| meth == n[0, len] }.sort
- unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
-
- if possibilities.include?(meth)
- [meth]
- elsif unique_possibilities.size == 1
- unique_possibilities
- else
- possibilities
- end
+ end
+ alias retrieve_task_name retrieve_command_name
+
+ # receives a (possibly nil) command name and returns a name that is in
+ # the commands hash. In addition to normalizing aliases, this logic
+ # will determine if a shortened command is an unambiguous substring of
+ # a command or alias.
+ #
+ # +normalize_command_name+ also converts names like +animal-prison+
+ # into +animal_prison+.
+ def normalize_command_name(meth) #:nodoc:
+ return default_command.to_s.gsub('-', '_') unless meth
+
+ possibilities = find_command_possibilities(meth)
+ if possibilities.size > 1
+ raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]"
+ elsif possibilities.size < 1
+ meth = meth || default_command
+ elsif map[meth]
+ meth = map[meth]
+ else
+ meth = possibilities.first
end
- def subcommand_help(cmd)
- desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
- class_eval <<-RUBY
- def help(task = nil, subcommand = true); super; end
- RUBY
+ meth.to_s.gsub('-','_') # treat foo-bar as foo_bar
+ end
+ alias normalize_task_name normalize_command_name
+
+ # this is the logic that takes the command name passed in by the user
+ # and determines whether it is an unambiguous substrings of a command or
+ # alias name.
+ def find_command_possibilities(meth)
+ len = meth.to_s.length
+ possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
+ unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
+
+ if possibilities.include?(meth)
+ [meth]
+ elsif unique_possibilities.size == 1
+ unique_possibilities
+ else
+ possibilities
end
+ end
+ alias find_task_possibilities find_command_possibilities
+
+ def subcommand_help(cmd)
+ desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
+ class_eval <<-RUBY
+ def help(command = nil, subcommand = true); super; end
+ RUBY
+ end
+ alias subtask_help subcommand_help
+
end
include Thor::Base
map HELP_MAPPINGS => :help
- desc "help [TASK]", "Describe available tasks or one specific task"
- def help(task = nil, subcommand = false)
- task ? self.class.task_help(shell, task) : self.class.help(shell, subcommand)
+ desc "help [COMMAND]", "Describe available commands or one specific command"
+ def help(command = nil, subcommand = false)
+ command ? self.class.command_help(shell, command) : self.class.help(shell, subcommand)
end
end
diff --git a/lib/bundler/vendor/thor/actions.rb b/lib/bundler/vendor/thor/actions.rb
index 8cc16e6896..7e574d8bde 100644
--- a/lib/bundler/vendor/thor/actions.rb
+++ b/lib/bundler/vendor/thor/actions.rb
@@ -1,6 +1,6 @@
require 'fileutils'
require 'uri'
-require 'thor/core_ext/file_binary_read'
+require 'thor/core_ext/io_binary_read'
require 'thor/actions/create_file'
require 'thor/actions/create_link'
require 'thor/actions/directory'
@@ -73,13 +73,13 @@ class Thor
#
def initialize(args=[], options={}, config={})
self.behavior = case config[:behavior].to_s
- when "force", "skip"
- _cleanup_options_and_set(options, config[:behavior])
- :invoke
- when "revoke"
- :revoke
- else
- :invoke
+ when "force", "skip"
+ _cleanup_options_and_set(options, config[:behavior])
+ :invoke
+ when "revoke"
+ :revoke
+ else
+ :invoke
end
super
@@ -227,7 +227,7 @@ class Thor
# ==== Parameters
# command<String>:: the command to be executed.
# config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
- # to append an executable to command executation.
+ # to append an executable to command execution.
#
# ==== Example
#
@@ -268,8 +268,8 @@ class Thor
# switches.
#
# ==== Parameters
- # task<String>:: the task to be invoked
- # args<Array>:: arguments to the task
+ # command<String>:: the command to be invoked
+ # args<Array>:: arguments to the command
# config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output.
# Other options are given as parameter to Thor.
#
@@ -282,13 +282,13 @@ class Thor
# thor :list, :all => true, :substring => 'rails'
# #=> thor list --all --substring=rails
#
- def thor(task, *args)
+ def thor(command, *args)
config = args.last.is_a?(Hash) ? args.pop : {}
verbose = config.key?(:verbose) ? config.delete(:verbose) : true
pretend = config.key?(:pretend) ? config.delete(:pretend) : false
capture = config.key?(:capture) ? config.delete(:capture) : false
- args.unshift task
+ args.unshift(command)
args.push Thor::Options.to_switches(config)
command = args.join(' ').strip
@@ -305,12 +305,12 @@ class Thor
def _cleanup_options_and_set(options, key) #:nodoc:
case options
- when Array
- %w(--force -f --skip -s).each { |i| options.delete(i) }
- options << "--#{key}"
- when Hash
- [:force, :skip, "force", "skip"].each { |i| options.delete(i) }
- options.merge!(key => true)
+ when Array
+ %w(--force -f --skip -s).each { |i| options.delete(i) }
+ options << "--#{key}"
+ when Hash
+ [:force, :skip, "force", "skip"].each { |i| options.delete(i) }
+ options.merge!(key => true)
end
end
diff --git a/lib/bundler/vendor/thor/actions/create_link.rb b/lib/bundler/vendor/thor/actions/create_link.rb
index 864a1e9923..fba3915155 100644
--- a/lib/bundler/vendor/thor/actions/create_link.rb
+++ b/lib/bundler/vendor/thor/actions/create_link.rb
@@ -52,6 +52,9 @@ class Thor
given_destination
end
+ def exists?
+ super || File.symlink?(destination)
+ end
end
end
end
diff --git a/lib/bundler/vendor/thor/actions/directory.rb b/lib/bundler/vendor/thor/actions/directory.rb
index 8e64513d6b..7f8fd97c9b 100644
--- a/lib/bundler/vendor/thor/actions/directory.rb
+++ b/lib/bundler/vendor/thor/actions/directory.rb
@@ -39,6 +39,7 @@ class Thor
# config<Hash>:: give :verbose => false to not log the status.
# If :recursive => false, does not look for paths recursively.
# If :mode => :preserve, preserve the file mode from the source.
+ # If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
#
# ==== Examples
#
@@ -74,26 +75,45 @@ class Thor
def execute!
lookup = Util.escape_globs(source)
lookup = config[:recursive] ? File.join(lookup, '**') : lookup
- lookup = File.join(lookup, '{*,.[a-z]*}')
+ lookup = file_level_lookup(lookup)
- Dir[lookup].sort.each do |file_source|
+ files(lookup).sort.each do |file_source|
next if File.directory?(file_source)
+ next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
file_destination.gsub!('/./', '/')
case file_source
- when /\.empty_directory$/
- dirname = File.dirname(file_destination).gsub(/\/\.$/, '')
- next if dirname == given_destination
- base.empty_directory(dirname, config)
- when /\.tt$/
- destination = base.template(file_source, file_destination[0..-4], config, &@block)
- else
- destination = base.copy_file(file_source, file_destination, config, &@block)
+ when /\.empty_directory$/
+ dirname = File.dirname(file_destination).gsub(/\/\.$/, '')
+ next if dirname == given_destination
+ base.empty_directory(dirname, config)
+ when /\.tt$/
+ destination = base.template(file_source, file_destination[0..-4], config, &@block)
+ else
+ destination = base.copy_file(file_source, file_destination, config, &@block)
end
end
end
+ if RUBY_VERSION < '2.0'
+ def file_level_lookup(previous_lookup)
+ File.join(previous_lookup, '{*,.[a-z]*}')
+ end
+
+ def files(lookup)
+ Dir[lookup]
+ end
+ else
+ def file_level_lookup(previous_lookup)
+ File.join(previous_lookup, '*')
+ end
+
+ def files(lookup)
+ Dir.glob(lookup, File::FNM_DOTMATCH)
+ end
+ end
+
end
end
end
diff --git a/lib/bundler/vendor/thor/actions/empty_directory.rb b/lib/bundler/vendor/thor/actions/empty_directory.rb
index 93d3e2a839..d9970abae4 100644
--- a/lib/bundler/vendor/thor/actions/empty_directory.rb
+++ b/lib/bundler/vendor/thor/actions/empty_directory.rb
@@ -97,28 +97,12 @@ class Thor
#
# user.rb
#
- # The method referenced by %-string SHOULD be public. Otherwise you
- # get the exception with the corresponding error message.
+ # The method referenced can be either public or private.
#
def convert_encoded_instructions(filename)
filename.gsub(/%(.*?)%/) do |initial_string|
- call_public_method($1.strip) or initial_string
- end
- end
-
- # Calls `base`'s public method `sym`.
- # Returns:: result of `base.sym` or `nil` if `sym` wasn't found in
- # `base`
- # Raises:: Thor::PrivateMethodEncodedError if `sym` references
- # a private method.
- def call_public_method(sym)
- if base.respond_to?(sym)
- base.send(sym)
- elsif base.respond_to?(sym, true)
- raise Thor::PrivateMethodEncodedError,
- "Method #{base.class}##{sym} should be public, not private"
- else
- nil
+ method = $1.strip
+ base.respond_to?(method, true) ? base.send(method) : initial_string
end
end
diff --git a/lib/bundler/vendor/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/actions/file_manipulation.rb
index d9d0d6f62a..a6effbec29 100644
--- a/lib/bundler/vendor/thor/actions/file_manipulation.rb
+++ b/lib/bundler/vendor/thor/actions/file_manipulation.rb
@@ -129,7 +129,7 @@ class Thor
#
# ==== Example
#
- # chmod "script/*", 0755
+ # chmod "script/server", 0755
#
def chmod(path, mode, config={})
return unless behavior == :invoke
@@ -251,7 +251,7 @@ class Thor
def uncomment_lines(path, flag, *args)
flag = flag.respond_to?(:source) ? flag.source : flag
- gsub_file(path, /^(\s*)#\s*(.*#{flag})/, '\1\2', *args)
+ gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
end
# Comment all lines matching a given regex. It will leave the space
@@ -293,8 +293,11 @@ class Thor
end
alias :remove_dir :remove_file
- attr_accessor :output_buffer
+ attr_accessor :output_buffer
+ private :output_buffer, :output_buffer=
+
private
+
def concat(string)
@output_buffer.concat(string)
end
diff --git a/lib/bundler/vendor/thor/base.rb b/lib/bundler/vendor/thor/base.rb
index aa1a6f65bf..272dae417b 100644
--- a/lib/bundler/vendor/thor/base.rb
+++ b/lib/bundler/vendor/thor/base.rb
@@ -1,10 +1,10 @@
+require 'thor/command'
require 'thor/core_ext/hash_with_indifferent_access'
require 'thor/core_ext/ordered_hash'
require 'thor/error'
-require 'thor/shell'
require 'thor/invocation'
require 'thor/parser'
-require 'thor/task'
+require 'thor/shell'
require 'thor/util'
class Thor
@@ -47,8 +47,8 @@ class Thor
# first two parameters.
if options.is_a?(Array)
- task_options = config.delete(:task_options) # hook for start
- parse_options = parse_options.merge(task_options) if task_options
+ command_options = config.delete(:command_options) # hook for start
+ parse_options = parse_options.merge(command_options) if command_options
array_options, hash_options = options, {}
else
# Handle the case where the class was explicitly instantiated
@@ -59,9 +59,10 @@ class Thor
# Let Thor::Options parse the options first, so it can remove
# declared options from the array. This will leave us with
# a list of arguments that weren't declared.
- stop_on_unknown = self.class.stop_on_unknown_option? config[:current_task]
+ stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown)
self.options = opts.parse(array_options)
+ self.options = config[:class_options].merge(self.options) if config[:class_options]
# If unknown options are disallowed, make sure that none of the
# remaining arguments looks like an option.
@@ -119,15 +120,15 @@ class Thor
module ClassMethods
def attr_reader(*) #:nodoc:
- no_tasks { super }
+ no_commands { super }
end
def attr_writer(*) #:nodoc:
- no_tasks { super }
+ no_commands { super }
end
def attr_accessor(*) #:nodoc:
- no_tasks { super }
+ no_commands { super }
end
# If you want to raise an error for unknown options, call check_unknown_options!
@@ -146,8 +147,8 @@ class Thor
# If true, option parsing is suspended as soon as an unknown option or a
# regular argument is encountered. All remaining arguments are passed to
- # the task as regular arguments.
- def stop_on_unknown_option?(task_name) #:nodoc:
+ # the command as regular arguments.
+ def stop_on_unknown_option?(command_name) #:nodoc:
false
end
@@ -172,11 +173,11 @@ class Thor
# is how they are parsed from the command line, arguments are retrieved
# from position:
#
- # thor task NAME
+ # thor command NAME
#
# Instead of:
#
- # thor task --name=NAME
+ # thor command --name=NAME
#
# Besides, arguments are used inside your code as an accessor (self.argument),
# while options are all kept in a hash (self.options).
@@ -203,7 +204,7 @@ class Thor
#
def argument(name, options={})
is_thor_reserved_word?(name, :argument)
- no_tasks { attr_accessor name }
+ no_commands { attr_accessor name }
required = if options.key?(:optional)
!options[:optional]
@@ -307,88 +308,92 @@ class Thor
end
# Defines the group. This is used when thor list is invoked so you can specify
- # that only tasks from a pre-defined group will be shown. Defaults to standard.
+ # that only commands from a pre-defined group will be shown. Defaults to standard.
#
# ==== Parameters
# name<String|Symbol>
#
def group(name=nil)
- case name
- when nil
- @group ||= from_superclass(:group, 'standard')
- else
- @group = name.to_s
+ @group = case name
+ when nil
+ @group || from_superclass(:group, 'standard')
+ else
+ name.to_s
end
end
- # Returns the tasks for this Thor class.
+ # Returns the commands for this Thor class.
#
# ==== Returns
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
+ # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
# objects as values.
#
- def tasks
- @tasks ||= Thor::CoreExt::OrderedHash.new
+ def commands
+ @commands ||= Thor::CoreExt::OrderedHash.new
end
+ alias tasks commands
- # Returns the tasks for this Thor class and all subclasses.
+ # Returns the commands for this Thor class and all subclasses.
#
# ==== Returns
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
+ # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
# objects as values.
#
- def all_tasks
- @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new)
- @all_tasks.merge(tasks)
+ def all_commands
+ @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
+ @all_commands.merge(commands)
end
+ alias all_tasks all_commands
- # Removes a given task from this Thor class. This is usually done if you
+ # Removes a given command from this Thor class. This is usually done if you
# are inheriting from another class and don't want it to be available
# anymore.
#
- # By default it only remove the mapping to the task. But you can supply
+ # By default it only remove the mapping to the command. But you can supply
# :undefine => true to undefine the method from the class as well.
#
# ==== Parameters
- # name<Symbol|String>:: The name of the task to be removed
- # options<Hash>:: You can give :undefine => true if you want tasks the method
+ # name<Symbol|String>:: The name of the command to be removed
+ # options<Hash>:: You can give :undefine => true if you want commands the method
# to be undefined from the class as well.
#
- def remove_task(*names)
+ def remove_command(*names)
options = names.last.is_a?(Hash) ? names.pop : {}
names.each do |name|
- tasks.delete(name.to_s)
- all_tasks.delete(name.to_s)
+ commands.delete(name.to_s)
+ all_commands.delete(name.to_s)
undef_method name if options[:undefine]
end
end
+ alias remove_task remove_command
- # All methods defined inside the given block are not added as tasks.
+ # All methods defined inside the given block are not added as commands.
#
# So you can do:
#
# class MyScript < Thor
- # no_tasks do
- # def this_is_not_a_task
+ # no_commands do
+ # def this_is_not_a_command
# end
# end
# end
#
- # You can also add the method and remove it from the task list:
+ # You can also add the method and remove it from the command list:
#
# class MyScript < Thor
- # def this_is_not_a_task
+ # def this_is_not_a_command
# end
- # remove_task :this_is_not_a_task
+ # remove_command :this_is_not_a_command
# end
#
- def no_tasks
- @no_tasks = true
+ def no_commands
+ @no_commands = true
yield
ensure
- @no_tasks = false
+ @no_commands = false
end
+ alias no_tasks no_commands
# Sets the namespace for the Thor or Thor::Group class. By default the
# namespace is retrieved from the class name. If your Thor class is named
@@ -400,7 +405,7 @@ class Thor
#
# namespace :my_scripts
#
- # You change how your tasks are invoked:
+ # You change how your commands are invoked:
#
# thor my_scripts -h
#
@@ -408,26 +413,25 @@ class Thor
#
# namespace :default
#
- # Your tasks can be invoked with a shortcut. Instead of:
+ # Your commands can be invoked with a shortcut. Instead of:
#
- # thor :my_task
+ # thor :my_command
#
def namespace(name=nil)
- case name
- when nil
- @namespace ||= Thor::Util.namespace_from_thor_class(self)
- else
+ if name
@namespace = name.to_s
+ else
+ @namespace ||= Thor::Util.namespace_from_thor_class(self)
end
end
- # Parses the task and options from the given args, instantiate the class
- # and invoke the task. This method is used when the arguments must be parsed
+ # Parses the command and options from the given args, instantiate the class
+ # and invoke the command. This method is used when the arguments must be parsed
# from an array. If you are inside Ruby and want to use a Thor class, you
# can simply initialize it:
#
# script = MyScript.new(args, options, config)
- # script.invoke(:task, first_arg, second_arg, third_arg)
+ # script.invoke(:command, first_arg, second_arg, third_arg)
#
def start(given_args=ARGV, config={})
config[:shell] ||= Thor::Base.shell.new
@@ -436,48 +440,44 @@ class Thor
ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
exit(1) if exit_on_failure?
rescue Errno::EPIPE
- # This happens if a thor task is piped to something like `head`,
+ # This happens if a thor command is piped to something like `head`,
# which closes the pipe when it's done reading. This will also
# mean that if the pipe is closed, further unnecessary
# computation will not occur.
exit(0)
end
- # Allows to use private methods from parent in child classes as tasks.
+ # Allows to use private methods from parent in child classes as commands.
#
# ==== Parameters
- # names<Array>:: Method names to be used as tasks
+ # names<Array>:: Method names to be used as commands
#
# ==== Examples
#
- # public_task :foo
- # public_task :foo, :bar, :baz
+ # public_command :foo
+ # public_command :foo, :bar, :baz
#
- def public_task(*names)
+ def public_command(*names)
names.each do |name|
class_eval "def #{name}(*); super end"
end
end
+ alias public_task public_command
- def handle_no_task_error(task, has_namespace = $thor_runner) #:nodoc:
+ def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
if has_namespace
- raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
+ raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace."
else
- raise UndefinedTaskError, "Could not find task #{task.inspect}."
+ raise UndefinedCommandError, "Could not find command #{command.inspect}."
end
end
+ alias handle_no_task_error handle_no_command_error
- def handle_argument_error(task, error, arity=nil) #:nodoc:
- msg = "#{basename} #{task.name}"
- if arity
- required = arity < 0 ? (-1 - arity) : arity
- msg << " requires at least #{required} argument"
- msg << "s" if required > 1
- else
- msg = "call #{msg} as"
- end
-
- msg << ": #{self.banner(task).inspect}."
+ def handle_argument_error(command, error, args, arity) #:nodoc:
+ msg = "ERROR: \"#{basename} #{command.name}\" was called with "
+ msg << 'no arguments' if args.empty?
+ msg << 'arguments ' << args.inspect if !args.empty?
+ msg << "\nUsage: #{self.banner(command).inspect}"
raise InvocationError, msg
end
@@ -555,28 +555,29 @@ class Thor
end
end
- # Finds a task with the given name. If the task belongs to the current
+ # Finds a command with the given name. If the command belongs to the current
# class, just return it, otherwise dup it and add the fresh copy to the
- # current task hash.
- def find_and_refresh_task(name) #:nodoc:
- task = if task = tasks[name.to_s]
- task
- elsif task = all_tasks[name.to_s]
- tasks[name.to_s] = task.clone
+ # current command hash.
+ def find_and_refresh_command(name) #:nodoc:
+ command = if command = commands[name.to_s]
+ command
+ elsif command = all_commands[name.to_s]
+ commands[name.to_s] = command.clone
else
- raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found."
+ raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
end
end
+ alias find_and_refresh_task find_and_refresh_command
# Everytime someone inherits from a Thor class, register the klass
# and file into baseclass.
def inherited(klass)
Thor::Base.register_klass_file(klass)
- klass.instance_variable_set(:@no_tasks, false)
+ klass.instance_variable_set(:@no_commands, false)
end
# Fire this callback whenever a method is added. Added methods are
- # tracked as tasks by invoking the create_task method.
+ # tracked as commands by invoking the create_command method.
def method_added(meth)
meth = meth.to_s
@@ -586,12 +587,11 @@ class Thor
end
# Return if it's not a public instance method
- return unless public_instance_methods.include?(meth) ||
- public_instance_methods.include?(meth.to_sym)
+ return unless public_method_defined?(meth.to_sym)
- return if (defined?(@no_tasks) && @no_tasks) || !create_task(meth)
+ return if @no_commands || !create_command(meth)
- is_thor_reserved_word?(meth, :task)
+ is_thor_reserved_word?(meth, :command)
Thor::Base.register_klass_file(self)
end
@@ -603,13 +603,16 @@ class Thor
else
value = superclass.send(method)
- if value
- if value.is_a?(TrueClass) || value.is_a?(Symbol)
- value
- else
- value.dup
- end
+ # Ruby implements `dup` on Object, but raises a `TypeError`
+ # if the method is called on immediates. As a result, we
+ # don't have a good way to check whether dup will succeed
+ # without calling it and rescuing the TypeError.
+ begin
+ value.dup
+ rescue TypeError
+ value
end
+
end
end
@@ -630,10 +633,11 @@ class Thor
def baseclass #:nodoc:
end
- # SIGNATURE: Creates a new task if valid_task? is true. This method is
+ # SIGNATURE: Creates a new command if valid_command? is true. This method is
# called when a new method is added to the class.
- def create_task(meth) #:nodoc:
+ def create_command(meth) #:nodoc:
end
+ alias create_task create_command
# SIGNATURE: Defines behavior when the initialize method is added to the
# class.
@@ -641,7 +645,7 @@ class Thor
end
# SIGNATURE: The hook invoked by start.
- def dispatch(task, given_args, given_opts, config) #:nodoc:
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
raise NotImplementedError
end
diff --git a/lib/bundler/vendor/thor/task.rb b/lib/bundler/vendor/thor/command.rb
index 92fb557f43..e56bd44c0b 100644
--- a/lib/bundler/vendor/thor/task.rb
+++ b/lib/bundler/vendor/thor/command.rb
@@ -1,5 +1,5 @@
class Thor
- class Task < Struct.new(:name, :description, :long_description, :usage, :options)
+ class Command < Struct.new(:name, :description, :long_description, :usage, :options)
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
def initialize(name, description, long_description, usage, options=nil)
@@ -15,27 +15,27 @@ class Thor
false
end
- # By default, a task invokes a method in the thor class. You can change this
- # implementation to create custom tasks.
+ # By default, a command invokes a method in the thor class. You can change this
+ # implementation to create custom commands.
def run(instance, args=[])
arity = nil
if private_method?(instance)
- instance.class.handle_no_task_error(name)
+ instance.class.handle_no_command_error(name)
elsif public_method?(instance)
arity = instance.method(name).arity
instance.__send__(name, *args)
elsif local_method?(instance, :method_missing)
instance.__send__(:method_missing, name.to_sym, *args)
else
- instance.class.handle_no_task_error(name)
+ instance.class.handle_no_command_error(name)
end
rescue ArgumentError => e
handle_argument_error?(instance, e, caller) ?
- instance.class.handle_argument_error(self, e, arity) : (raise e)
+ instance.class.handle_argument_error(self, e, args, arity) : (raise e)
rescue NoMethodError => e
handle_no_method_error?(instance, e, caller) ?
- instance.class.handle_no_task_error(name) : (raise e)
+ instance.class.handle_no_command_error(name) : (raise e)
end
# Returns the formatted usage by injecting given required arguments
@@ -107,26 +107,30 @@ class Thor
error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
end
end
+ Task = Command
- # A task that is hidden in help messages but still invocable.
- class HiddenTask < Task
+ # A command that is hidden in help messages but still invocable.
+ class HiddenCommand < Command
def hidden?
true
end
end
+ HiddenTask = HiddenCommand
- # A dynamic task that handles method missing scenarios.
- class DynamicTask < Task
+ # A dynamic command that handles method missing scenarios.
+ class DynamicCommand < Command
def initialize(name, options=nil)
- super(name.to_s, "A dynamically-generated task", name.to_s, name.to_s, options)
+ super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
end
def run(instance, args=[])
if (instance.methods & [name.to_s, name.to_sym]).empty?
super
else
- instance.class.handle_no_task_error(name)
+ instance.class.handle_no_command_error(name)
end
end
end
+ DynamicTask = DynamicCommand
+
end
diff --git a/lib/bundler/vendor/thor/core_ext/dir_escape.rb b/lib/bundler/vendor/thor/core_ext/dir_escape.rb
deleted file mode 100644
index e69de29bb2..0000000000
--- a/lib/bundler/vendor/thor/core_ext/dir_escape.rb
+++ /dev/null
diff --git a/lib/bundler/vendor/thor/core_ext/file_binary_read.rb b/lib/bundler/vendor/thor/core_ext/file_binary_read.rb
deleted file mode 100644
index d6af7e44b0..0000000000
--- a/lib/bundler/vendor/thor/core_ext/file_binary_read.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class File #:nodoc:
-
- unless File.respond_to?(:binread)
- def self.binread(file)
- File.open(file, 'rb') { |f| f.read }
- end
- end
-
-end
diff --git a/lib/bundler/vendor/thor/core_ext/io_binary_read.rb b/lib/bundler/vendor/thor/core_ext/io_binary_read.rb
new file mode 100644
index 0000000000..a824f1b2b1
--- /dev/null
+++ b/lib/bundler/vendor/thor/core_ext/io_binary_read.rb
@@ -0,0 +1,12 @@
+class IO #:nodoc:
+ class << self
+
+ def binread(file, *args)
+ raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
+ File.open(file, 'rb') do |f|
+ f.read(*args)
+ end
+ end unless method_defined? :binread
+
+ end
+end
diff --git a/lib/bundler/vendor/thor/error.rb b/lib/bundler/vendor/thor/error.rb
index 532db462b8..3174c57eac 100644
--- a/lib/bundler/vendor/thor/error.rb
+++ b/lib/bundler/vendor/thor/error.rb
@@ -5,17 +5,19 @@ class Thor
# Errors that are caused by the developer, like declaring a method which
# overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
# ensure that developer errors are shown with full backtrace.
- #
class Error < StandardError
end
- # Raised when a task was not found.
- #
- class UndefinedTaskError < Error
+ # Raised when a command was not found.
+ class UndefinedCommandError < Error
end
+ UndefinedTaskError = UndefinedCommandError
- # Raised when a task was found, but not invoked properly.
- #
+ class AmbiguousCommandError < Error
+ end
+ AmbiguousTaskError = AmbiguousCommandError
+
+ # Raised when a command was found, but not invoked properly.
class InvocationError < Error
end
@@ -27,9 +29,4 @@ class Thor
class MalformattedArgumentError < InvocationError
end
-
- # Raised when a user tries to call a private method encoded in templated filename.
- #
- class PrivateMethodEncodedError < Error
- end
end
diff --git a/lib/bundler/vendor/thor/group.rb b/lib/bundler/vendor/thor/group.rb
index 874ac47a1e..2aaee73778 100644
--- a/lib/bundler/vendor/thor/group.rb
+++ b/lib/bundler/vendor/thor/group.rb
@@ -1,9 +1,9 @@
require 'thor/base'
# Thor has a special class called Thor::Group. The main difference to Thor class
-# is that it invokes all tasks at once. It also include some methods that allows
+# is that it invokes all commands at once. It also include some methods that allows
# invocations to be done at the class method, which are not available to Thor
-# tasks.
+# commands.
class Thor::Group
class << self
# The description for this Thor::Group. If none is provided, but a source root
@@ -14,11 +14,11 @@ class Thor::Group
# description<String>:: The description for this Thor::Group.
#
def desc(description=nil)
- case description
- when nil
- @desc ||= from_superclass(:desc, nil)
- else
- @desc = description
+ @desc = case description
+ when nil
+ @desc || from_superclass(:desc, nil)
+ else
+ description
end
end
@@ -48,7 +48,7 @@ class Thor::Group
end
# Invoke the given namespace or class given. It adds an instance
- # method that will invoke the klass and task. You can give a block to
+ # method that will invoke the klass and command. You can give a block to
# configure how it will be invoked.
#
# The namespace/class given will have its options showed on the help
@@ -64,12 +64,12 @@ class Thor::Group
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_#{name.to_s.gsub(/\W/, '_')}
- klass, task = self.class.prepare_for_invocation(nil, #{name.inspect})
+ klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
if klass
say_status :invoke, #{name.inspect}, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
- _invoke_for_class_method klass, task, &block
+ _invoke_for_class_method klass, command, &block
else
say_status :error, %(#{name.inspect} [not found]), :red
end
@@ -100,7 +100,7 @@ class Thor::Group
# In some cases you want to customize how a specified hook is going to be
# invoked. You can do that by overwriting the class method
# prepare_for_invocation. The class method must necessarily return a klass
- # and an optional task.
+ # and an optional command.
#
# ==== Custom invocations
#
@@ -127,12 +127,12 @@ class Thor::Group
value = options[#{name.inspect}]
value = #{name.inspect} if TrueClass === value
- klass, task = self.class.prepare_for_invocation(#{name.inspect}, value)
+ klass, command = self.class.prepare_for_invocation(#{name.inspect}, value)
if klass
say_status :invoke, value, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
- _invoke_for_class_method klass, task, &block
+ _invoke_for_class_method klass, command, &block
else
say_status :error, %(\#{value} [not found]), :red
end
@@ -149,7 +149,7 @@ class Thor::Group
#
def remove_invocation(*names)
names.each do |name|
- remove_task(name)
+ remove_command(name)
remove_class_option(name)
invocations.delete(name)
invocation_blocks.delete(name)
@@ -196,30 +196,26 @@ class Thor::Group
end
end
- # Returns tasks ready to be printed.
- def printable_tasks(*)
+ # Returns commands ready to be printed.
+ def printable_commands(*)
item = []
item << banner
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
[item]
end
+ alias printable_tasks printable_commands
- def handle_argument_error(task, error, arity=nil) #:nodoc:
- if arity > 0
- msg = "#{basename} #{task.name} takes #{arity} argument"
- msg << "s" if arity > 1
- msg << ", but it should not."
- else
- msg = "You should not pass arguments to #{basename} #{task.name}."
- end
-
+ def handle_argument_error(command, error, args, arity) #:nodoc:
+ msg = "#{basename} #{command.name} takes #{arity} argument"
+ msg << "s" if arity > 1
+ msg << ", but it should not."
raise error, msg
end
protected
# The method responsible for dispatching given the args.
- def dispatch(task, given_args, given_opts, config) #:nodoc:
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
if Thor::HELP_MAPPINGS.include?(given_args.first)
help(config[:shell])
return
@@ -230,10 +226,9 @@ class Thor::Group
instance = new(args, opts, config)
yield instance if block_given?
- args = instance.args
- if task
- instance.invoke_task(all_tasks[task])
+ if command
+ instance.invoke_command(all_commands[command])
else
instance.invoke_all
end
@@ -242,22 +237,24 @@ class Thor::Group
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Thor::Runner.
def banner
- "#{basename} #{self_task.formatted_usage(self, false)}"
+ "#{basename} #{self_command.formatted_usage(self, false)}"
end
- # Represents the whole class as a task.
- def self_task #:nodoc:
- Thor::DynamicTask.new(self.namespace, class_options)
+ # Represents the whole class as a command.
+ def self_command #:nodoc:
+ Thor::DynamicCommand.new(self.namespace, class_options)
end
+ alias self_task self_command
def baseclass #:nodoc:
Thor::Group
end
- def create_task(meth) #:nodoc:
- tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, nil)
+ def create_command(meth) #:nodoc:
+ commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil)
true
end
+ alias create_task create_command
end
include Thor::Base
@@ -266,19 +263,19 @@ class Thor::Group
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
- def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
+ def _invoke_for_class_method(klass, command=nil, *args, &block) #:nodoc:
with_padding do
if block
case block.arity
when 3
- block.call(self, klass, task)
+ block.call(self, klass, command)
when 2
block.call(self, klass)
when 1
instance_exec(klass, &block)
end
else
- invoke klass, task, *args
+ invoke klass, command, *args
end
end
end
diff --git a/lib/bundler/vendor/thor/invocation.rb b/lib/bundler/vendor/thor/invocation.rb
index 71db7c81de..a9adeb560c 100644
--- a/lib/bundler/vendor/thor/invocation.rb
+++ b/lib/bundler/vendor/thor/invocation.rb
@@ -6,12 +6,12 @@ class Thor
module ClassMethods
# This method is responsible for receiving a name and find the proper
- # class and task for it. The key is an optional parameter which is
+ # class and command for it. The key is an optional parameter which is
# available only in class methods invocations (i.e. in Thor::Group).
def prepare_for_invocation(key, name) #:nodoc:
case name
when Symbol, String
- Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
+ Thor::Util.find_class_and_command_by_namespace(name.to_s, !key)
else
name
end
@@ -25,15 +25,15 @@ class Thor
super
end
- # Receives a name and invokes it. The name can be a string (either "task" or
- # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
- # cannot be guessed by name, it can also be supplied as second argument.
+ # Receives a name and invokes it. The name can be a string (either "command" or
+ # "namespace:command"), a Thor::Command, a Class or a Thor instance. If the
+ # command cannot be guessed by name, it can also be supplied as second argument.
#
# You can also supply the arguments, options and configuration values for
- # the task to be invoked, if none is given, the same values used to
+ # the command to be invoked, if none is given, the same values used to
# initialize the invoker are used to initialize the invoked.
#
- # When no name is given, it will invoke the default task of the current class.
+ # When no name is given, it will invoke the default command of the current class.
#
# ==== Examples
#
@@ -54,16 +54,16 @@ class Thor
# end
# end
#
- # You can notice that the method "foo" above invokes two tasks: "bar",
+ # You can notice that the method "foo" above invokes two commands: "bar",
# which belongs to the same class and "hello" which belongs to the class B.
#
- # By using an invocation system you ensure that a task is invoked only once.
+ # By using an invocation system you ensure that a command is invoked only once.
# In the example above, invoking "foo" will invoke "b:hello" just once, even
# if it's invoked later by "bar" method.
#
# When class A invokes class B, all arguments used on A initialization are
# supplied to B. This allows lazy parse of options. Let's suppose you have
- # some rspec tasks:
+ # some rspec commands:
#
# class Rspec < Thor::Group
# class_option :mock_framework, :type => :string, :default => :rr
@@ -100,30 +100,31 @@ class Thor
end
args.unshift(nil) if Array === args.first || NilClass === args.first
- task, args, opts, config = args
+ command, args, opts, config = args
- klass, task = _retrieve_class_and_task(name, task)
+ klass, command = _retrieve_class_and_command(name, command)
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
args, opts, config = _parse_initialization_options(args, opts, config)
- klass.send(:dispatch, task, args, opts, config) do |instance|
+ klass.send(:dispatch, command, args, opts, config) do |instance|
instance.parent_options = options
end
end
- # Invoke the given task if the given args.
- def invoke_task(task, *args) #:nodoc:
+ # Invoke the given command if the given args.
+ def invoke_command(command, *args) #:nodoc:
current = @_invocations[self.class]
- unless current.include?(task.name)
- current << task.name
- task.run(self, *args)
+ unless current.include?(command.name)
+ current << command.name
+ command.run(self, *args)
end
end
+ alias invoke_task invoke_command
- # Invoke all tasks for the current instance.
+ # Invoke all commands for the current instance.
def invoke_all #:nodoc:
- self.class.all_tasks.map { |_, task| invoke_task(task) }
+ self.class.all_commands.map { |_, command| invoke_command(command) }
end
# Invokes using shell padding.
@@ -138,21 +139,22 @@ class Thor
{ :invocations => @_invocations }
end
- # This method simply retrieves the class and task to be invoked.
- # If the name is nil or the given name is a task in the current class,
+ # This method simply retrieves the class and command to be invoked.
+ # If the name is nil or the given name is a command in the current class,
# use the given name and return self as class. Otherwise, call
# prepare_for_invocation in the current class.
- def _retrieve_class_and_task(name, sent_task=nil) #:nodoc:
+ def _retrieve_class_and_command(name, sent_command=nil) #:nodoc:
case
when name.nil?
[self.class, nil]
- when self.class.all_tasks[name.to_s]
+ when self.class.all_commands[name.to_s]
[self.class, name.to_s]
else
- klass, task = self.class.prepare_for_invocation(nil, name)
- [klass, task || sent_task]
+ klass, command = self.class.prepare_for_invocation(nil, name)
+ [klass, command || sent_command]
end
end
+ alias _retrieve_class_and_task _retrieve_class_and_command
# Initialize klass using values stored in the @_initializer.
def _parse_initialization_options(args, opts, config) #:nodoc:
diff --git a/lib/bundler/vendor/thor/parser/options.rb b/lib/bundler/vendor/thor/parser/options.rb
index 5e16c52318..9542e816c5 100644
--- a/lib/bundler/vendor/thor/parser/options.rb
+++ b/lib/bundler/vendor/thor/parser/options.rb
@@ -11,16 +11,16 @@ class Thor
def self.to_switches(options)
options.map do |key, value|
case value
- when true
- "--#{key}"
- when Array
- "--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
- when Hash
- "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
- when nil, false
- ""
- else
- "--#{key} #{value.inspect}"
+ when true
+ "--#{key}"
+ when Array
+ "--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
+ when Hash
+ "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
+ when nil, false
+ ""
+ else
+ "--#{key} #{value.inspect}"
end
end.join(" ")
end
@@ -46,7 +46,8 @@ class Thor
@switches[option.switch_name] = option
option.aliases.each do |short|
- @shorts[short.to_s] ||= option.switch_name
+ name = short.to_s.sub(/^(?!\-)/, '-')
+ @shorts[name] ||= option.switch_name
end
end
end
@@ -79,20 +80,21 @@ class Thor
if is_switch
case shifted
- when SHORT_SQ_RE
- unshift($1.split('').map { |f| "-#{f}" })
- next
- when EQ_RE, SHORT_NUM
- unshift($2)
- switch = $1
- when LONG_RE, SHORT_RE
- switch = $1
+ when SHORT_SQ_RE
+ unshift($1.split('').map { |f| "-#{f}" })
+ next
+ when EQ_RE, SHORT_NUM
+ unshift($2)
+ switch = $1
+ when LONG_RE, SHORT_RE
+ switch = $1
end
switch = normalize_switch(switch)
option = switch_option(switch)
@assigns[option.human_name] = parse_peek(switch, option)
elsif @stop_on_unknown
+ @parsing_options = false
@extra << shifted
@extra << shift while peek
break
diff --git a/lib/bundler/vendor/thor/rake_compat.rb b/lib/bundler/vendor/thor/rake_compat.rb
index c86e840578..fcb3b24d9d 100644
--- a/lib/bundler/vendor/thor/rake_compat.rb
+++ b/lib/bundler/vendor/thor/rake_compat.rb
@@ -6,12 +6,13 @@ class Thor
# rake package tasks. For example, to use rspec rake tasks, one can do:
#
# require 'thor/rake_compat'
+ # require 'rspec/core/rake_task'
#
# class Default < Thor
# include Thor::RakeCompat
#
- # Spec::Rake::SpecTask.new(:spec) do |t|
- # t.spec_opts = ['--options', "spec/spec.opts"]
+ # RSpec::Core::RakeTask.new(:spec) do |t|
+ # t.spec_opts = ['--options', "./.rspec"]
# t.spec_files = FileList['spec/**/*_spec.rb']
# end
# end
diff --git a/lib/bundler/vendor/thor/runner.rb b/lib/bundler/vendor/thor/runner.rb
index e32ae9ca0f..bef3d57bc7 100644
--- a/lib/bundler/vendor/thor/runner.rb
+++ b/lib/bundler/vendor/thor/runner.rb
@@ -1,6 +1,6 @@
require 'thor'
require 'thor/group'
-require 'thor/core_ext/file_binary_read'
+require 'thor/core_ext/io_binary_read'
require 'fileutils'
require 'open-uri'
@@ -16,33 +16,33 @@ class Thor::Runner < Thor #:nodoc:
def help(meth = nil)
if meth && !self.respond_to?(meth)
initialize_thorfiles(meth)
- klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
- self.class.handle_no_task_error(task, false) if klass.nil?
- klass.start(["-h", task].compact, :shell => self.shell)
+ klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
+ self.class.handle_no_command_error(command, false) if klass.nil?
+ klass.start(["-h", command].compact, :shell => self.shell)
else
super
end
end
- # If a task is not found on Thor::Runner, method missing is invoked and
- # Thor::Runner is then responsible for finding the task in all classes.
+ # If a command is not found on Thor::Runner, method missing is invoked and
+ # Thor::Runner is then responsible for finding the command in all classes.
#
def method_missing(meth, *args)
meth = meth.to_s
initialize_thorfiles(meth)
- klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
- self.class.handle_no_task_error(task, false) if klass.nil?
- args.unshift(task) if task
+ klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
+ self.class.handle_no_command_error(command, false) if klass.nil?
+ args.unshift(command) if command
klass.start(args, :shell => self.shell)
end
- desc "install NAME", "Install an optionally named Thor file into your system tasks"
+ desc "install NAME", "Install an optionally named Thor file into your system commands"
method_options :as => :string, :relative => :boolean, :force => :boolean
def install(name)
initialize_thorfiles
# If a directory name is provided as the argument, look for a 'main.thor'
- # task in said directory.
+ # command in said directory.
begin
if File.directory?(File.expand_path(name))
base, package = File.join(name, "main.thor"), :directory
@@ -143,14 +143,14 @@ class Thor::Runner < Thor #:nodoc:
end
end
- desc "installed", "List the installed Thor modules and tasks"
+ desc "installed", "List the installed Thor modules and commands"
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
display_klasses(true, options["internal"])
end
- desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
+ desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
def list(search="")
initialize_thorfiles
@@ -168,8 +168,8 @@ class Thor::Runner < Thor #:nodoc:
private
- def self.banner(task, all = false, subcommand = false)
- "thor " + task.formatted_usage(self, all, subcommand)
+ def self.banner(command, all = false, subcommand = false)
+ "thor " + command.formatted_usage(self, all, subcommand)
end
def thor_root
@@ -206,7 +206,7 @@ class Thor::Runner < Thor #:nodoc:
# in the thor_root instead of loading them all.
#
# By default, it also traverses the current path until find Thor files, as
- # described in thorfiles. This look up can be skipped by suppliying
+ # described in thorfiles. This look up can be skipped by supplying
# skip_lookup true.
#
def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
@@ -276,25 +276,25 @@ class Thor::Runner < Thor #:nodoc:
def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
- raise Error, "No Thor tasks available" if klasses.empty?
+ raise Error, "No Thor commands available" if klasses.empty?
show_modules if with_modules && !thor_yaml.empty?
list = Hash.new { |h,k| h[k] = [] }
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
# Get classes which inherit from Thor
- (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) }
# Get classes which inherit from Thor::Base
- groups.map! { |k| k.printable_tasks(false).first }
+ groups.map! { |k| k.printable_commands(false).first }
list["root"] = groups
# Order namespaces with default coming first
list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
- list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
+ list.each { |n, commands| display_commands(n, commands) unless commands.empty? }
end
- def display_tasks(namespace, list) #:nodoc:
+ def display_commands(namespace, list) #:nodoc:
list.sort!{ |a,b| a[0] <=> b[0] }
say shell.set_color(namespace, :blue, true)
@@ -303,6 +303,7 @@ class Thor::Runner < Thor #:nodoc:
print_table(list, :truncate => true)
say
end
+ alias display_tasks display_commands
def show_modules #:nodoc:
info = []
diff --git a/lib/bundler/vendor/thor/shell/basic.rb b/lib/bundler/vendor/thor/shell/basic.rb
index 9f8ad24383..d9119caeff 100644
--- a/lib/bundler/vendor/thor/shell/basic.rb
+++ b/lib/bundler/vendor/thor/shell/basic.rb
@@ -47,8 +47,13 @@ class Thor
#
def ask(statement, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
+ color = args.first
- options[:limited_to] ? ask_filtered(statement, options[:limited_to], *args) : ask_simply(statement, *args)
+ if options[:limited_to]
+ ask_filtered(statement, color, options)
+ else
+ ask_simply(statement, color, options)
+ end
end
# Say (print) something to the user. If the sentence ends with a whitespace
@@ -61,7 +66,7 @@ class Thor
def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)\Z/))
message = message.to_s
- message = set_color(message, *color) if color
+ message = set_color(message, *color) if color && can_display_colors?
spaces = " " * padding
@@ -226,20 +231,20 @@ class Thor
answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
case answer
- when is?(:yes), is?(:force), ""
- return true
- when is?(:no), is?(:skip)
- return false
- when is?(:always)
- return @always_force = true
- when is?(:quit)
- say 'Aborting...'
- raise SystemExit
- when is?(:diff)
- show_diff(destination, yield) if block_given?
- say 'Retrying...'
- else
- say file_collision_help
+ when is?(:yes), is?(:force), ""
+ return true
+ when is?(:no), is?(:skip)
+ return false
+ when is?(:always)
+ return @always_force = true
+ when is?(:quit)
+ say 'Aborting...'
+ raise SystemExit
+ when is?(:diff)
+ show_diff(destination, yield) if block_given?
+ say 'Retrying...'
+ else
+ say file_collision_help
end
end
end
@@ -275,6 +280,10 @@ class Thor
protected
+ def can_display_colors?
+ false
+ end
+
def lookup_color(color)
return color unless color.is_a?(Symbol)
self.class.const_get(color.to_s.upcase)
@@ -368,17 +377,30 @@ HELP
end
end
- def ask_simply(statement, color=nil)
- say("#{statement} ", color)
- stdin.gets.tap{|text| text.strip! if text}
+ def ask_simply(statement, color, options)
+ default = options[:default]
+ message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
+ say(message, color)
+ result = stdin.gets
+
+ return unless result
+
+ result.strip!
+
+ if default && result == ""
+ default
+ else
+ result
+ end
end
- def ask_filtered(statement, answer_set, *args)
+ def ask_filtered(statement, color, options)
+ answer_set = options[:limited_to]
correct_answer = nil
until correct_answer
- answer = ask_simply("#{statement} #{answer_set.inspect}", *args)
+ answers = answer_set.join(", ")
+ answer = ask_simply("#{statement} [#{answers}]", color, options)
correct_answer = answer_set.include?(answer) ? answer : nil
- answers = answer_set.map(&:inspect).join(", ")
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
end
correct_answer
diff --git a/lib/bundler/vendor/thor/shell/color.rb b/lib/bundler/vendor/thor/shell/color.rb
index f24ab6ccb1..fcf9c25d7d 100644
--- a/lib/bundler/vendor/thor/shell/color.rb
+++ b/lib/bundler/vendor/thor/shell/color.rb
@@ -94,6 +94,10 @@ class Thor
protected
+ def can_display_colors?
+ stdout.tty?
+ end
+
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
@@ -112,15 +116,15 @@ class Thor
def output_diff_line(diff) #:nodoc:
case diff.action
- when '-'
- say "- #{diff.old_element.chomp}", :red, true
- when '+'
- say "+ #{diff.new_element.chomp}", :green, true
- when '!'
- say "- #{diff.old_element.chomp}", :red, true
- say "+ #{diff.new_element.chomp}", :green, true
- else
- say " #{diff.old_element.chomp}", nil, true
+ when '-'
+ say "- #{diff.old_element.chomp}", :red, true
+ when '+'
+ say "+ #{diff.new_element.chomp}", :green, true
+ when '!'
+ say "- #{diff.old_element.chomp}", :red, true
+ say "+ #{diff.new_element.chomp}", :green, true
+ else
+ say " #{diff.old_element.chomp}", nil, true
end
end
diff --git a/lib/bundler/vendor/thor/shell/html.rb b/lib/bundler/vendor/thor/shell/html.rb
index 678bc89b87..2a1bb3840a 100644
--- a/lib/bundler/vendor/thor/shell/html.rb
+++ b/lib/bundler/vendor/thor/shell/html.rb
@@ -73,6 +73,10 @@ class Thor
protected
+ def can_display_colors?
+ true
+ end
+
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
@@ -91,15 +95,15 @@ class Thor
def output_diff_line(diff) #:nodoc:
case diff.action
- when '-'
- say "- #{diff.old_element.chomp}", :red, true
- when '+'
- say "+ #{diff.new_element.chomp}", :green, true
- when '!'
- say "- #{diff.old_element.chomp}", :red, true
- say "+ #{diff.new_element.chomp}", :green, true
- else
- say " #{diff.old_element.chomp}", nil, true
+ when '-'
+ say "- #{diff.old_element.chomp}", :red, true
+ when '+'
+ say "+ #{diff.new_element.chomp}", :green, true
+ when '!'
+ say "- #{diff.old_element.chomp}", :red, true
+ say "+ #{diff.new_element.chomp}", :green, true
+ else
+ say " #{diff.old_element.chomp}", nil, true
end
end
diff --git a/lib/bundler/vendor/thor/util.rb b/lib/bundler/vendor/thor/util.rb
index d45843dde0..2510630f05 100644
--- a/lib/bundler/vendor/thor/util.rb
+++ b/lib/bundler/vendor/thor/util.rb
@@ -16,251 +16,255 @@ class Thor
#
module Util
- # Receives a namespace and search for it in the Thor::Base subclasses.
- #
- # ==== Parameters
- # namespace<String>:: The namespace to search for.
- #
- def self.find_by_namespace(namespace)
- namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
- Thor::Base.subclasses.find { |klass| klass.namespace == namespace }
- end
+ class << self
- # Receives a constant and converts it to a Thor namespace. Since Thor tasks
- # can be added to a sandbox, this method is also responsable for removing
- # the sandbox namespace.
- #
- # This method should not be used in general because it's used to deal with
- # older versions of Thor. On current versions, if you need to get the
- # namespace from a class, just call namespace on it.
- #
- # ==== Parameters
- # constant<Object>:: The constant to be converted to the thor path.
- #
- # ==== Returns
- # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
- #
- def self.namespace_from_thor_class(constant)
- constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
- constant = snake_case(constant).squeeze(":")
- constant
- end
+ # Receives a namespace and search for it in the Thor::Base subclasses.
+ #
+ # ==== Parameters
+ # namespace<String>:: The namespace to search for.
+ #
+ def find_by_namespace(namespace)
+ namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
+ Thor::Base.subclasses.find { |klass| klass.namespace == namespace }
+ end
- # Given the contents, evaluate it inside the sandbox and returns the
- # namespaces defined in the sandbox.
- #
- # ==== Parameters
- # contents<String>
- #
- # ==== Returns
- # Array[Object]
- #
- def self.namespaces_in_content(contents, file=__FILE__)
- old_constants = Thor::Base.subclasses.dup
- Thor::Base.subclasses.clear
+ # Receives a constant and converts it to a Thor namespace. Since Thor
+ # commands can be added to a sandbox, this method is also responsable for
+ # removing the sandbox namespace.
+ #
+ # This method should not be used in general because it's used to deal with
+ # older versions of Thor. On current versions, if you need to get the
+ # namespace from a class, just call namespace on it.
+ #
+ # ==== Parameters
+ # constant<Object>:: The constant to be converted to the thor path.
+ #
+ # ==== Returns
+ # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
+ #
+ def namespace_from_thor_class(constant)
+ constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
+ constant = snake_case(constant).squeeze(":")
+ constant
+ end
- load_thorfile(file, contents)
+ # Given the contents, evaluate it inside the sandbox and returns the
+ # namespaces defined in the sandbox.
+ #
+ # ==== Parameters
+ # contents<String>
+ #
+ # ==== Returns
+ # Array[Object]
+ #
+ def namespaces_in_content(contents, file=__FILE__)
+ old_constants = Thor::Base.subclasses.dup
+ Thor::Base.subclasses.clear
- new_constants = Thor::Base.subclasses.dup
- Thor::Base.subclasses.replace(old_constants)
+ load_thorfile(file, contents)
- new_constants.map!{ |c| c.namespace }
- new_constants.compact!
- new_constants
- end
+ new_constants = Thor::Base.subclasses.dup
+ Thor::Base.subclasses.replace(old_constants)
- # Returns the thor classes declared inside the given class.
- #
- def self.thor_classes_in(klass)
- stringfied_constants = klass.constants.map { |c| c.to_s }
- Thor::Base.subclasses.select do |subclass|
- next unless subclass.name
- stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
+ new_constants.map!{ |c| c.namespace }
+ new_constants.compact!
+ new_constants
end
- end
-
- # Receives a string and convert it to snake case. SnakeCase returns snake_case.
- #
- # ==== Parameters
- # String
- #
- # ==== Returns
- # String
- #
- def self.snake_case(str)
- return str.downcase if str =~ /^[A-Z_]+$/
- str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/
- return $+.downcase
- end
-
- # Receives a string and convert it to camel case. camel_case returns CamelCase.
- #
- # ==== Parameters
- # String
- #
- # ==== Returns
- # String
- #
- def self.camel_case(str)
- return str if str !~ /_/ && str =~ /[A-Z]+.*/
- str.split('_').map { |i| i.capitalize }.join
- end
- # Receives a namespace and tries to retrieve a Thor or Thor::Group class
- # from it. It first searches for a class using the all the given namespace,
- # if it's not found, removes the highest entry and searches for the class
- # again. If found, returns the highest entry as the class name.
- #
- # ==== Examples
- #
- # class Foo::Bar < Thor
- # def baz
- # end
- # end
- #
- # class Baz::Foo < Thor::Group
- # end
- #
- # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default task
- # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
- # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
- #
- # ==== Parameters
- # namespace<String>
- #
- def self.find_class_and_task_by_namespace(namespace, fallback = true)
- if namespace.include?(?:) # look for a namespaced task
- pieces = namespace.split(":")
- task = pieces.pop
- klass = Thor::Util.find_by_namespace(pieces.join(":"))
- end
- unless klass # look for a Thor::Group with the right name
- klass, task = Thor::Util.find_by_namespace(namespace), nil
+ # Returns the thor classes declared inside the given class.
+ #
+ def thor_classes_in(klass)
+ stringfied_constants = klass.constants.map { |c| c.to_s }
+ Thor::Base.subclasses.select do |subclass|
+ next unless subclass.name
+ stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
+ end
end
- if !klass && fallback # try a task in the default namespace
- task = namespace
- klass = Thor::Util.find_by_namespace('')
+
+ # Receives a string and convert it to snake case. SnakeCase returns snake_case.
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def snake_case(str)
+ return str.downcase if str =~ /^[A-Z_]+$/
+ str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/
+ return $+.downcase
end
- return klass, task
- end
- # Receives a path and load the thor file in the path. The file is evaluated
- # inside the sandbox to avoid namespacing conflicts.
- #
- def self.load_thorfile(path, content=nil, debug=false)
- content ||= File.binread(path)
+ # Receives a string and convert it to camel case. camel_case returns CamelCase.
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def camel_case(str)
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
+ str.split('_').map { |i| i.capitalize }.join
+ end
- begin
- Thor::Sandbox.class_eval(content, path)
- rescue Exception => e
- $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}")
- if debug
- $stderr.puts(*e.backtrace)
- else
- $stderr.puts(e.backtrace.first)
+ # Receives a namespace and tries to retrieve a Thor or Thor::Group class
+ # from it. It first searches for a class using the all the given namespace,
+ # if it's not found, removes the highest entry and searches for the class
+ # again. If found, returns the highest entry as the class name.
+ #
+ # ==== Examples
+ #
+ # class Foo::Bar < Thor
+ # def baz
+ # end
+ # end
+ #
+ # class Baz::Foo < Thor::Group
+ # end
+ #
+ # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command
+ # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
+ # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
+ #
+ # ==== Parameters
+ # namespace<String>
+ #
+ def find_class_and_command_by_namespace(namespace, fallback = true)
+ if namespace.include?(?:) # look for a namespaced command
+ pieces = namespace.split(":")
+ command = pieces.pop
+ klass = Thor::Util.find_by_namespace(pieces.join(":"))
+ end
+ unless klass # look for a Thor::Group with the right name
+ klass, command = Thor::Util.find_by_namespace(namespace), nil
+ end
+ if !klass && fallback # try a command in the default namespace
+ command = namespace
+ klass = Thor::Util.find_by_namespace('')
end
+ return klass, command
end
- end
+ alias find_class_and_task_by_namespace find_class_and_command_by_namespace
+
+ # Receives a path and load the thor file in the path. The file is evaluated
+ # inside the sandbox to avoid namespacing conflicts.
+ #
+ def load_thorfile(path, content=nil, debug=false)
+ content ||= File.binread(path)
- def self.user_home
- @@user_home ||= if ENV["HOME"]
- ENV["HOME"]
- elsif ENV["USERPROFILE"]
- ENV["USERPROFILE"]
- elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
- File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
- elsif ENV["APPDATA"]
- ENV["APPDATA"]
- else
begin
- File.expand_path("~")
- rescue
- if File::ALT_SEPARATOR
- "C:/"
+ Thor::Sandbox.class_eval(content, path)
+ rescue Exception => e
+ $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}")
+ if debug
+ $stderr.puts(*e.backtrace)
else
- "/"
+ $stderr.puts(e.backtrace.first)
end
end
end
- end
- # Returns the root where thor files are located, depending on the OS.
- #
- def self.thor_root
- File.join(user_home, ".thor").gsub(/\\/, '/')
- end
+ def user_home
+ @@user_home ||= if ENV["HOME"]
+ ENV["HOME"]
+ elsif ENV["USERPROFILE"]
+ ENV["USERPROFILE"]
+ elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
+ File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
+ elsif ENV["APPDATA"]
+ ENV["APPDATA"]
+ else
+ begin
+ File.expand_path("~")
+ rescue
+ if File::ALT_SEPARATOR
+ "C:/"
+ else
+ "/"
+ end
+ end
+ end
+ end
+
+ # Returns the root where thor files are located, depending on the OS.
+ #
+ def thor_root
+ File.join(user_home, ".thor").gsub(/\\/, '/')
+ end
- # Returns the files in the thor root. On Windows thor_root will be something
- # like this:
- #
- # C:\Documents and Settings\james\.thor
- #
- # If we don't #gsub the \ character, Dir.glob will fail.
- #
- def self.thor_root_glob
- files = Dir["#{escape_globs(thor_root)}/*"]
+ # Returns the files in the thor root. On Windows thor_root will be something
+ # like this:
+ #
+ # C:\Documents and Settings\james\.thor
+ #
+ # If we don't #gsub the \ character, Dir.glob will fail.
+ #
+ def thor_root_glob
+ files = Dir["#{escape_globs(thor_root)}/*"]
- files.map! do |file|
- File.directory?(file) ? File.join(file, "main.thor") : file
+ files.map! do |file|
+ File.directory?(file) ? File.join(file, "main.thor") : file
+ end
end
- end
- # Where to look for Thor files.
- #
- def self.globs_for(path)
- path = escape_globs(path)
- ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
- end
+ # Where to look for Thor files.
+ #
+ def globs_for(path)
+ path = escape_globs(path)
+ ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
+ end
- # Return the path to the ruby interpreter taking into account multiple
- # installations and windows extensions.
- #
- def self.ruby_command
- @ruby_command ||= begin
- ruby_name = RbConfig::CONFIG['ruby_install_name']
- ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name)
- ruby << RbConfig::CONFIG['EXEEXT']
+ # Return the path to the ruby interpreter taking into account multiple
+ # installations and windows extensions.
+ #
+ def ruby_command
+ @ruby_command ||= begin
+ ruby_name = RbConfig::CONFIG['ruby_install_name']
+ ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name)
+ ruby << RbConfig::CONFIG['EXEEXT']
- # avoid using different name than ruby (on platforms supporting links)
- if ruby_name != 'ruby' && File.respond_to?(:readlink)
- begin
- alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
- alternate_ruby << RbConfig::CONFIG['EXEEXT']
+ # avoid using different name than ruby (on platforms supporting links)
+ if ruby_name != 'ruby' && File.respond_to?(:readlink)
+ begin
+ alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
+ alternate_ruby << RbConfig::CONFIG['EXEEXT']
- # ruby is a symlink
- if File.symlink? alternate_ruby
- linked_ruby = File.readlink alternate_ruby
+ # ruby is a symlink
+ if File.symlink? alternate_ruby
+ linked_ruby = File.readlink alternate_ruby
- # symlink points to 'ruby_install_name'
- ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
+ # symlink points to 'ruby_install_name'
+ ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
+ end
+ rescue NotImplementedError
+ # just ignore on windows
end
- rescue NotImplementedError
- # just ignore on windows
end
+
+ # escape string in case path to ruby executable contain spaces.
+ ruby.sub!(/.*\s.*/m, '"\&"')
+ ruby
end
+ end
- # escape string in case path to ruby executable contain spaces.
- ruby.sub!(/.*\s.*/m, '"\&"')
- ruby
+ # Returns a string that has had any glob characters escaped.
+ # The glob characters are `* ? { } [ ]`.
+ #
+ # ==== Examples
+ #
+ # Thor::Util.escape_globs('[apps]') # => '\[apps\]'
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def escape_globs(path)
+ path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
end
- end
- # Returns a string that has had any glob characters escaped.
- # The glob characters are `* ? { } [ ]`.
- #
- # ==== Examples
- #
- # Thor::Util.escape_globs('[apps]') # => '\[apps\]'
- #
- # ==== Parameters
- # String
- #
- # ==== Returns
- # String
- #
- def self.escape_globs(path)
- path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
end
-
end
end
diff --git a/lib/bundler/vendor/thor/version.rb b/lib/bundler/vendor/thor/version.rb
index 4765b35d20..646cd37dec 100644
--- a/lib/bundler/vendor/thor/version.rb
+++ b/lib/bundler/vendor/thor/version.rb
@@ -1,3 +1,3 @@
class Thor
- VERSION = "0.16.0"
+ VERSION = "0.18.1"
end
diff --git a/man/bundle-install.ronn b/man/bundle-install.ronn
index 1bf9066308..4bbfc8c969 100644
--- a/man/bundle-install.ronn
+++ b/man/bundle-install.ronn
@@ -10,6 +10,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile
[--binstubs[=DIRECTORY]]
[--standalone[=GROUP1[ GROUP2...]]]
[--trust-policy=POLICY]
+ [--jobs=SIZE]
[--no-cache]
[--quiet]
@@ -59,7 +60,7 @@ update process below under [CONSERVATIVE UPDATING][].
Do not attempt to connect to `rubygems.org`, instead using just
the gems already present in Rubygems' cache or in `vendor/cache`.
Note that if a more appropriate platform-specific gem exists on
- `rubygems.org`, it will not be found.
+ `rubygems.org`, it will not be found. This option implies `--no-cache`.
* `--deployment`:
Switches bundler's defaults into [deployment mode][DEPLOYMENT MODE].
@@ -88,6 +89,9 @@ update process below under [CONSERVATIVE UPDATING][].
HighSecurity, MediumSecurity, LowSecurity, or NoSecurity. For more detail,
see the Rubygems signing documentation, linked below in [SEE ALSO][].
+* `--jobs=[<size>]`:
+ Install gems parallely by starting <size> number of parallel workers.
+
* `--no-cache`:
Do not update the cache in `vendor/cache` with the newly bundled gems. This
does not remove any existing cached gems, only stops the newly bundled gems
diff --git a/man/gemfile.5.ronn b/man/gemfile.5.ronn
index 53c1c341aa..17b0ce2c1a 100644
--- a/man/gemfile.5.ronn
+++ b/man/gemfile.5.ronn
@@ -74,16 +74,19 @@ Each _gem_ `MAY` have one or more version specifiers.
### REQUIRE AS (:require)
Each _gem_ `MAY` specify files that should be used when autorequiring via
-`Bundler.require`. You may pass an array with multiple files, or `false` to
+`Bundler.require`. You may pass an array with multiple files or `true` if file
+you want `required` has same name as _gem_ or `false` to
prevent any file from being autorequired.
gem "redis", :require => ["redis/connection/hiredis", "redis"]
gem "webmock", :require => false
+ gem "debugger", :require => true
The argument defaults to the name of the gem. For example, these are identical:
gem "nokogiri"
gem "nokogiri", :require => "nokogiri"
+ gem "nokogiri", :require => true
### GROUPS (:group or :groups)
@@ -258,6 +261,10 @@ Are both equivalent to
gem "rails", :git => "git://github.com/rails/rails.git"
+In addition, if you wish to choose a specific branch:
+
+ gem "rails", :github => "rails/rails", :branch => "branch_name"
+
### PATH (:path)
You can specify that a gem is located in a particular location
diff --git a/spec/bundler/cli_rspec.rb b/spec/bundler/cli_rspec.rb
index e476c74498..0f7711144c 100644
--- a/spec/bundler/cli_rspec.rb
+++ b/spec/bundler/cli_rspec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe 'bundle executable' do
- it 'returns non-zero exit status when passed unrecognized options' do
+describe "bundle executable" do
+ it "returns non-zero exit status when passed unrecognized options" do
bundle '--invalid_argument', :exitstatus => true
expect(exitstatus).to_not be_zero
end
- it 'returns non-zero exit status when passed unrecognized task' do
+ it "returns non-zero exit status when passed unrecognized task" do
bundle 'unrecognized-tast', :exitstatus => true
expect(exitstatus).to_not be_zero
end
diff --git a/spec/bundler/dsl_spec.rb b/spec/bundler/dsl_spec.rb
index 17ce5246fb..1b05eb48b4 100644
--- a/spec/bundler/dsl_spec.rb
+++ b/spec/bundler/dsl_spec.rb
@@ -2,14 +2,14 @@ require 'spec_helper'
describe Bundler::Dsl do
before do
- @rubygems = mock("rubygems")
+ @rubygems = double("rubygems")
Bundler::Source::Rubygems.stub(:new){ @rubygems }
end
- describe '#_normalize_options' do
+ describe "#_normalize_options" do
it "converts :github to :git" do
subject.gem("sparks", :github => "indirect/sparks")
- github_uri = "git://github.com/indirect/sparks.git"
+ github_uri = "https://github.com/indirect/sparks.git"
expect(subject.dependencies.first.source.uri).to eq(github_uri)
end
@@ -27,13 +27,13 @@ describe Bundler::Dsl do
it "converts 'rails' to 'rails/rails'" do
subject.gem("rails", :github => "rails")
- github_uri = "git://github.com/rails/rails.git"
+ github_uri = "https://github.com/rails/rails.git"
expect(subject.dependencies.first.source.uri).to eq(github_uri)
end
end
- describe '#method_missing' do
- it 'raises an error for unknown DSL methods' do
+ describe "#method_missing" do
+ it "raises an error for unknown DSL methods" do
Bundler.should_receive(:read_file).with("Gemfile").and_return("unknown")
error_msg = "Undefined local variable or method `unknown'" \
" for Gemfile\\s+from Gemfile:1"
@@ -51,7 +51,7 @@ describe Bundler::Dsl do
end
describe "syntax errors" do
- it "raise a Bundler::GemfileError" do
+ it "will raise a Bundler::GemfileError" do
gemfile "gem 'foo', :path => /unquoted/string/syntax/error"
expect { Bundler::Dsl.evaluate(bundled_app("Gemfile"), nil, true) }.
to raise_error(Bundler::GemfileError)
diff --git a/spec/bundler/gem_helper_spec.rb b/spec/bundler/gem_helper_spec.rb
index 134fc87099..d3181e076c 100644
--- a/spec/bundler/gem_helper_spec.rb
+++ b/spec/bundler/gem_helper_spec.rb
@@ -62,7 +62,7 @@ describe "Bundler::GemHelper tasks" do
expect(Bundler.ui).to be_a(Bundler::UI::Shell)
end
- describe 'install_tasks' do
+ describe "install_tasks" do
before(:each) do
@saved, Rake.application = Rake.application, Rake::Application.new
end
@@ -92,7 +92,7 @@ describe "Bundler::GemHelper tasks" do
end
end
- describe 'build' do
+ describe "build" do
it "builds" do
mock_build_message
@helper.build_gem
@@ -106,7 +106,7 @@ describe "Bundler::GemHelper tasks" do
end
end
- describe 'install' do
+ describe "install" do
it "installs" do
mock_build_message
mock_confirm_message "test (0.0.1) installed."
@@ -127,7 +127,15 @@ describe "Bundler::GemHelper tasks" do
end
end
- describe 'release' do
+ describe "release" do
+ before do
+ Dir.chdir(@app) do
+ `git init`
+ `git config user.email "you@example.com"`
+ `git config user.name "name"`
+ end
+ end
+
it "shouldn't push if there are unstaged files" do
expect { @helper.release_gem }.to raise_error(/files that need to be committed/)
end
@@ -137,18 +145,11 @@ describe "Bundler::GemHelper tasks" do
expect { @helper.release_gem }.to raise_error(/files that need to be committed/)
end
- it 'raises an appropriate error if there is no git remote' do
+ it "raises an appropriate error if there is no git remote" do
Bundler.ui.stub(:confirm => nil, :error => nil) # silence messages
- Dir.chdir(gem_repo1) {
- `git init --bare`
- }
- Dir.chdir(@app) {
- `git init`
- `git config user.email "you@example.com"`
- `git config user.name "name"`
- `git commit -a -m "initial commit"`
- }
+ Dir.chdir(gem_repo1) { `git init --bare` }
+ Dir.chdir(@app) { `git commit -a -m "initial commit"` }
expect { @helper.release_gem }.to raise_error
end
@@ -160,18 +161,13 @@ describe "Bundler::GemHelper tasks" do
@helper.should_receive(:rubygem_push).with(bundled_app('test/pkg/test-0.0.1.gem').to_s)
- Dir.chdir(gem_repo1) {
- `git init --bare`
- }
- Dir.chdir(@app) {
- `git init`
- `git config user.email "you@example.com"`
- `git config user.name "name"`
+ Dir.chdir(gem_repo1) { `git init --bare` }
+ Dir.chdir(@app) do
`git remote add origin file://#{gem_repo1}`
`git commit -a -m "initial commit"`
sys_exec("git push origin master", true)
`git commit -a -m "another commit"`
- }
+ end
@helper.release_gem
end
diff --git a/spec/bundler/safe_catch_spec.rb b/spec/bundler/safe_catch_spec.rb
new file mode 100644
index 0000000000..40b6ec092c
--- /dev/null
+++ b/spec/bundler/safe_catch_spec.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+require 'spec_helper'
+require 'bundler'
+require "bundler/safe_catch"
+require "bundler/current_ruby"
+
+class RecursiveTmpResolver
+ include Bundler::SafeCatch
+end
+
+describe Bundler::SafeCatch do
+ let(:resolver) { RecursiveTmpResolver.new() }
+
+ it "should use safe_catch on jruby" do
+ if Bundler.current_ruby.jruby?
+ Bundler::SafeCatch::Internal.should_receive(:catch).and_call_original
+ Bundler::SafeCatch::Internal.should_receive(:throw).and_call_original
+
+ retval = resolver.safe_catch(:resolve) do
+ resolver.safe_throw(:resolve, "good bye world")
+ end
+ expect(retval).to eq("good bye world")
+ end
+ end
+
+ it "should use regular catch/throw on MRI" do
+ if Bundler.current_ruby.mri?
+ Bundler::SafeCatch::Internal.should_not_receive(:catch)
+ Bundler::SafeCatch::Internal.should_not_receive(:throw)
+
+ retval = resolver.safe_catch(:resolve) do
+ resolver.safe_throw(:resolve, "good bye world")
+ end
+ expect(retval).to eq("good bye world")
+ end
+ end
+end
diff --git a/spec/install/gems/dependency_api_spec.rb b/spec/install/gems/dependency_api_spec.rb
index f89c865eba..d56d788c21 100644
--- a/spec/install/gems/dependency_api_spec.rb
+++ b/spec/install/gems/dependency_api_spec.rb
@@ -325,7 +325,7 @@ describe "gemcutter's dependency API" do
expect(out).to include("Fetching gem metadata from #{source_uri}")
end
- fit "should install when EndpointSpecification with a bin dir owned by root", :sudo => true do
+ it "should install when EndpointSpecification has a bin dir owned by root", :sudo => true do
sudo "mkdir -p #{system_gem_path("bin")}"
sudo "chown -R root #{system_gem_path("bin")}"
@@ -334,7 +334,6 @@ describe "gemcutter's dependency API" do
gem "rails"
G
bundle :install, :artifice => "endpoint"
- puts out, err
should_be_installed "rails 2.3.2"
end
diff --git a/spec/install/gems/packed_spec.rb b/spec/install/gems/packed_spec.rb
index cce3b4ab26..1992f5d342 100644
--- a/spec/install/gems/packed_spec.rb
+++ b/spec/install/gems/packed_spec.rb
@@ -2,7 +2,7 @@ require "spec_helper"
describe "bundle install with gem sources" do
describe "when cached and locked" do
- it "does not hit the remote at all" do
+ it "does not hit the remote at all if --local is passed" do
build_repo2
install_gemfile <<-G
source "file://#{gem_repo2}"
@@ -14,10 +14,11 @@ describe "bundle install with gem sources" do
FileUtils.rm_rf gem_repo2
bundle "install --local"
+ expect(out).not_to include("Updating files in vendor/cache")
should_be_installed "rack 1.0.0"
end
- it "does not hit the remote at all" do
+ it "does not hit the remote at all if --deployment is passed" do
build_repo2
install_gemfile <<-G
source "file://#{gem_repo2}"
@@ -29,6 +30,7 @@ describe "bundle install with gem sources" do
FileUtils.rm_rf gem_repo2
bundle "install --deployment"
+ expect(out).not_to include("Updating files in vendor/cache")
should_be_installed "rack 1.0.0"
end
diff --git a/spec/install/gems/post_install_spec.rb b/spec/install/gems/post_install_spec.rb
index f7cad665e1..49ee8d8385 100644
--- a/spec/install/gems/post_install_spec.rb
+++ b/spec/install/gems/post_install_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe 'bundle install with gem sources' do
- describe 'when gems include post install messages' do
+describe "bundle install with gem sources" do
+ describe "when gems include post install messages" do
it "should display the post-install messages after installing" do
gemfile <<-G
source "file://#{gem_repo1}"
@@ -20,7 +20,7 @@ describe 'bundle install with gem sources' do
end
end
- describe 'when gems do not include post install messages' do
+ describe "when gems do not include post install messages" do
it "should not display any post-install messages" do
gemfile <<-G
source "file://#{gem_repo1}"
diff --git a/spec/install/gems/resolving_spec.rb b/spec/install/gems/resolving_spec.rb
index edf8b326ae..c459becfca 100644
--- a/spec/install/gems/resolving_spec.rb
+++ b/spec/install/gems/resolving_spec.rb
@@ -67,6 +67,43 @@ describe "bundle install with gem sources" do
should_be_installed "net_a 1.0", "net_b 1.0", "net_c 1.0", "net_d 1.0", "net_e 1.0"
end
+
+ context "with ENV['DEBUG_RESOLVER'] set" do
+ before do
+ ENV['DEBUG_RESOLVER'] = '1'
+ end
+ it "produces debug output" do
+ expect(capture(:stdout) do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_c"
+ gem "net_e"
+ G
+ end).to include "==== Iterating ===="
+ end
+ after do
+ ENV['DEBUG_RESOLVER'] = nil
+ end
+ end
+
+ context "with ENV['DEBUG_RESOLVER_TREE'] set" do
+ before do
+ ENV['DEBUG_RESOLVER_TREE'] = '1'
+ end
+ it "produces debug output" do
+ expect(capture(:stdout) do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "net_c"
+ gem "net_e"
+ G
+ end).to include " net_b (>= 0) ruby"
+ end
+ after do
+ ENV['DEBUG_RESOLVER_TREE'] = nil
+ end
+ end
+
end
describe "when some gems require a different version of ruby" do
diff --git a/spec/install/gems/simple_case_spec.rb b/spec/install/gems/simple_case_spec.rb
index 4c38e77b82..630ea083c1 100644
--- a/spec/install/gems/simple_case_spec.rb
+++ b/spec/install/gems/simple_case_spec.rb
@@ -814,4 +814,22 @@ describe "bundle install with gem sources" do
end
end
+ it "should use gemspecs in the system cache when available" do
+ gemfile <<-G
+ source "http://localtestserver.gem"
+ gem 'rack'
+ G
+
+ FileUtils.mkdir_p "#{tmp}/gems/system/specifications"
+ File.open("#{tmp}/gems/system/specifications/rack-1.0.0.gemspec", 'w+') do |f|
+ spec = Gem::Specification.new do |s|
+ s.name = 'rack'
+ s.version = '1.0.0'
+ s.add_runtime_dependency 'activesupport', '2.3.2'
+ end
+ f.write spec.to_ruby
+ end
+ bundle :install, :artifice => 'endpoint_marshal_fail' # force gemspec load
+ should_be_installed "activesupport 2.3.2"
+ end
end
diff --git a/spec/install/gems/win32_spec.rb b/spec/install/gems/win32_spec.rb
index 771d3faa4a..6224220281 100644
--- a/spec/install/gems/win32_spec.rb
+++ b/spec/install/gems/win32_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe "bundle install with win32-generated lockfile" do
- it 'should read lockfile' do
+ it "should read lockfile" do
File.open(bundled_app('Gemfile.lock'), 'wb') do |f|
f << "GEM\r\n"
f << " remote: file:#{gem_repo1}/\r\n"
diff --git a/spec/install/security_policy_spec.rb b/spec/install/security_policy_spec.rb
index c0cadcf9ba..941e14de27 100644
--- a/spec/install/security_policy_spec.rb
+++ b/spec/install/security_policy_spec.rb
@@ -14,37 +14,37 @@ describe "policies with unsigned gems" do
G
end
- it "works after you try to deploy without a lock" do
+ it "will work after you try to deploy without a lock" do
bundle "install --deployment"
bundle :install, :exitstatus => true
expect(exitstatus).to eq(0)
should_be_installed "rack 1.0", "signed_gem 1.0"
end
- it "fails when given invalid security policy" do
+ it "will fail when given invalid security policy" do
bundle "install --trust-policy=InvalidPolicyName"
expect(out).to include("Rubygems doesn't know about trust policy")
end
- it "fails with High Security setting due to presence of unsigned gem" do
+ it "will fail with High Security setting due to presence of unsigned gem" do
bundle "install --trust-policy=HighSecurity"
expect(out).to include("security policy didn't allow")
end
# This spec will fail on Rubygems 2 rc1 due to a bug in policy.rb. the bug is fixed in rc3.
- it "fails with Medium Security setting due to presence of unsigned gem", :unless => ENV['RGV'] == "v2.0.0.rc.1" do
+ it "will fail with Medium Security setting due to presence of unsigned gem", :unless => ENV['RGV'] == "v2.0.0.rc.1" do
bundle "install --trust-policy=MediumSecurity"
expect(out).to include("security policy didn't allow")
end
- it "succeeds with no policy" do
+ it "will succeed with no policy" do
bundle "install", :exitstatus => true
expect(exitstatus).to eq(0)
end
end
-describe "policies with signed gems, no CA" do
+describe "policies with signed gems and no CA" do
before do
build_security_repo
gemfile <<-G
@@ -53,26 +53,25 @@ describe "policies with signed gems, no CA" do
G
end
- it "fails with High Security setting, gem is self-signed" do
+ it "will fail with High Security setting, gem is self-signed" do
bundle "install --trust-policy=HighSecurity"
expect(out).to include("security policy didn't allow")
end
- it "fails with Medium Security setting, gem is self-signed" do
+ it "will fail with Medium Security setting, gem is self-signed" do
bundle "install --trust-policy=MediumSecurity"
expect(out).to include("security policy didn't allow")
end
- it "succeeds with Low Security setting, low security accepts self signed gem" do
+ it "will succeed with Low Security setting, low security accepts self signed gem" do
bundle "install --trust-policy=LowSecurity", :exitstatus => true
expect(exitstatus).to eq(0)
should_be_installed "signed_gem 1.0"
end
- it "succeeds with no policy" do
+ it "will succeed with no policy" do
bundle "install", :exitstatus => true
expect(exitstatus).to eq(0)
should_be_installed "signed_gem 1.0"
end
-
end
diff --git a/spec/integration/inject.rb b/spec/integration/inject.rb
index f7ffff6083..2737b1a4c7 100644
--- a/spec/integration/inject.rb
+++ b/spec/integration/inject.rb
@@ -21,7 +21,7 @@ describe "bundle inject" do
bundle "install"
end
- it "adds the injected gems to the gemfile" do
+ it "adds the injected gems to the Gemfile" do
expect(bundled_app("Gemfile").read).not_to match(/rack-obama/)
bundle "inject 'rack-obama' '> 0'"
expect(bundled_app("Gemfile").read).to match(/rack-obama/)
diff --git a/spec/lock/git_spec.rb b/spec/lock/git_spec.rb
index 23b7f3112c..9d4ecf9bc7 100644
--- a/spec/lock/git_spec.rb
+++ b/spec/lock/git_spec.rb
@@ -31,5 +31,4 @@ describe "bundle lock with git gems" do
RUBY
expect(out).to eq(bundle("show foo"))
end
-
end
diff --git a/spec/lock/lockfile_spec.rb b/spec/lock/lockfile_spec.rb
index 30c4b22483..779fd1c613 100644
--- a/spec/lock/lockfile_spec.rb
+++ b/spec/lock/lockfile_spec.rb
@@ -292,7 +292,7 @@ describe "the lockfile format" do
G
end
- it "order dependencies of dependencies in alphabetical order" do
+ it "orders dependencies' dependencies in alphabetical order" do
install_gemfile <<-G
source "file://#{gem_repo1}"
@@ -328,7 +328,7 @@ describe "the lockfile format" do
G
end
- it "orders dependencies according to version" do
+ it "orders dependencies by version" do
install_gemfile <<-G
source "file://#{gem_repo1}"
gem 'double_deps'
@@ -472,6 +472,30 @@ describe "the lockfile format" do
G
end
+ it "stores relative paths when the path is provided for gemspec" do
+ build_lib("foo", :path => tmp.join("foo"))
+
+ install_gemfile <<-G
+ gemspec :path => "../foo"
+ G
+
+ lockfile_should_be <<-G
+ PATH
+ remote: ../foo
+ specs:
+ foo (1.0)
+
+ GEM
+ specs:
+
+ PLATFORMS
+ #{generic(Gem::Platform.local)}
+
+ DEPENDENCIES
+ foo!
+ G
+ end
+
it "keeps existing platforms in the lockfile" do
lockfile <<-G
GEM
@@ -638,7 +662,7 @@ describe "the lockfile format" do
end
- it "raises if two different versions are used" do
+ it "raises if two different sources are used" do
install_gemfile <<-G
source "file://#{gem_repo1}"
gem "rack"
@@ -676,7 +700,7 @@ describe "the lockfile format" do
# * multiple copies of the same GIT section appeared in the lockfile
# * when this happened, those sections got multiple copies of gems
# in those sections.
- it "fix corrupted lockfiles" do
+ it "fixes corrupted lockfiles" do
build_git "omg", :path => lib_path('omg')
revision = revision_for(lib_path('omg'))
@@ -740,7 +764,7 @@ describe "the lockfile format" do
L
end
- describe "line endings" do
+ describe "a line ending" do
def set_lockfile_mtime_to_known_value
time = Time.local(2000, 1, 1, 0, 0, 0)
File.utime(time, time, bundled_app('Gemfile.lock'))
diff --git a/spec/other/binstubs_spec.rb b/spec/other/binstubs_spec.rb
index f5e1347a39..3d3ed0fc58 100644
--- a/spec/other/binstubs_spec.rb
+++ b/spec/other/binstubs_spec.rb
@@ -26,6 +26,30 @@ describe "bundle binstubs <gem>" do
expect(bundled_app("bin/rails")).to exist
end
+ it "does install multiple binstubs" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ gem "rails"
+ G
+
+ bundle "binstubs rails rack"
+
+ expect(bundled_app("bin/rackup")).to exist
+ expect(bundled_app("bin/rails")).to exist
+ end
+
+ it "displays an error when used without any gem" do
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rack"
+ G
+
+ bundle "binstubs", :exitstatus => true
+ expect(exitstatus).to eq(1)
+ expect(out).to eq("`bundle binstubs` needs at least one gem to run.")
+ end
+
it "does not bundle the bundler binary" do
install_gemfile <<-G
source "file://#{gem_repo1}"
@@ -37,7 +61,7 @@ describe "bundle binstubs <gem>" do
expect(out).to eq("Sorry, Bundler can only be run via Rubygems.")
end
- it "install binstubs from git gems" do
+ it "installs binstubs from git gems" do
FileUtils.mkdir_p(lib_path("foo/bin"))
FileUtils.touch(lib_path("foo/bin/foo"))
build_git "foo", "1.0", :path => lib_path("foo") do |s|
@@ -95,7 +119,7 @@ describe "bundle binstubs <gem>" do
end
context "when the bin already exists" do
- it "don't override it and warn" do
+ it "doesn't overwrite and warns" do
FileUtils.mkdir_p(bundled_app("bin"))
File.open(bundled_app("bin/rackup"), 'wb') do |file|
file.print "OMG"
@@ -115,7 +139,7 @@ describe "bundle binstubs <gem>" do
end
context "when using --force" do
- it "overrides the binstub" do
+ it "overwrites the binstub" do
FileUtils.mkdir_p(bundled_app("bin"))
File.open(bundled_app("bin/rackup"), 'wb') do |file|
file.print "OMG"
diff --git a/spec/other/bundle_ruby_spec.rb b/spec/other/bundle_ruby_spec.rb
index 164a23c6ae..5bb563a74f 100644
--- a/spec/other/bundle_ruby_spec.rb
+++ b/spec/other/bundle_ruby_spec.rb
@@ -83,7 +83,7 @@ describe "bundle_ruby" do
expect(out).to eq("Please define :engine")
end
- it "raises an error if engine version doesn't match ruby version for mri" do
+ it "raises an error if engine version doesn't match ruby version for MRI" do
gemfile <<-G
source "file://#{gem_repo1}"
ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
diff --git a/spec/other/check_spec.rb b/spec/other/check_spec.rb
index 5f36fa26fe..248b5c2eed 100644
--- a/spec/other/check_spec.rb
+++ b/spec/other/check_spec.rb
@@ -23,7 +23,7 @@ describe "bundle check" do
expect(out).to eq("The Gemfile's dependencies are satisfied")
end
- it "creates a Gemfile.lock by default if one did not exist" do
+ it "creates a Gemfile.lock by default if one does not exist" do
install_gemfile <<-G
source "file://#{gem_repo1}"
gem "rails"
diff --git a/spec/other/clean_spec.rb b/spec/other/clean_spec.rb
index 393902ba08..cee1312a5f 100644
--- a/spec/other/clean_spec.rb
+++ b/spec/other/clean_spec.rb
@@ -100,7 +100,7 @@ describe "bundle clean" do
expect(vendored_gems("bin/rackup")).to exist
end
- it "remove gems in bundle without groups" do
+ it "removes gems in bundle without groups" do
gemfile <<-G
source "file://#{gem_repo1}"
@@ -288,7 +288,7 @@ describe "bundle clean" do
end
# handling bundle clean upgrade path from the pre's
- it "removes .gem/.gemspec file even if there's no corresponding gem dir is already moved" do
+ it "removes .gem/.gemspec file even if there's no corresponding gem dir" do
gemfile <<-G
source "file://#{gem_repo1}"
diff --git a/spec/other/exec_spec.rb b/spec/other/exec_spec.rb
index b5504249cc..4ed9f2f6aa 100644
--- a/spec/other/exec_spec.rb
+++ b/spec/other/exec_spec.rb
@@ -108,13 +108,13 @@ describe "bundle exec" do
should_not_be_installed "rack_middleware 1.0"
end
- it "should not duplicate already exec'ed RUBYOPT or PATH" do
+ it "does not duplicate already exec'ed RUBYOPT" do
install_gemfile <<-G
gem "rack"
G
rubyopt = ENV['RUBYOPT']
- rubyopt = "-I#{bundler_path} -rbundler/setup #{rubyopt}"
+ rubyopt = "-rbundler/setup #{rubyopt}"
bundle "exec 'echo $RUBYOPT'"
expect(out).to have_rubyopts(rubyopt)
@@ -123,6 +123,22 @@ describe "bundle exec" do
expect(out).to have_rubyopts(rubyopt)
end
+ it "does not duplicate already exec'ed RUBYLIB" do
+ install_gemfile <<-G
+ gem "rack"
+ G
+
+ rubylib = ENV['RUBYLIB']
+ rubylib = "#{rubylib}".split(File::PATH_SEPARATOR).unshift "#{bundler_path}"
+ rubylib = rubylib.uniq.join(File::PATH_SEPARATOR)
+
+ bundle "exec 'echo $RUBYLIB'"
+ expect(out).to eq(rubylib)
+
+ bundle "exec 'echo $RUBYLIB'", :env => {"RUBYLIB" => rubylib}
+ expect(out).to eq(rubylib)
+ end
+
it "errors nicely when the argument doesn't exist" do
install_gemfile <<-G
gem "rack"
@@ -244,6 +260,5 @@ describe "bundle exec" do
expect(out).to eq("1.0")
end
end
-
end
end
diff --git a/spec/other/init_spec.rb b/spec/other/init_spec.rb
index 1c686461e6..8bd566dcf5 100644
--- a/spec/other/init_spec.rb
+++ b/spec/other/init_spec.rb
@@ -36,5 +36,4 @@ describe "bundle init" do
expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1)
expect(gemfile.scan(/group :development/).size).to eq(1)
end
-
end
diff --git a/spec/other/outdated_spec.rb b/spec/other/outdated_spec.rb
index 54676fcf0e..b498510ff7 100644
--- a/spec/other/outdated_spec.rb
+++ b/spec/other/outdated_spec.rb
@@ -1,7 +1,6 @@
require "spec_helper"
describe "bundle outdated" do
-
before :each do
build_repo2 do
build_git "foo", :path => lib_path("foo")
@@ -26,7 +25,7 @@ describe "bundle outdated" do
bundle "outdated"
- expect(out).to include("activesupport (3.0 > 2.3.5)")
+ expect(out).to include("activesupport (3.0 > 2.3.5) Gemfile specifies \"= 2.3.5\"")
expect(out).to include("foo (1.0")
# Gem names are one per-line, between "*" and their parenthesized version.
@@ -93,7 +92,7 @@ describe "bundle outdated" do
end
bundle "outdated --pre"
- expect(out).to include("activesupport (3.0.0.beta > 2.3.5)")
+ expect(out).to include("activesupport (3.0.0.beta > 2.3.5) Gemfile specifies \"= 2.3.5\"")
end
end
@@ -110,9 +109,8 @@ describe "bundle outdated" do
G
bundle "outdated"
- expect(out).to include("activesupport (3.0.0.beta.2 > 3.0.0.beta.1)")
+ expect(out).to include("activesupport (3.0.0.beta.2 > 3.0.0.beta.1) Gemfile specifies \"= 3.0.0.beta.1\"")
end
end
end
-
end
diff --git a/spec/other/platform_spec.rb b/spec/other/platform_spec.rb
index f0329a53e9..4efac30e8e 100644
--- a/spec/other/platform_spec.rb
+++ b/spec/other/platform_spec.rb
@@ -81,7 +81,7 @@ G
expect(out).to eq("ruby 1.9.3")
end
- it "engine defaults to MRI" do
+ it "defaults to MRI" do
gemfile <<-G
source "file://#{gem_repo1}"
ruby "1.9.3"
@@ -146,7 +146,7 @@ G
expect(exitstatus).not_to eq(0)
end
- it "raises an error if engine version doesn't match ruby version for mri" do
+ it "raises an error if engine version doesn't match ruby version for MRI" do
gemfile <<-G
source "file://#{gem_repo1}"
ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4'
diff --git a/spec/quality_spec.rb b/spec/quality_spec.rb
index a9d3d6f4f8..840f2be97f 100644
--- a/spec/quality_spec.rb
+++ b/spec/quality_spec.rb
@@ -5,6 +5,18 @@ if defined?(Encoding) && Encoding.default_external != "UTF-8"
end
describe "The library itself" do
+ def check_for_spec_defs_with_single_quotes(filename)
+ failing_lines = []
+
+ File.readlines(filename).each_with_index do |line,number|
+ failing_lines << number + 1 if line =~ /^ *(describe|it|context) {1}'{1}/
+ end
+
+ unless failing_lines.empty?
+ "#{filename} uses inconsistent single quotes on lines #{failing_lines.join(', ')}"
+ end
+ end
+
def check_for_tab_characters(filename)
failing_lines = []
File.readlines(filename).each_with_index do |line,number|
@@ -20,6 +32,7 @@ describe "The library itself" do
failing_lines = []
File.readlines(filename).each_with_index do |line,number|
next if line =~ /^\s+#.*\s+\n$/
+ next if %w(LICENCE.md).include?(line)
failing_lines << number + 1 if line =~ /\s+\n$/
end
@@ -39,10 +52,11 @@ describe "The library itself" do
end
it "has no malformed whitespace" do
+ exempt = /\.gitmodules|\.marshal|fixtures|vendor|ssl_certs|LICENSE/
error_messages = []
Dir.chdir(File.expand_path("../..", __FILE__)) do
`git ls-files`.split("\n").each do |filename|
- next if filename =~ /\.gitmodules|\.marshal|fixtures|vendor|ssl_certs/
+ next if filename =~ exempt
error_messages << check_for_tab_characters(filename)
error_messages << check_for_extra_spaces(filename)
end
@@ -50,6 +64,18 @@ describe "The library itself" do
expect(error_messages.compact).to be_well_formed
end
+ it "uses double-quotes consistently in specs" do
+ included = /spec/
+ error_messages = []
+ Dir.chdir(File.expand_path("../", __FILE__)) do
+ `git ls-files`.split("\n").each do |filename|
+ next unless filename =~ included
+ error_messages << check_for_spec_defs_with_single_quotes(filename)
+ end
+ end
+ expect(error_messages.compact).to be_well_formed
+ end
+
it "can still be built" do
Dir.chdir(root) do
`gem build bundler.gemspec`
diff --git a/spec/realworld/dependency_api_spec.rb b/spec/realworld/dependency_api_spec.rb
index ad4756c2f6..5d32947223 100644
--- a/spec/realworld/dependency_api_spec.rb
+++ b/spec/realworld/dependency_api_spec.rb
@@ -48,7 +48,7 @@ describe "gemcutter's dependency API", :realworld => true do
gem "rack"
old_v, $VERBOSE = $VERBOSE, nil
- Bundler::Fetcher::API_TIMEOUT = 1
+ Bundler::Fetcher.api_timeout = 1
$VERBOSE = old_v
G
@@ -57,5 +57,4 @@ describe "gemcutter's dependency API", :realworld => true do
should_be_installed "rack 1.0.0"
end
end
-
end
diff --git a/spec/realworld/edgecases_spec.rb b/spec/realworld/edgecases_spec.rb
index 57c2252c74..fd5b607ba6 100644
--- a/spec/realworld/edgecases_spec.rb
+++ b/spec/realworld/edgecases_spec.rb
@@ -10,7 +10,7 @@ describe "real world edgecases", :realworld => true do
expect(err).to eq("")
end
- # https://github.com/carlhuda/bundler/issues/1202
+ # https://github.com/bundler/bundler/issues/1202
it "bundle cache works with rubygems 1.3.7 and pre gems", :ruby => "1.8" do
install_gemfile <<-G
source :rubygems
@@ -21,7 +21,7 @@ describe "real world edgecases", :realworld => true do
expect(out).not_to include("Removing outdated .gem files from vendor/cache")
end
- # https://github.com/carlhuda/bundler/issues/1486
+ # https://github.com/bundler/bundler/issues/1486
# this is a hash collision that only manifests on 1.8.7
it "finds the correct child versions", :ruby => "1.8" do
install_gemfile <<-G
@@ -35,7 +35,7 @@ describe "real world edgecases", :realworld => true do
expect(out).to include("activemodel (3.0.5)")
end
- # https://github.com/carlhuda/bundler/issues/1500
+ # https://github.com/bundler/bundler/issues/1500
it "does not fail install because of gem plugins" do
realworld_system_gems("open_gem --version 1.4.2", "rake --version 0.9.2")
gemfile <<-G
diff --git a/spec/realworld/parallel_install_spec.rb b/spec/realworld/parallel_install_spec.rb
new file mode 100644
index 0000000000..656d029bb3
--- /dev/null
+++ b/spec/realworld/parallel_install_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe "installing dependencies parallely", :realworld => true do
+ it "installs gems parallely" do
+ gemfile <<-G
+ source "https://rubygems.org"
+
+ gem 'activesupport', '~> 3.2.13'
+ gem 'faker', '~> 1.1.2'
+ G
+
+ bundle :install, :jobs => 4
+ bundle "show activesupport"
+ expect(out).to match(/activesupport/)
+
+ bundle "show faker"
+ expect(out).to match(/faker/)
+ end
+end
diff --git a/spec/resolver/basic_spec.rb b/spec/resolver/basic_spec.rb
index 6371dc518b..22dc2f0fbe 100644
--- a/spec/resolver/basic_spec.rb
+++ b/spec/resolver/basic_spec.rb
@@ -18,9 +18,20 @@ describe "Resolving" do
should_resolve_as %w(actionpack-2.3.5 activesupport-2.3.5 rack-1.0)
end
- it "resolve a conflicting index" do
+ it "resolves a conflicting index" do
@index = a_conflict_index
dep "my_app"
should_resolve_as %w(activemodel-3.2.11 builder-3.0.4 grape-0.2.6 my_app-1.0.0)
end
+
+ it "should throw error in case of circular dependencies" do
+ @index = a_circular_index
+ dep "circular_app"
+
+ got = resolve
+ expect {
+ got = got.map { |s| s.full_name }.sort
+ }.to raise_error(Bundler::CyclicDependencyError, /please remove either gem 'foo' or gem 'bar'/i)
+ end
+
end
diff --git a/spec/runtime/require_spec.rb b/spec/runtime/require_spec.rb
index db72ff70f0..e1c93897a5 100644
--- a/spec/runtime/require_spec.rb
+++ b/spec/runtime/require_spec.rb
@@ -33,6 +33,10 @@ describe "Bundler.require" do
s.write "lib/seven.rb", "puts 'seven'"
end
+ build_lib "eight", "1.0.0" do |s|
+ s.write "lib/eight.rb", "puts 'eight'"
+ end
+
gemfile <<-G
path "#{lib_path}"
gem "one", :group => :bar, :require => %w(baz qux)
@@ -42,6 +46,7 @@ describe "Bundler.require" do
gem "five"
gem "six", :group => "string"
gem "seven", :group => :not
+ gem "eight", :require => true, :group => :require_true
G
end
@@ -69,6 +74,10 @@ describe "Bundler.require" do
# required in resolver order instead of gemfile order
run("Bundler.require(:not)")
expect(out.split("\n").sort).to eq(['seven', 'three'])
+
+ # test require: true
+ run "Bundler.require(:require_true)"
+ expect(out).to eq("eight")
end
it "allows requiring gems with non standard names explicitly" do
diff --git a/spec/runtime/with_clean_env_spec.rb b/spec/runtime/with_clean_env_spec.rb
index 7a77db0b4d..193a6e5b0c 100644
--- a/spec/runtime/with_clean_env_spec.rb
+++ b/spec/runtime/with_clean_env_spec.rb
@@ -88,5 +88,4 @@ describe "Bundler.with_env helpers" do
expect($?.exitstatus).to eq(42)
end
end
-
end
diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb
index 0fd74bde73..27654b7650 100644
--- a/spec/support/helpers.rb
+++ b/spec/support/helpers.rb
@@ -94,10 +94,6 @@ module Spec
requires_str = requires.map{|r| "-r#{r}"}.join(" ")
env = (options.delete(:env) || {}).map{|k,v| "#{k}='#{v}' "}.join
- args = options.map do |k,v|
- v == true ? " --#{k}" : " --#{k} #{v}" if v
- end.join
-
cmd = "#{env}#{Gem.ruby} -I#{lib} #{requires_str} #{bundle_bin}"
if exitstatus
diff --git a/spec/support/indexes.rb b/spec/support/indexes.rb
index 2ca67a637a..418f7f32db 100644
--- a/spec/support/indexes.rb
+++ b/spec/support/indexes.rb
@@ -130,5 +130,23 @@ module Spec
end
end
end
+
+ def a_circular_index
+ build_index do
+ gem "rack", "1.0.1"
+ gem("foo", '0.2.6') do
+ dep "bar", ">= 0"
+ end
+
+ gem("bar", "1.0.0") do
+ dep "foo", ">= 0"
+ end
+
+ gem("circular_app", '1.0.0') do
+ dep "foo", ">= 0"
+ dep "bar", ">= 0"
+ end
+ end
+ end
end
end
diff --git a/spec/support/streams.rb b/spec/support/streams.rb
new file mode 100644
index 0000000000..610e99986a
--- /dev/null
+++ b/spec/support/streams.rb
@@ -0,0 +1,13 @@
+require 'stringio'
+
+def capture(*streams)
+ streams.map! { |stream| stream.to_s }
+ begin
+ result = StringIO.new
+ streams.each { |stream| eval "$#{stream} = result" }
+ yield
+ ensure
+ streams.each { |stream| eval("$#{stream} = #{stream.upcase}") }
+ end
+ result.string
+end
diff --git a/spec/update/gems_spec.rb b/spec/update/gems_spec.rb
index 3616fcdd89..26a367d027 100644
--- a/spec/update/gems_spec.rb
+++ b/spec/update/gems_spec.rb
@@ -34,12 +34,12 @@ describe "bundle update" do
end
describe "--quiet argument" do
- it 'shows UI messages without --quiet argument' do
+ it "shows UI messages without --quiet argument" do
bundle "update"
expect(out).to include("Fetching source")
end
- it 'does not show UI messages with --quiet argument' do
+ it "does not show UI messages with --quiet argument" do
bundle "update --quiet"
expect(out).not_to include("Fetching source")
end
@@ -56,7 +56,7 @@ describe "bundle update" do
end
end
- describe "with a unknown dependency" do
+ describe "with an unknown dependency" do
it "should inform the user" do
bundle "update halting-problem-solver", :expect_err=>true
expect(out).to include "Could not find gem 'halting-problem-solver'"
diff --git a/spec/update/git_spec.rb b/spec/update/git_spec.rb
index 53d6e5b275..cba5a1f72b 100644
--- a/spec/update/git_spec.rb
+++ b/spec/update/git_spec.rb
@@ -137,7 +137,7 @@ describe "bundle update" do
end
end
- it "it unlocks the source when submodules is added to a git source" do
+ it "it unlocks the source when submodules are added to a git source" do
install_gemfile <<-G
git "#{lib_path('has_submodule-1.0')}" do
gem "has_submodule"
@@ -157,7 +157,7 @@ describe "bundle update" do
expect(out).to eq('GIT')
end
- it "it unlocks the source when submodules is removed from git source" do
+ it "it unlocks the source when submodules are removed from git source" do
pending "This would require actually removing the submodule from the clone"
install_gemfile <<-G
git "#{lib_path('has_submodule-1.0')}", :submodules => true do
diff --git a/spec/update/source_spec.rb b/spec/update/source_spec.rb
index 6134c7700c..22f3225428 100644
--- a/spec/update/source_spec.rb
+++ b/spec/update/source_spec.rb
@@ -47,5 +47,4 @@ describe "bundle update" do
should_be_installed "rack 1.0"
end
end
-
end