Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #2145 from sol/do-not-swallow-verbose-on-exec

Do not swallow --verbose on `bundle exec` (fixes #2102)

Conflicts:
	lib/bundler/vendor/thor/base.rb
	spec/install/gems/dependency_api_spec.rb
  • Loading branch information...
commit efc387cb726f3563244a880401b7b7f0c64fad07 1 parent 5499771
@indirect indirect authored
View
6 CHANGELOG.md
@@ -1,3 +1,9 @@
+## next
+
+Bugfixes:
+
+ - Do not swallow --verbose on `bundle exec` (#2102)
+
## 1.2.2 (Nov 14, 2012)
Bugfixes:
View
2  lib/bundler/cli.rb
@@ -17,6 +17,8 @@ def initialize(*)
check_unknown_options!(:except => [:config, :exec])
+ stop_on_unknown_option! :exec
+
default_task :install
class_option "no-color", :type => :boolean, :banner => "Disable colorization in output"
class_option "verbose", :type => :boolean, :banner => "Enable verbose output mode", :aliases => "-V"
View
75 lib/bundler/vendor/thor.rb
@@ -1,3 +1,4 @@
+require 'set'
require 'thor/base'
class Thor
@@ -210,7 +211,7 @@ def subcommand(subcommand, subcommand_class)
define_method(subcommand) do |*args|
args, opts = Thor::Arguments.split(args)
- invoke subcommand_class, args, opts
+ invoke subcommand_class, args, opts, :invoked_via_subcommand => true
end
end
@@ -251,15 +252,83 @@ def check_unknown_options?(config) #:nodoc:
end
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
+ # 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
+ # an external command. A user may want to pass arbitrary options and
+ # arguments to that command. The task itself also accepts some options,
+ # which should be handled by Thor.
+ #
+ # class_option "verbose", :type => :boolean
+ # stop_on_unknown_option! :exec
+ # check_unknown_options! :except => :exec
+ #
+ # desc "exec", "Run a shell command"
+ # def exec(*args)
+ # puts "diagnostic output" if options[:verbose]
+ # Kernel.exec(*args)
+ # end
+ #
+ # Here +exec+ can be called with +--verbose+ to get diagnostic output,
+ # e.g.:
+ #
+ # $ thor exec --verbose echo foo
+ # diagnostic output
+ # foo
+ #
+ # But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
+ #
+ # $ thor exec echo --verbose foo
+ # --verbose foo
+ #
+ # ==== Parameters
+ # Symbol ...:: A list of tasks that should be affected.
+ def stop_on_unknown_option!(*task_names)
+ @stop_on_unknown_option ||= Set.new
+ @stop_on_unknown_option.merge(task_names)
+ end
+
+ def stop_on_unknown_option?(task) #:nodoc:
+ !!@stop_on_unknown_option && @stop_on_unknown_option.include?(task.name.to_sym)
+ end
+
protected
# The method responsible for dispatching given the args.
def dispatch(meth, given_args, given_opts, config) #:nodoc:
- meth ||= retrieve_task_name(given_args)
- task = all_tasks[normalize_task_name(meth)]
+ # 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
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)
View
1  lib/bundler/vendor/thor/actions/directory.rb
@@ -38,6 +38,7 @@ module Actions
# destination<String>:: the relative path to the destination root.
# 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.
#
# ==== Examples
#
View
8 lib/bundler/vendor/thor/actions/file_manipulation.rb
@@ -10,7 +10,9 @@ module Actions
# ==== Parameters
# source<String>:: the relative path to the source root.
# destination<String>:: the relative path to the destination root.
- # config<Hash>:: give :verbose => false to not log the status.
+ # config<Hash>:: give :verbose => false to not log the status, and
+ # :mode => :preserve, to preserve the file mode from the source.
+
#
# ==== Examples
#
@@ -28,6 +30,10 @@ def copy_file(source, *args, &block)
content = block.call(content) if block
content
end
+ if config[:mode] == :preserve
+ mode = File.stat(source).mode
+ chmod(destination, mode, config)
+ end
end
# Links the file from the relative source to the relative destination. If
View
13 lib/bundler/vendor/thor/base.rb
@@ -10,6 +10,7 @@
class Thor
autoload :Actions, 'thor/actions'
autoload :RakeCompat, 'thor/rake_compat'
+ autoload :Group, 'thor/group'
# Shortcuts for help.
HELP_MAPPINGS = %w(-h -? --help -D)
@@ -58,7 +59,8 @@ def initialize(args=[], options={}, config={})
# 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.
- opts = Thor::Options.new(parse_options, hash_options)
+ stop_on_unknown = self.class.stop_on_unknown_option? config[:current_task]
+ opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown)
self.options = opts.parse(array_options)
# If unknown options are disallowed, make sure that none of the
@@ -74,7 +76,7 @@ def initialize(args=[], options={}, config={})
to_parse += opts.remaining unless self.class.strict_args_position?(config)
thor_args = Thor::Arguments.new(self.class.arguments)
- thor_args.parse(to_parse).each { |k,v| send("#{k}=", v) }
+ thor_args.parse(to_parse).each { |k,v| __send__("#{k}=", v) }
@args = thor_args.remaining
end
@@ -142,6 +144,13 @@ def check_unknown_options?(config) #:nodoc:
!!check_unknown_options
end
+ # 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:
+ false
+ end
+
# If you want only strict string args (useful when cascading thor classes),
# call strict_args_position! This is disabled by default to allow dynamic
# invocations.
View
5 lib/bundler/vendor/thor/core_ext/hash_with_indifferent_access.rb
@@ -45,6 +45,11 @@ def merge!(other)
self
end
+ # Convert to a Hash with String keys.
+ def to_hash
+ Hash.new(default).merge!(self)
+ end
+
protected
def convert_key(key)
View
86 lib/bundler/vendor/thor/parser/options.rb
@@ -5,6 +5,7 @@ class Options < Arguments #:nodoc:
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
+ OPTS_END = '--'.freeze
# Receives a hash and makes it switches.
def self.to_switches(options)
@@ -25,7 +26,11 @@ def self.to_switches(options)
end
# Takes a hash of Thor::Option and a hash with defaults.
- def initialize(hash_options={}, defaults={})
+ #
+ # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
+ # an unknown option or a regular argument.
+ def initialize(hash_options={}, defaults={}, stop_on_unknown=false)
+ @stop_on_unknown = stop_on_unknown
options = hash_options.values
super(options)
@@ -50,33 +55,55 @@ def remaining
@extra
end
+ def peek
+ return super unless @parsing_options
+
+ result = super
+ if result == OPTS_END
+ shift
+ @parsing_options = false
+ super
+ else
+ result
+ end
+ end
+
def parse(args)
@pile = args.dup
+ @parsing_options = true
while peek
- match, is_switch = current_is_switch?
- shifted = shift
-
- 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
+ if parsing_options?
+ match, is_switch = current_is_switch?
+ shifted = shift
+
+ 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
+ end
+
+ switch = normalize_switch(switch)
+ option = switch_option(switch)
+ @assigns[option.human_name] = parse_peek(switch, option)
+ elsif @stop_on_unknown
+ @extra << shifted
+ @extra << shift while peek
+ break
+ elsif match
+ @extra << shifted
+ @extra << shift while peek && peek !~ /^-/
+ else
+ @extra << shifted
end
-
- switch = normalize_switch(switch)
- option = switch_option(switch)
- @assigns[option.human_name] = parse_peek(switch, option)
- elsif match
- @extra << shifted
- @extra << shift while peek && peek !~ /^-/
else
- @extra << shifted
+ @extra << shift
end
end
@@ -95,8 +122,10 @@ def check_unknown!
protected
- # Returns true if the current value in peek is a registered switch.
+ # Check if the current value in peek is a registered switch.
#
+ # Two booleans are returned. The first is true if the current value
+ # starts with a hyphen; the second is true if it is a registered switch.
def current_is_switch?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
@@ -117,6 +146,10 @@ def current_is_switch_formatted?
end
end
+ def current_is_value?
+ peek && (!parsing_options? || super)
+ end
+
def switch?(arg)
switch_option(normalize_switch(arg))
end
@@ -135,6 +168,11 @@ def normalize_switch(arg)
(@shorts[arg] || arg).tr('_', '-')
end
+ def parsing_options?
+ peek
+ @parsing_options
+ end
+
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
#
def parse_boolean(switch)
@@ -156,7 +194,7 @@ def parse_boolean(switch)
# Parse the value at the peek analyzing if it requires an input or not.
#
def parse_peek(switch, option)
- if current_is_switch_formatted? || last?
+ if parsing_options? && (current_is_switch_formatted? || last?)
if option.boolean?
# No problem for boolean types
elsif no_or_skip?(switch)
View
2  lib/bundler/vendor/thor/runner.rb
@@ -25,7 +25,7 @@ def help(meth = nil)
end
# If a task is not found on Thor::Runner, method missing is invoked and
- # Thor::Runner is then responsable for finding the task in all classes.
+ # Thor::Runner is then responsible for finding the task in all classes.
#
def method_missing(meth, *args)
meth = meth.to_s
View
4 lib/bundler/vendor/thor/shell/basic.rb
@@ -58,7 +58,7 @@ def ask(statement, *args)
# ==== Example
# say("I know you knew that.")
#
- def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
+ def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)\Z/))
message = message.to_s
message = set_color(message, *color) if color
@@ -370,7 +370,7 @@ def as_unicode
def ask_simply(statement, color=nil)
say("#{statement} ", color)
- stdin.gets.strip
+ stdin.gets.tap{|text| text.strip! if text}
end
def ask_filtered(statement, answer_set, *args)
View
4 lib/bundler/vendor/thor/task.rb
@@ -24,9 +24,9 @@ def run(instance, args=[])
instance.class.handle_no_task_error(name)
elsif public_method?(instance)
arity = instance.method(name).arity
- instance.send(name, *args)
+ instance.__send__(name, *args)
elsif local_method?(instance, :method_missing)
- instance.send(:method_missing, name.to_sym, *args)
+ instance.__send__(:method_missing, name.to_sym, *args)
else
instance.class.handle_no_task_error(name)
end
View
24 spec/other/exec_spec.rb
@@ -39,6 +39,30 @@
expect(out).to eq("exec")
end
+ it "accepts --verbose" do
+ install_gemfile 'gem "rack"'
+ bundle "exec --verbose echo foobar"
+ expect(out).to eq("foobar")
+ end
+
+ it "passes --verbose to command if it is given after the command" do
+ install_gemfile 'gem "rack"'
+ bundle "exec echo --verbose"
+ expect(out).to eq("--verbose")
+ end
+
+ it "can run a command named --verbose" do
+ install_gemfile 'gem "rack"'
+ File.open("--verbose", 'w') do |f|
+ f.puts "#!/bin/sh"
+ f.puts "echo foobar"
+ end
+ File.chmod(0744, "--verbose")
+ ENV['PATH'] = "."
+ bundle "exec -- --verbose"
+ expect(out).to eq("foobar")
+ end
+
it "handles different versions in different bundles" do
build_repo2 do
build_gem "rack_two", "1.0.0" do |s|
Please sign in to comment.
Something went wrong with that request. Please try again.