From 16223dcfbc0176ba6a1e855890fa36172ec8b9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 25 Mar 2019 14:02:54 +0100 Subject: Bump thor to 0.20.3 --- lib/bundler/vendor/thor/lib/thor/actions.rb | 18 +++-- .../thor/lib/thor/actions/file_manipulation.rb | 13 +++- lib/bundler/vendor/thor/lib/thor/base.rb | 7 +- lib/bundler/vendor/thor/lib/thor/error.rb | 82 ++++++++++++++++++++++ lib/bundler/vendor/thor/lib/thor/group.rb | 4 +- lib/bundler/vendor/thor/lib/thor/parser/options.rb | 9 ++- lib/bundler/vendor/thor/lib/thor/runner.rb | 4 +- lib/bundler/vendor/thor/lib/thor/shell.rb | 2 +- lib/bundler/vendor/thor/lib/thor/shell/basic.rb | 59 ++++++++++++++-- lib/bundler/vendor/thor/lib/thor/version.rb | 2 +- 10 files changed, 175 insertions(+), 25 deletions(-) diff --git a/lib/bundler/vendor/thor/lib/thor/actions.rb b/lib/bundler/vendor/thor/lib/thor/actions.rb index e6698572a9..b06feac2a0 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions.rb @@ -113,8 +113,10 @@ class Bundler::Thor # the script started). # def relative_to_original_destination_root(path, remove_dot = true) - path = path.dup - if path.gsub!(@destination_stack[0], ".") + root = @destination_stack[0] + if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size]) + path = path.dup + path[0...root.size] = '.' remove_dot ? (path[2..-1] || "") : path else path @@ -217,6 +219,7 @@ class Bundler::Thor shell.padding += 1 if verbose contents = if is_uri + require "open-uri" open(path, "Accept" => "application/x-thor-template", &:read) else open(path, &:read) @@ -252,9 +255,16 @@ class Bundler::Thor say_status :run, desc, config.fetch(:verbose, true) - unless options[:pretend] - config[:capture] ? `#{command}` : system(command.to_s) + return if options[:pretend] + + result = config[:capture] ? `#{command}` : system(command.to_s) + + if config[:abort_on_failure] + success = config[:capture] ? $?.success? : result + abort unless success end + + result end # Executes a ruby script (taking into account WIN32 platform quirks). diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb index 4c83bebc86..cc29db05a8 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -60,6 +60,9 @@ class Bundler::Thor # destination. If a block is given instead of destination, the content of # the url is yielded and used as location. # + # +get+ relies on open-uri, so passing application user input would provide + # a command injection attack vector. + # # ==== Parameters # source:: the address of the given content. # destination:: the relative path to the destination root. @@ -117,7 +120,13 @@ class Bundler::Thor context = config.delete(:context) || instance_eval("binding") create_file destination, nil, config do - content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").tap do |erb| + match = ERB.version.match(/(\d+\.\d+\.\d+)/) + capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+ + CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer") + else + CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer") + end + content = capturable_erb.tap do |erb| erb.filename = source end.result(context) content = yield(content) if block @@ -301,7 +310,7 @@ class Bundler::Thor def comment_lines(path, flag, *args) flag = flag.respond_to?(:source) ? flag.source : flag - gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args) + gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args) end # Removes a file at the given location. diff --git a/lib/bundler/vendor/thor/lib/thor/base.rb b/lib/bundler/vendor/thor/lib/thor/base.rb index 7e41dc226b..e79d03d087 100644 --- a/lib/bundler/vendor/thor/lib/thor/base.rb +++ b/lib/bundler/vendor/thor/lib/thor/base.rb @@ -466,13 +466,13 @@ class Bundler::Thor dispatch(nil, given_args.dup, nil, config) rescue Bundler::Thor::Error => e config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message) - exit(1) if exit_on_failure? + exit(false) if exit_on_failure? rescue Errno::EPIPE # 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) + exit(true) end # Allows to use private methods from parent in child classes as commands. @@ -493,8 +493,7 @@ class Bundler::Thor alias_method :public_task, :public_command def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc: - raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace - raise UndefinedCommandError, "Could not find command #{command.inspect}." + raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace)) end alias_method :handle_no_task_error, :handle_no_command_error diff --git a/lib/bundler/vendor/thor/lib/thor/error.rb b/lib/bundler/vendor/thor/lib/thor/error.rb index 2f816081f3..16c68294e4 100644 --- a/lib/bundler/vendor/thor/lib/thor/error.rb +++ b/lib/bundler/vendor/thor/lib/thor/error.rb @@ -1,4 +1,23 @@ class Bundler::Thor + Correctable = + begin + require 'did_you_mean' + + # In order to support versions of Ruby that don't have keyword + # arguments, we need our own spell checker class that doesn't take key + # words. Even though this code wouldn't be hit because of the check + # above, it's still necessary because the interpreter would otherwise be + # unable to parse the file. + class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc: + def initialize(dictionary) + @dictionary = dictionary + end + end + + DidYouMean::Correctable + rescue LoadError, NameError + end + # Bundler::Thor::Error is raised when it's caused by wrong usage of thor classes. Those # errors have their backtrace suppressed and are nicely shown to the user. # @@ -10,6 +29,35 @@ class Bundler::Thor # Raised when a command was not found. class UndefinedCommandError < Error + class SpellChecker + attr_reader :error + + def initialize(error) + @error = error + end + + def corrections + @corrections ||= spell_checker.correct(error.command).map(&:inspect) + end + + def spell_checker + NoKwargSpellChecker.new(error.all_commands) + end + end + + attr_reader :command, :all_commands + + def initialize(command, all_commands, namespace) + @command = command + @all_commands = all_commands + + message = "Could not find command #{command.inspect}" + message = namespace ? "#{message} in #{namespace.inspect} namespace." : "#{message}." + + super(message) + end + + prepend Correctable if Correctable end UndefinedTaskError = UndefinedCommandError @@ -22,6 +70,33 @@ class Bundler::Thor end class UnknownArgumentError < Error + class SpellChecker + attr_reader :error + + def initialize(error) + @error = error + end + + def corrections + @corrections ||= + error.unknown.flat_map { |unknown| spell_checker.correct(unknown) }.uniq.map(&:inspect) + end + + def spell_checker + @spell_checker ||= NoKwargSpellChecker.new(error.switches) + end + end + + attr_reader :switches, :unknown + + def initialize(switches, unknown) + @switches = switches + @unknown = unknown + + super("Unknown switches #{unknown.map(&:inspect).join(', ')}") + end + + prepend Correctable if Correctable end class RequiredArgumentMissingError < InvocationError @@ -29,4 +104,11 @@ class Bundler::Thor class MalformattedArgumentError < InvocationError end + + if Correctable + DidYouMean::SPELL_CHECKERS.merge!( + 'Bundler::Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker, + 'Bundler::Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker + ) + end end diff --git a/lib/bundler/vendor/thor/lib/thor/group.rb b/lib/bundler/vendor/thor/lib/thor/group.rb index 05ddc10cd3..30db46529e 100644 --- a/lib/bundler/vendor/thor/lib/thor/group.rb +++ b/lib/bundler/vendor/thor/lib/thor/group.rb @@ -61,7 +61,7 @@ class Bundler::Thor::Group invocations[name] = false invocation_blocks[name] = block if block_given? - class_eval <<-METHOD, __FILE__, __LINE__ + class_eval <<-METHOD, __FILE__, __LINE__ + 1 def _invoke_#{name.to_s.gsub(/\W/, '_')} klass, command = self.class.prepare_for_invocation(nil, #{name.inspect}) @@ -120,7 +120,7 @@ class Bundler::Thor::Group invocations[name] = true invocation_blocks[name] = block if block_given? - class_eval <<-METHOD, __FILE__, __LINE__ + class_eval <<-METHOD, __FILE__, __LINE__ + 1 def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} return unless options[#{name.inspect}] diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb index 70f6366842..179f4fa015 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -44,6 +44,7 @@ class Bundler::Thor @shorts = {} @switches = {} @extra = [] + @stopped_parsing_after_extra_index = nil options.each do |option| @switches[option.switch_name] = option @@ -66,6 +67,7 @@ class Bundler::Thor if result == OPTS_END shift @parsing_options = false + @stopped_parsing_after_extra_index ||= @extra.size super else result @@ -99,6 +101,7 @@ class Bundler::Thor elsif @stop_on_unknown @parsing_options = false @extra << shifted + @stopped_parsing_after_extra_index ||= @extra.size @extra << shift while peek break elsif match @@ -120,9 +123,11 @@ class Bundler::Thor end def check_unknown! + to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra + # an unknown option starts with - or -- and has no more --'s afterward. - unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ } - raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty? + unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ } + raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty? end protected diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index b110b8d478..65ae422d7f 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -3,7 +3,7 @@ require "bundler/vendor/thor/lib/thor/group" require "bundler/vendor/thor/lib/thor/core_ext/io_binary_read" require "yaml" -require "digest" +require "digest/md5" require "pathname" class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength @@ -90,7 +90,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng end thor_yaml[as] = { - :filename => Digest(:MD5).hexdigest(name + as), + :filename => Digest::MD5.hexdigest(name + as), :location => location, :namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base) } diff --git a/lib/bundler/vendor/thor/lib/thor/shell.rb b/lib/bundler/vendor/thor/lib/thor/shell.rb index e945549324..a68cdf8a98 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell.rb @@ -55,7 +55,7 @@ class Bundler::Thor # Common methods that are delegated to the shell. SHELL_DELEGATED_METHODS.each do |method| - module_eval <<-METHOD, __FILE__, __LINE__ + module_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(*args,&block) shell.#{method}(*args,&block) end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index 5162390efd..52648fee8f 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -1,6 +1,8 @@ class Bundler::Thor module Shell class Basic + DEFAULT_TERMINAL_WIDTH = 80 + attr_accessor :base attr_reader :padding @@ -45,6 +47,10 @@ class Bundler::Thor # Asks something to the user and receives a response. # + # If a default value is specified it will be presented to the user + # and allows them to select that value with an empty response. This + # option is ignored when limited answers are supplied. + # # If asked to limit the correct responses, you can pass in an # array of acceptable answers. If one of those is not supplied, # they will be shown a message stating that one of those answers @@ -61,6 +67,8 @@ class Bundler::Thor # ==== Example # ask("What is your name?") # + # ask("What is the planet furthest from the sun?", :default => "Pluto") + # # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"]) # # ask("What is your password?", :echo => false) @@ -222,8 +230,20 @@ class Bundler::Thor paras = message.split("\n\n") paras.map! do |unwrapped| - unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") } - end + counter = 0 + unwrapped.split(" ").inject do |memo, word| + word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n") + counter = 0 if word.include? "\n" + if (counter + word.length + 1) < width + memo = "#{memo} #{word}" + counter += (word.length + 1) + else + memo = "#{memo}\n#{word}" + counter = word.length + end + memo + end + end.compact! paras.each do |para| para.split("\n").each do |line| @@ -239,11 +259,11 @@ class Bundler::Thor # # ==== Parameters # destination:: the destination file to solve conflicts - # block:: an optional block that returns the value to be used in diff + # block:: an optional block that returns the value to be used in diff and merge # def file_collision(destination) return true if @always_force - options = block_given? ? "[Ynaqdh]" : "[Ynaqh]" + options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]" loop do answer = ask( @@ -267,6 +287,13 @@ class Bundler::Thor when is?(:diff) show_diff(destination, yield) if block_given? say "Retrying..." + when is?(:merge) + if block_given? && !merge_tool.empty? + merge(destination, yield) + return nil + end + + say "Please specify merge tool to `THOR_MERGE` env." else say file_collision_help end @@ -279,11 +306,11 @@ class Bundler::Thor result = if ENV["THOR_COLUMNS"] ENV["THOR_COLUMNS"].to_i else - unix? ? dynamic_width : 80 + unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH end - result < 10 ? 80 : result + result < 10 ? DEFAULT_TERMINAL_WIDTH : result rescue - 80 + DEFAULT_TERMINAL_WIDTH end # Called if something goes wrong during the execution. This is used by Bundler::Thor @@ -344,6 +371,7 @@ class Bundler::Thor q - quit, abort d - diff, show the differences between the old and the new h - help, show this help + m - merge, run merge tool HELP end @@ -432,6 +460,23 @@ class Bundler::Thor end correct_answer end + + def merge(destination, content) #:nodoc: + require "tempfile" + Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp| + temp.write content + temp.rewind + system %(#{merge_tool} "#{temp.path}" "#{destination}") + end + end + + def merge_tool #:nodoc: + @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool + end + + def git_merge_tool #:nodoc: + `git config merge.tool`.rstrip rescue "" + end end end end diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb index df8f18821a..98f2b79081 100644 --- a/lib/bundler/vendor/thor/lib/thor/version.rb +++ b/lib/bundler/vendor/thor/lib/thor/version.rb @@ -1,3 +1,3 @@ class Bundler::Thor - VERSION = "0.20.0" + VERSION = "0.20.3" end -- cgit v1.2.1