diff options
Diffstat (limited to 'lib/bundler/vendor/thor/lib/thor')
29 files changed, 726 insertions, 404 deletions
diff --git a/lib/bundler/vendor/thor/lib/thor/actions.rb b/lib/bundler/vendor/thor/lib/thor/actions.rb index de9323b2db..ca58182691 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions.rb @@ -46,17 +46,17 @@ class Bundler::Thor # Add runtime options that help actions execution. # def add_runtime_options! - class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, - :desc => "Overwrite files that already exist" + class_option :force, type: :boolean, aliases: "-f", group: :runtime, + desc: "Overwrite files that already exist" - class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, - :desc => "Run but do not make any changes" + class_option :pretend, type: :boolean, aliases: "-p", group: :runtime, + desc: "Run but do not make any changes" - class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, - :desc => "Suppress status output" + class_option :quiet, type: :boolean, aliases: "-q", group: :runtime, + desc: "Suppress status output" - class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, - :desc => "Skip files that already exist" + class_option :skip, type: :boolean, aliases: "-s", group: :runtime, + desc: "Skip files that already exist" end end @@ -113,9 +113,9 @@ class Bundler::Thor # def relative_to_original_destination_root(path, remove_dot = true) root = @destination_stack[0] - if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size]) + if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ""].include?(path[root.size..root.size]) path = path.dup - path[0...root.size] = '.' + path[0...root.size] = "." remove_dot ? (path[2..-1] || "") : path else path @@ -161,6 +161,8 @@ class Bundler::Thor # to the block you provide. The path is set back to the previous path when # the method exits. # + # Returns the value yielded by the block. + # # ==== Parameters # dir<String>:: the directory to move to. # config<Hash>:: give :verbose => true to log and use padding. @@ -173,22 +175,24 @@ class Bundler::Thor shell.padding += 1 if verbose @destination_stack.push File.expand_path(dir, destination_root) - # If the directory doesnt exist and we're not pretending + # If the directory doesn't exist and we're not pretending if !File.exist?(destination_root) && !pretend require "fileutils" FileUtils.mkdir_p(destination_root) end + result = nil if pretend # In pretend mode, just yield down to the block - block.arity == 1 ? yield(destination_root) : yield + result = block.arity == 1 ? yield(destination_root) : yield else require "fileutils" - FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } + FileUtils.cd(destination_root) { result = block.arity == 1 ? yield(destination_root) : yield } end @destination_stack.pop shell.padding -= 1 if verbose + result end # Goes to the root and execute the given block. @@ -221,7 +225,7 @@ class Bundler::Thor require "open-uri" URI.open(path, "Accept" => "application/x-thor-template", &:read) else - open(path, &:read) + File.open(path, &:read) end instance_eval(contents, path) @@ -280,7 +284,7 @@ class Bundler::Thor # def run_ruby_script(command, config = {}) return unless behavior == :invoke - run command, config.merge(:with => Bundler::Thor::Util.ruby_command) + run command, config.merge(with: Bundler::Thor::Util.ruby_command) end # Run a thor command. A hash of options can be given and it's converted to @@ -311,7 +315,7 @@ class Bundler::Thor args.push Bundler::Thor::Options.to_switches(config) command = args.join(" ").strip - run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture + run command, with: :thor, verbose: verbose, pretend: pretend, capture: capture end protected @@ -319,7 +323,7 @@ class Bundler::Thor # Allow current root to be shared between invocations. # def _shared_configuration #:nodoc: - super.merge!(:destination_root => destination_root) + super.merge!(destination_root: destination_root) end def _cleanup_options_and_set(options, key) #:nodoc: diff --git a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb index 330fc08cae..6724835b01 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/create_file.rb @@ -43,7 +43,8 @@ class Bundler::Thor # Boolean:: true if it is identical, false otherwise. # def identical? - exists? && File.binread(destination) == render + # binread uses ASCII-8BIT, so to avoid false negatives, the string must use the same + exists? && File.binread(destination) == String.new(render).force_encoding("ASCII-8BIT") end # Holds the content to be added to the file. @@ -60,7 +61,7 @@ class Bundler::Thor invoke_with_conflict_check do require "fileutils" FileUtils.mkdir_p(File.dirname(destination)) - File.open(destination, "wb") { |f| f.write render } + File.open(destination, "wb", config[:perm]) { |f| f.write render } end given_destination end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb index d37327a139..2f9687c0a5 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/directory.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/directory.rb @@ -58,7 +58,7 @@ class Bundler::Thor def initialize(base, source, destination = nil, config = {}, &block) @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first) @block = block - super(base, destination, {:recursive => true}.merge(config)) + super(base, destination, {recursive: true}.merge(config)) end def invoke! diff --git a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb index 284d92c19a..c0bca78525 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/empty_directory.rb @@ -33,7 +33,7 @@ class Bundler::Thor # def initialize(base, destination, config = {}) @base = base - @config = {:verbose => true}.merge(config) + @config = {verbose: true}.merge(config) self.destination = destination end 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 90a8d2e847..80a0255996 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -66,12 +66,15 @@ class Bundler::Thor # ==== Parameters # source<String>:: the address of the given content. # 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 + # :http_headers => <Hash> to add headers to an http request. # # ==== Examples # # get "http://gist.github.com/103208", "doc/README" # + # get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"} + # # get "http://gist.github.com/103208" do |content| # content.split("\n").first # end @@ -82,10 +85,10 @@ class Bundler::Thor render = if source =~ %r{^https?\://} require "open-uri" - URI.send(:open, source) { |input| input.binmode.read } + URI.send(:open, source, config.fetch(:http_headers, {})) { |input| input.binmode.read } else source = File.expand_path(find_in_source_paths(source.to_s)) - open(source) { |input| input.binmode.read } + File.open(source) { |input| input.binmode.read } end destination ||= if block_given? @@ -120,12 +123,7 @@ class Bundler::Thor context = config.delete(:context) || instance_eval("binding") create_file destination, nil, config do - 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 + capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer") content = capturable_erb.tap do |erb| erb.filename = source end.result(context) @@ -210,9 +208,9 @@ class Bundler::Thor # # ==== Examples # - # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n" + # inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n" # - # inject_into_class "app/controllers/application_controller.rb", ApplicationController do + # inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do # " filter_parameter :password\n" # end # @@ -233,9 +231,9 @@ class Bundler::Thor # # ==== Examples # - # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n" + # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper", " def help; 'help'; end\n" # - # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do + # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do # " def help; 'help'; end\n" # end # @@ -331,7 +329,7 @@ class Bundler::Thor path = File.expand_path(path, destination_root) say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) - if !options[:pretend] && File.exist?(path) + if !options[:pretend] && (File.exist?(path) || File.symlink?(path)) require "fileutils" ::FileUtils.rm_rf(path) end diff --git a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb index 09ce0864f0..70526e615f 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb @@ -21,7 +21,7 @@ class Bundler::Thor # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") # end # - WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' } + WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"} def insert_into_file(destination, *args, &block) data = block_given? ? block : args.shift @@ -37,7 +37,7 @@ class Bundler::Thor attr_reader :replacement, :flag, :behavior def initialize(base, destination, data, config) - super(base, destination, {:verbose => true}.merge(config)) + super(base, destination, {verbose: true}.merge(config)) @behavior, @flag = if @config.key?(:after) [:after, @config.delete(:after)] @@ -59,6 +59,8 @@ class Bundler::Thor if exists? if replace!(/#{flag}/, content, config[:force]) say_status(:invoke) + elsif replacement_present? + say_status(:unchanged, color: :blue) else say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red) end @@ -96,6 +98,8 @@ class Bundler::Thor end elsif warning warning + elsif behavior == :unchanged + :unchanged else :subtract end @@ -103,15 +107,21 @@ class Bundler::Thor super(status, (color || config[:verbose])) end + def content + @content ||= File.read(destination) + end + + def replacement_present? + content.include?(replacement) + end + # Adds the content to the file. # def replace!(regexp, string, force) - return if pretend? - content = File.read(destination) - if force || !content.include?(replacement) + if force || !replacement_present? success = content.gsub!(regexp, string) - File.open(destination, "wb") { |file| file.write(content) } + File.open(destination, "wb") { |file| file.write(content) } unless pretend? success end end diff --git a/lib/bundler/vendor/thor/lib/thor/base.rb b/lib/bundler/vendor/thor/lib/thor/base.rb index 8487f9590a..b156899c1e 100644 --- a/lib/bundler/vendor/thor/lib/thor/base.rb +++ b/lib/bundler/vendor/thor/lib/thor/base.rb @@ -24,9 +24,9 @@ class Bundler::Thor class << self def deprecation_warning(message) #:nodoc: - unless ENV['THOR_SILENCE_DEPRECATION'] + unless ENV["THOR_SILENCE_DEPRECATION"] warn "Deprecation warning: #{message}\n" + - 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.' + "You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION." end end end @@ -60,6 +60,7 @@ class Bundler::Thor command_options = config.delete(:command_options) # hook for start parse_options = parse_options.merge(command_options) if command_options + if local_options.is_a?(Array) array_options = local_options hash_options = {} @@ -73,9 +74,24 @@ class Bundler::Thor # Let Bundler::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_command] - disable_required_check = self.class.disable_required_check? config[:current_command] - opts = Bundler::Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check) + current_command = config[:current_command] + stop_on_unknown = self.class.stop_on_unknown_option? current_command + + # Give a relation of options. + # After parsing, Bundler::Thor::Options check whether right relations are kept + relations = if current_command.nil? + {exclusive_option_names: [], at_least_one_option_names: []} + else + current_command.options_relation + end + + self.class.class_exclusive_option_names.map { |n| relations[:exclusive_option_names] << n } + self.class.class_at_least_one_option_names.map { |n| relations[:at_least_one_option_names] << n } + + disable_required_check = self.class.disable_required_check? current_command + + opts = Bundler::Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check, relations) + self.options = opts.parse(array_options) self.options = config[:class_options].merge(options) if config[:class_options] @@ -310,9 +326,92 @@ class Bundler::Thor # :hide:: -- If you want to hide this option from the help. # def class_option(name, options = {}) + unless [ Symbol, String ].any? { |klass| name.is_a?(klass) } + raise ArgumentError, "Expected a Symbol or String, got #{name.inspect}" + end build_option(name, options, class_options) end + # Adds and declares option group for exclusive options in the + # block and arguments. You can declare options as the outside of the block. + # + # ==== Parameters + # Array[Bundler::Thor::Option.name] + # + # ==== Examples + # + # class_exclusive do + # class_option :one + # class_option :two + # end + # + # Or + # + # class_option :one + # class_option :two + # class_exclusive :one, :two + # + # If you give "--one" and "--two" at the same time ExclusiveArgumentsError + # will be raised. + # + def class_exclusive(*args, &block) + register_options_relation_for(:class_options, + :class_exclusive_option_names, *args, &block) + end + + # Adds and declares option group for required at least one of options in the + # block and arguments. You can declare options as the outside of the block. + # + # ==== Examples + # + # class_at_least_one do + # class_option :one + # class_option :two + # end + # + # Or + # + # class_option :one + # class_option :two + # class_at_least_one :one, :two + # + # If you do not give "--one" and "--two" AtLeastOneRequiredArgumentError + # will be raised. + # + # You can use class_at_least_one and class_exclusive at the same time. + # + # class_exclusive do + # class_at_least_one do + # class_option :one + # class_option :two + # end + # end + # + # Then it is required either only one of "--one" or "--two". + # + def class_at_least_one(*args, &block) + register_options_relation_for(:class_options, + :class_at_least_one_option_names, *args, &block) + end + + # Returns this class exclusive options array set, looking up in the ancestors chain. + # + # ==== Returns + # Array[Array[Bundler::Thor::Option.name]] + # + def class_exclusive_option_names + @class_exclusive_option_names ||= from_superclass(:class_exclusive_option_names, []) + end + + # Returns this class at least one of required options array set, looking up in the ancestors chain. + # + # ==== Returns + # Array[Array[Bundler::Thor::Option.name]] + # + def class_at_least_one_option_names + @class_at_least_one_option_names ||= from_superclass(:class_at_least_one_option_names, []) + end + # Removes a previous defined argument. If :undefine is given, undefine # accessors as well. # @@ -506,7 +605,7 @@ class Bundler::Thor # def public_command(*names) names.each do |name| - class_eval "def #{name}(*); super end" + class_eval "def #{name}(*); super end", __FILE__, __LINE__ end end alias_method :public_task, :public_command @@ -558,20 +657,19 @@ class Bundler::Thor return if options.empty? list = [] - padding = options.map { |o| o.aliases.size }.max.to_i * 4 - + padding = options.map { |o| o.aliases_for_usage.size }.max.to_i options.each do |option| next if option.hide item = [option.usage(padding)] item.push(option.description ? "# #{option.description}" : "") list << item - list << ["", "# Default: #{option.default}"] if option.show_default? - list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum + list << ["", "# Default: #{option.print_default}"] if option.show_default? + list << ["", "# Possible values: #{option.enum_to_s}"] if option.enum end shell.say(group_name ? "#{group_name} options:" : "Options:") - shell.print_table(list, :indent => 2) + shell.print_table(list, indent: 2) shell.say "" end @@ -588,7 +686,7 @@ class Bundler::Thor # options<Hash>:: Described in both class_option and method_option. # scope<Hash>:: Options hash that is being built up def build_option(name, options, scope) #:nodoc: - scope[name] = Bundler::Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options)) + scope[name] = Bundler::Thor::Option.new(name, {check_default_type: check_default_type}.merge!(options)) end # Receives a hash of options, parse them and add to the scope. This is a @@ -610,7 +708,7 @@ class Bundler::Thor def find_and_refresh_command(name) #:nodoc: if commands[name.to_s] commands[name.to_s] - elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition + elsif command = all_commands[name.to_s] # rubocop:disable Lint/AssignmentInCondition commands[name.to_s] = command.clone else raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." @@ -618,7 +716,7 @@ class Bundler::Thor end alias_method :find_and_refresh_task, :find_and_refresh_command - # Everytime someone inherits from a Bundler::Thor class, register the klass + # Every time someone inherits from a Bundler::Thor class, register the klass # and file into baseclass. def inherited(klass) super(klass) @@ -694,6 +792,34 @@ class Bundler::Thor def dispatch(command, given_args, given_opts, config) #:nodoc: raise NotImplementedError end + + # Register a relation of options for target(method_option/class_option) + # by args and block. + def register_options_relation_for(target, relation, *args, &block) # :nodoc: + opt = args.pop if args.last.is_a? Hash + opt ||= {} + names = args.map{ |arg| arg.to_s } + names += built_option_names(target, opt, &block) if block_given? + command_scope_member(relation, opt) << names + end + + # Get target(method_options or class_options) options + # of before and after by block evaluation. + def built_option_names(target, opt = {}, &block) # :nodoc: + before = command_scope_member(target, opt).map{ |k,v| v.name } + instance_eval(&block) + after = command_scope_member(target, opt).map{ |k,v| v.name } + after - before + end + + # Get command scope member by name. + def command_scope_member(name, options = {}) # :nodoc: + if options[:for] + find_and_refresh_command(options[:for]).send(name) + else + send(name) + end + end end end end diff --git a/lib/bundler/vendor/thor/lib/thor/command.rb b/lib/bundler/vendor/thor/lib/thor/command.rb index 040c971c0c..68c8fffedb 100644 --- a/lib/bundler/vendor/thor/lib/thor/command.rb +++ b/lib/bundler/vendor/thor/lib/thor/command.rb @@ -1,14 +1,15 @@ class Bundler::Thor - class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name) + class Command < Struct.new(:name, :description, :long_description, :wrap_long_description, :usage, :options, :options_relation, :ancestor_name) FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ - def initialize(name, description, long_description, usage, options = nil) - super(name.to_s, description, long_description, usage, options || {}) + def initialize(name, description, long_description, wrap_long_description, usage, options = nil, options_relation = nil) + super(name.to_s, description, long_description, wrap_long_description, usage, options || {}, options_relation || {}) end def initialize_copy(other) #:nodoc: super(other) self.options = other.options.dup if other.options + self.options_relation = other.options_relation.dup if other.options_relation end def hidden? @@ -62,6 +63,14 @@ class Bundler::Thor end.join("\n") end + def method_exclusive_option_names #:nodoc: + self.options_relation[:exclusive_option_names] || [] + end + + def method_at_least_one_option_names #:nodoc: + self.options_relation[:at_least_one_option_names] || [] + end + protected # Add usage with required arguments @@ -127,7 +136,7 @@ class Bundler::Thor # A dynamic command that handles method missing scenarios. class DynamicCommand < Command def initialize(name, options = nil) - super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options) + super(name.to_s, "A dynamically-generated command", name.to_s, nil, name.to_s, options) end def run(instance, args = []) diff --git a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb index c167aa33b8..b16a98f782 100644 --- a/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/lib/bundler/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -28,10 +28,20 @@ class Bundler::Thor super(convert_key(key)) end + def except(*keys) + dup.tap do |hash| + keys.each { |key| hash.delete(convert_key(key)) } + end + end + def fetch(key, *args) super(convert_key(key), *args) end + def slice(*keys) + super(*keys.map{ |key| convert_key(key) }) + end + def key?(key) super(convert_key(key)) end diff --git a/lib/bundler/vendor/thor/lib/thor/error.rb b/lib/bundler/vendor/thor/lib/thor/error.rb index 7d57129b83..928646e501 100644 --- a/lib/bundler/vendor/thor/lib/thor/error.rb +++ b/lib/bundler/vendor/thor/lib/thor/error.rb @@ -1,18 +1,15 @@ class Bundler::Thor Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable) # rubocop:disable Naming/ConstantName - # 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 - end + Module.new do + def to_s + super + DidYouMean.formatter.message_for(corrections) + end + + def corrections + @corrections ||= self.class.const_get(:SpellChecker).new(self).corrections + end + end + 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. @@ -37,7 +34,7 @@ class Bundler::Thor end def spell_checker - NoKwargSpellChecker.new(error.all_commands) + DidYouMean::SpellChecker.new(dictionary: error.all_commands) end end @@ -79,7 +76,7 @@ class Bundler::Thor end def spell_checker - @spell_checker ||= NoKwargSpellChecker.new(error.switches) + @spell_checker ||= DidYouMean::SpellChecker.new(dictionary: error.switches) end end @@ -101,10 +98,9 @@ class Bundler::Thor class MalformattedArgumentError < InvocationError end - if Correctable - DidYouMean::SPELL_CHECKERS.merge!( - 'Bundler::Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker, - 'Bundler::Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker - ) + class ExclusiveArgumentError < InvocationError + end + + class AtLeastOneRequiredArgumentError < InvocationError end end diff --git a/lib/bundler/vendor/thor/lib/thor/group.rb b/lib/bundler/vendor/thor/lib/thor/group.rb index 7861d05345..7ea11e8f93 100644 --- a/lib/bundler/vendor/thor/lib/thor/group.rb +++ b/lib/bundler/vendor/thor/lib/thor/group.rb @@ -169,7 +169,7 @@ class Bundler::Thor::Group # options are added to group_options hash. Options that already exists # in base_options are not added twice. # - def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength + def get_options_from_invocations(group_options, base_options) #:nodoc: invocations.each do |name, from_option| value = if from_option option = class_options[name] diff --git a/lib/bundler/vendor/thor/lib/thor/invocation.rb b/lib/bundler/vendor/thor/lib/thor/invocation.rb index 248a466f8e..5ce74710ba 100644 --- a/lib/bundler/vendor/thor/lib/thor/invocation.rb +++ b/lib/bundler/vendor/thor/lib/thor/invocation.rb @@ -143,7 +143,7 @@ class Bundler::Thor # Configuration values that are shared between invocations. def _shared_configuration #:nodoc: - {:invocations => @_invocations} + {invocations: @_invocations} end # This method simply retrieves the class and command to be invoked. diff --git a/lib/bundler/vendor/thor/lib/thor/nested_context.rb b/lib/bundler/vendor/thor/lib/thor/nested_context.rb index fd36b9d43f..7d60cb1c12 100644 --- a/lib/bundler/vendor/thor/lib/thor/nested_context.rb +++ b/lib/bundler/vendor/thor/lib/thor/nested_context.rb @@ -13,10 +13,10 @@ class Bundler::Thor end def entered? - @depth > 0 + @depth.positive? end - private + private def push @depth += 1 diff --git a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb index dfe7398583..b9e94e4669 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb @@ -24,6 +24,17 @@ class Bundler::Thor validate! # Trigger specific validations end + def print_default + if @type == :array and @default.is_a?(Array) + @default.map { |x| + p = x.gsub('"','\\"') + "\"#{p}\"" + }.join(" ") + else + @default + end + end + def usage required? ? banner : "[#{banner}]" end @@ -41,11 +52,19 @@ class Bundler::Thor end end + def enum_to_s + if enum.respond_to? :join + enum.join(", ") + else + "#{enum.first}..#{enum.last}" + end + end + protected def validate! raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? - raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array) + raise ArgumentError, "An argument cannot have an enum other than an enumerable." if @enum && !@enum.is_a?(Enumerable) end def valid_type?(type) diff --git a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb index 3a5d82cf29..b6f9c9a37a 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/arguments.rb @@ -1,5 +1,5 @@ class Bundler::Thor - class Arguments #:nodoc: # rubocop:disable ClassLength + class Arguments #:nodoc: NUMERIC = /[-+]?(\d*\.\d+|\d+)/ # Receives an array of args and returns two arrays, one with arguments @@ -30,11 +30,7 @@ class Bundler::Thor arguments.each do |argument| if !argument.default.nil? - begin - @assigns[argument.human_name] = argument.default.dup - rescue TypeError # Compatibility shim for un-dup-able Fixnum in Ruby < 2.4 - @assigns[argument.human_name] = argument.default - end + @assigns[argument.human_name] = argument.default.dup elsif argument.required? @non_assigned_required << argument end @@ -121,8 +117,18 @@ class Bundler::Thor # def parse_array(name) return shift if peek.is_a?(Array) + array = [] - array << shift while current_is_value? + + while current_is_value? + value = shift + + if !value.empty? + validate_enum_value!(name, value, "Expected all values of '%s' to be one of %s; got %s") + end + + array << value + end array end @@ -138,11 +144,9 @@ class Bundler::Thor end value = $&.index(".") ? shift.to_f : shift.to_i - if @switches.is_a?(Hash) && switch = @switches[name] - if switch.enum && !switch.enum.include?(value) - raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" - end - end + + validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s") + value end @@ -156,15 +160,27 @@ class Bundler::Thor nil else value = shift - if @switches.is_a?(Hash) && switch = @switches[name] - if switch.enum && !switch.enum.include?(value) - raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" - end - end + + validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s") + value end end + # Raises an error if the switch is an enum and the values aren't included on it. + # + def validate_enum_value!(name, value, message) + return unless @switches.is_a?(Hash) + + switch = @switches[name] + + return unless switch + + if switch.enum && !switch.enum.include?(value) + raise MalformattedArgumentError, message % [name, switch.enum_to_s, value] + end + end + # Raises an error if @non_assigned_required array is not empty. # def check_requirement! diff --git a/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/lib/bundler/vendor/thor/lib/thor/parser/option.rb index 5a5af6f888..c6af4e1e87 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/option.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/option.rb @@ -11,7 +11,7 @@ class Bundler::Thor super @lazy_default = options[:lazy_default] @group = options[:group].to_s.capitalize if options[:group] - @aliases = Array(options[:aliases]) + @aliases = normalize_aliases(options[:aliases]) @hide = options[:hide] end @@ -58,7 +58,7 @@ class Bundler::Thor default = nil if VALID_TYPES.include?(value) value - elsif required = (value == :required) # rubocop:disable AssignmentInCondition + elsif required = (value == :required) # rubocop:disable Lint/AssignmentInCondition :string end when TrueClass, FalseClass @@ -69,7 +69,7 @@ class Bundler::Thor value.class.name.downcase.to_sym end - new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases) + new(name.to_s, required: required, type: type, default: default, aliases: aliases) end def switch_name @@ -90,13 +90,26 @@ class Bundler::Thor sample = "[#{sample}]".dup unless required? if boolean? - sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-") + sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.match(/\Ano[\-_]/) end + aliases_for_usage.ljust(padding) + sample + end + + def aliases_for_usage if aliases.empty? - (" " * padding) << sample + "" + else + "#{aliases.join(', ')}, " + end + end + + def show_default? + case default + when TrueClass, FalseClass + true else - "#{aliases.join(', ')}, #{sample}" + super end end @@ -138,8 +151,8 @@ class Bundler::Thor raise ArgumentError, err elsif @check_default_type == nil Bundler::Thor.deprecation_warning "#{err}.\n" + - 'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' + - ' or call `allow_incompatible_default_type!` in your code' + "This will be rejected in the future unless you explicitly pass the options `check_default_type: false`" + + " or call `allow_incompatible_default_type!` in your code" end end end @@ -155,5 +168,11 @@ class Bundler::Thor def dasherize(str) (str.length > 1 ? "--" : "-") + str.tr("_", "-") end + + private + + def normalize_aliases(aliases) + Array(aliases).map { |short| short.to_s.sub(/^(?!\-)/, "-") } + end end end diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb index 3a8927d09c..978e76b132 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -1,5 +1,5 @@ class Bundler::Thor - class Options < Arguments #:nodoc: # rubocop:disable ClassLength + class Options < Arguments #:nodoc: LONG_RE = /^(--\w+(?:-\w+)*)$/ SHORT_RE = /^(-[a-z])$/i EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i @@ -29,8 +29,10 @@ class Bundler::Thor # # 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, disable_required_check = false) + def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false, relations = {}) @stop_on_unknown = stop_on_unknown + @exclusives = (relations[:exclusive_option_names] || []).select{|array| !array.empty?} + @at_least_ones = (relations[:at_least_one_option_names] || []).select{|array| !array.empty?} @disable_required_check = disable_required_check options = hash_options.values super(options) @@ -45,12 +47,12 @@ class Bundler::Thor @switches = {} @extra = [] @stopped_parsing_after_extra_index = nil + @is_treated_as_value = false options.each do |option| @switches[option.switch_name] = option - option.aliases.each do |short| - name = short.to_s.sub(/^(?!\-)/, "-") + option.aliases.each do |name| @shorts[name] ||= option.switch_name end end @@ -74,8 +76,19 @@ class Bundler::Thor end end - def parse(args) # rubocop:disable MethodLength + def shift + @is_treated_as_value = false + super + end + + def unshift(arg, is_value: false) + @is_treated_as_value = is_value + super(arg) + end + + def parse(args) # rubocop:disable Metrics/MethodLength @pile = args.dup + @is_treated_as_value = false @parsing_options = true while peek @@ -88,7 +101,10 @@ class Bundler::Thor when SHORT_SQ_RE unshift($1.split("").map { |f| "-#{f}" }) next - when EQ_RE, SHORT_NUM + when EQ_RE + unshift($2, is_value: true) + switch = $1 + when SHORT_NUM unshift($2) switch = $1 when LONG_RE, SHORT_RE @@ -117,12 +133,38 @@ class Bundler::Thor end check_requirement! unless @disable_required_check + check_exclusive! + check_at_least_one! assigns = Bundler::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) assigns.freeze assigns end + def check_exclusive! + opts = @assigns.keys + # When option A and B are exclusive, if A and B are given at the same time, + # the diffrence of argument array size will decrease. + found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 } + if found + names = names_to_switch_names(found & opts).map{|n| "'#{n}'"} + class_name = self.class.name.split("::").last.downcase + fail ExclusiveArgumentError, "Found exclusive #{class_name} #{names.join(", ")}" + end + end + + def check_at_least_one! + opts = @assigns.keys + # When at least one is required of the options A and B, + # if the both options were not given, none? would be true. + found = @at_least_ones.find{ |one_reqs| one_reqs.none?{ |o| opts.include? o} } + if found + names = names_to_switch_names(found).map{|n| "'#{n}'"} + class_name = self.class.name.split("::").last.downcase + fail AtLeastOneRequiredArgumentError, "Not found at least one of required #{class_name} #{names.join(", ")}" + end + end + def check_unknown! to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra @@ -133,6 +175,17 @@ class Bundler::Thor protected + # Option names changes to swith name or human name + def names_to_switch_names(names = []) + @switches.map do |_, o| + if names.include? o.name + o.respond_to?(:switch_name) ? o.switch_name : o.human_name + else + nil + end + end.compact + end + def assign_result!(option, result) if option.repeatable && option.type == :hash (@assigns[option.human_name] ||= {}).merge!(result) @@ -148,6 +201,7 @@ class Bundler::Thor # 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? + return [false, false] if @is_treated_as_value case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM [true, switch?($1)] @@ -159,6 +213,7 @@ class Bundler::Thor end def current_is_switch_formatted? + return false if @is_treated_as_value case peek when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE true @@ -168,6 +223,7 @@ class Bundler::Thor end def current_is_value? + return true if @is_treated_as_value peek && (!parsing_options? || super) end @@ -176,7 +232,7 @@ class Bundler::Thor end def switch_option(arg) - if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition + if match = no_or_skip?(arg) # rubocop:disable Lint/AssignmentInCondition @switches[arg] || @switches["--#{match}"] else @switches[arg] diff --git a/lib/bundler/vendor/thor/lib/thor/rake_compat.rb b/lib/bundler/vendor/thor/lib/thor/rake_compat.rb index f8f86372cc..c6a4653fc1 100644 --- a/lib/bundler/vendor/thor/lib/thor/rake_compat.rb +++ b/lib/bundler/vendor/thor/lib/thor/rake_compat.rb @@ -41,7 +41,7 @@ instance_eval do def task(*) task = super - if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition non_namespaced_name = task.name.split(":").last description = non_namespaced_name @@ -59,7 +59,7 @@ instance_eval do end def namespace(name) - if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + if klass = Bundler::Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition const_name = Bundler::Thor::Util.camel_case(name.to_s).to_sym klass.const_set(const_name, Class.new(Bundler::Thor)) new_klass = klass.const_get(const_name) diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index 54c5525093..c7cc873131 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -2,12 +2,10 @@ require_relative "../thor" require_relative "group" require "yaml" -require "digest/md5" +require "digest/sha2" require "pathname" -class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLength - autoload :OpenURI, "open-uri" - +class Bundler::Thor::Runner < Bundler::Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version def self.banner(command, all = false, subcommand = false) @@ -25,7 +23,7 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng initialize_thorfiles(meth) klass, command = Bundler::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 => shell) + klass.start(["-h", command].compact, shell: shell) else super end @@ -40,30 +38,42 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng klass, command = Bundler::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 => shell) + klass.start(args, shell: shell) end desc "install NAME", "Install an optionally named Bundler::Thor file into your system commands" - method_options :as => :string, :relative => :boolean, :force => :boolean - def install(name) # rubocop:disable MethodLength + method_options as: :string, relative: :boolean, force: :boolean + def install(name) # rubocop:disable Metrics/MethodLength initialize_thorfiles - # If a directory name is provided as the argument, look for a 'main.thor' - # command in said directory. - begin - if File.directory?(File.expand_path(name)) - base = File.join(name, "main.thor") - package = :directory - contents = open(base, &:read) - else - base = name - package = :file - contents = open(name, &:read) + is_uri = name =~ %r{^https?\://} + + if is_uri + base = name + package = :file + require "open-uri" + begin + contents = URI.open(name, &:read) + rescue OpenURI::HTTPError + raise Error, "Error opening URI '#{name}'" + end + else + # If a directory name is provided as the argument, look for a 'main.thor' + # command in said directory. + begin + if File.directory?(File.expand_path(name)) + base = File.join(name, "main.thor") + package = :directory + contents = File.open(base, &:read) + else + base = name + package = :file + require "open-uri" + contents = URI.open(name, &:read) + end + rescue Errno::ENOENT + raise Error, "Error opening file '#{name}'" end - rescue OpenURI::HTTPError - raise Error, "Error opening URI '#{name}'" - rescue Errno::ENOENT - raise Error, "Error opening file '#{name}'" end say "Your Thorfile contains:" @@ -84,16 +94,16 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng as = basename if as.empty? end - location = if options[:relative] || name =~ %r{^https?://} + location = if options[:relative] || is_uri name else File.expand_path(name) end thor_yaml[as] = { - :filename => Digest::MD5.hexdigest(name + as), - :location => location, - :namespaces => Bundler::Thor::Util.namespaces_in_content(contents, base) + filename: Digest::SHA256.hexdigest(name + as), + location: location, + namespaces: Bundler::Thor::Util.namespaces_in_content(contents, base) } save_yaml(thor_yaml) @@ -154,14 +164,14 @@ class Bundler::Thor::Runner < Bundler::Thor #:nodoc: # rubocop:disable ClassLeng end desc "installed", "List the installed Bundler::Thor modules and commands" - method_options :internal => :boolean + method_options internal: :boolean def installed initialize_thorfiles(nil, true) display_klasses(true, options["internal"]) end desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)" - method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean + method_options substring: :boolean, group: :string, all: :boolean, debug: :boolean def list(search = "") initialize_thorfiles @@ -303,7 +313,7 @@ private say shell.set_color(namespace, :blue, true) say "-" * namespace.size - print_table(list, :truncate => true) + print_table(list, truncate: true) say end alias_method :display_tasks, :display_commands diff --git a/lib/bundler/vendor/thor/lib/thor/shell.rb b/lib/bundler/vendor/thor/lib/thor/shell.rb index e36fa472d6..265f3ba046 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell.rb @@ -21,7 +21,7 @@ class Bundler::Thor end module Shell - SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] + SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_error, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] attr_writer :shell autoload :Basic, File.expand_path("shell/basic", __dir__) @@ -75,7 +75,7 @@ class Bundler::Thor # Allow shell to be shared between invocations. # def _shared_configuration #:nodoc: - super.merge!(:shell => shell) + super.merge!(shell: shell) end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index 2dddd4a53a..dc3179e5f3 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -1,8 +1,10 @@ +require_relative "column_printer" +require_relative "table_printer" +require_relative "wrapped_printer" + class Bundler::Thor module Shell class Basic - DEFAULT_TERMINAL_WIDTH = 80 - attr_accessor :base attr_reader :padding @@ -103,6 +105,23 @@ class Bundler::Thor stdout.flush end + # Say (print) an error to the user. If the sentence ends with a whitespace + # or tab character, a new line is not appended (print + flush). Otherwise + # are passed straight to puts (behavior got from Highline). + # + # ==== Example + # say_error("error: something went wrong") + # + def say_error(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/)) + return if quiet? + + buffer = prepare_message(message, *color) + buffer << "\n" if force_new_line && !message.to_s.end_with?("\n") + + stderr.print(buffer) + stderr.flush + end + # Say a status with the given color and appends the message. Since this # method is used frequently by actions, it allows nil or false to be given # in log_status, avoiding the message from being shown. If a Symbol is @@ -111,13 +130,14 @@ class Bundler::Thor def say_status(status, message, log_status = true) return if quiet? || log_status == false spaces = " " * (padding + 1) - color = log_status.is_a?(Symbol) ? log_status : :green - status = status.to_s.rjust(12) + margin = " " * status.length + spaces + + color = log_status.is_a?(Symbol) ? log_status : :green status = set_color status, color, true if color - buffer = "#{status}#{spaces}#{message}" - buffer = "#{buffer}\n" unless buffer.end_with?("\n") + message = message.to_s.chomp.gsub(/(?<!\A)^/, margin) + buffer = "#{status}#{spaces}#{message}\n" stdout.print(buffer) stdout.flush @@ -127,14 +147,14 @@ class Bundler::Thor # "yes". # def yes?(statement, color = nil) - !!(ask(statement, color, :add_to_history => false) =~ is?(:yes)) + !!(ask(statement, color, add_to_history: false) =~ is?(:yes)) end # Make a question the to user and returns true if the user replies "n" or # "no". # def no?(statement, color = nil) - !!(ask(statement, color, :add_to_history => false) =~ is?(:no)) + !!(ask(statement, color, add_to_history: false) =~ is?(:no)) end # Prints values in columns @@ -143,16 +163,8 @@ class Bundler::Thor # Array[String, String, ...] # def print_in_columns(array) - return if array.empty? - colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2 - array.each_with_index do |value, index| - # Don't output trailing spaces when printing the last column - if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length - stdout.puts value - else - stdout.printf("%-#{colwidth}s", value) - end - end + printer = ColumnPrinter.new(stdout) + printer.print(array) end # Prints a table. @@ -163,58 +175,11 @@ class Bundler::Thor # ==== Options # indent<Integer>:: Indent the first column by indent value. # colwidth<Integer>:: Force the first column to colwidth spaces wide. + # borders<Boolean>:: Adds ascii borders. # - def print_table(array, options = {}) # rubocop:disable MethodLength - return if array.empty? - - formats = [] - indent = options[:indent].to_i - colwidth = options[:colwidth] - options[:truncate] = terminal_width if options[:truncate] == true - - formats << "%-#{colwidth + 2}s".dup if colwidth - start = colwidth ? 1 : 0 - - colcount = array.max { |a, b| a.size <=> b.size }.size - - maximas = [] - - start.upto(colcount - 1) do |index| - maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max - maximas << maxima - formats << if index == colcount - 1 - # Don't output 2 trailing spaces when printing the last column - "%-s".dup - else - "%-#{maxima + 2}s".dup - end - end - - formats[0] = formats[0].insert(0, " " * indent) - formats << "%s" - - array.each do |row| - sentence = "".dup - - row.each_with_index do |column, index| - maxima = maximas[index] - - f = if column.is_a?(Numeric) - if index == row.size - 1 - # Don't output 2 trailing spaces when printing the last column - "%#{maxima}s" - else - "%#{maxima}s " - end - else - formats[index] - end - sentence << f % column.to_s - end - - sentence = truncate(sentence, options[:truncate]) if options[:truncate] - stdout.puts sentence - end + def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength + printer = TablePrinter.new(stdout, options) + printer.print(array) end # Prints a long string, word-wrapping the text to the current width of the @@ -227,33 +192,8 @@ class Bundler::Thor # indent<Integer>:: Indent each line of the printed paragraph by indent value. # def print_wrapped(message, options = {}) - indent = options[:indent] || 0 - width = terminal_width - indent - paras = message.split("\n\n") - - paras.map! do |unwrapped| - words = unwrapped.split(" ") - counter = words.first.length - words.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| - stdout.puts line.insert(0, " " * indent) - end - stdout.puts unless para == paras.last - end + printer = WrappedPrinter.new(stdout, options) + printer.print(message) end # Deals with file collision and returns true if the file should be @@ -271,7 +211,7 @@ class Bundler::Thor loop do answer = ask( %[Overwrite #{destination}? (enter "h" for help) #{options}], - :add_to_history => false + add_to_history: false ) case answer @@ -298,24 +238,11 @@ class Bundler::Thor say "Please specify merge tool to `THOR_MERGE` env." else - say file_collision_help + say file_collision_help(block_given?) end end end - # This code was copied from Rake, available under MIT-LICENSE - # Copyright (c) 2003, 2004 Jim Weirich - def terminal_width - result = if ENV["THOR_COLUMNS"] - ENV["THOR_COLUMNS"].to_i - else - unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH - end - result < 10 ? DEFAULT_TERMINAL_WIDTH : result - rescue - DEFAULT_TERMINAL_WIDTH - end - # Called if something goes wrong during the execution. This is used by Bundler::Thor # internally and should not be used inside your scripts. If something went # wrong, you can always raise an exception. If you raise a Bundler::Thor::Error, it @@ -366,16 +293,21 @@ class Bundler::Thor end end - def file_collision_help #:nodoc: - <<-HELP + def file_collision_help(block_given) #:nodoc: + help = <<-HELP Y - yes, overwrite n - no, do not overwrite a - all, overwrite this and all others 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 + if block_given + help << <<-HELP + d - diff, show the differences between the old and the new + m - merge, run merge tool + HELP + end + help end def show_diff(destination, content) #:nodoc: @@ -393,46 +325,8 @@ class Bundler::Thor mute? || (base && base.options[:quiet]) end - # Calculate the dynamic width of the terminal - def dynamic_width - @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) - end - - def dynamic_width_stty - `stty size 2>/dev/null`.split[1].to_i - end - - def dynamic_width_tput - `tput cols 2>/dev/null`.to_i - end - def unix? - RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i - end - - def truncate(string, width) - as_unicode do - chars = string.chars.to_a - if chars.length <= width - chars.join - else - chars[0, width - 3].join + "..." - end - end - end - - if "".respond_to?(:encode) - def as_unicode - yield - end - else - def as_unicode - old = $KCODE - $KCODE = "U" - yield - ensure - $KCODE = old - end + Terminal.unix? end def ask_simply(statement, color, options) diff --git a/lib/bundler/vendor/thor/lib/thor/shell/color.rb b/lib/bundler/vendor/thor/lib/thor/shell/color.rb index dc167ed3cc..5d708fadca 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/color.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/color.rb @@ -105,52 +105,7 @@ class Bundler::Thor end def are_colors_disabled? - !ENV['NO_COLOR'].nil? - end - - # Overwrite show_diff to show diff with colors if Diff::LCS is - # available. - # - def show_diff(destination, content) #:nodoc: - if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? - actual = File.binread(destination).to_s.split("\n") - content = content.to_s.split("\n") - - Diff::LCS.sdiff(actual, content).each do |diff| - output_diff_line(diff) - end - else - super - end - end - - 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 - end - end - - # Check if Diff::LCS is loaded. If it is, use it to create pretty output - # for diff. - # - def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) - return @diff_lcs_loaded unless @diff_lcs_loaded.nil? - - @diff_lcs_loaded = begin - require "diff/lcs" - true - rescue LoadError - false - end + !ENV["NO_COLOR"].nil? && !ENV["NO_COLOR"].empty? end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/column_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/column_printer.rb new file mode 100644 index 0000000000..56a9e6181b --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/column_printer.rb @@ -0,0 +1,29 @@ +require_relative "terminal" + +class Bundler::Thor + module Shell + class ColumnPrinter + attr_reader :stdout, :options + + def initialize(stdout, options = {}) + @stdout = stdout + @options = options + @indent = options[:indent].to_i + end + + def print(array) + return if array.empty? + colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2 + array.each_with_index do |value, index| + # Don't output trailing spaces when printing the last column + if ((((index + 1) % (Terminal.terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length + stdout.puts value + else + stdout.printf("%-#{colwidth}s", value) + end + end + end + end + end +end + diff --git a/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/lib/bundler/vendor/thor/lib/thor/shell/html.rb index 77a6d13a23..0277b882b7 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/html.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/html.rb @@ -76,51 +76,6 @@ class Bundler::Thor def can_display_colors? true end - - # Overwrite show_diff to show diff with colors if Diff::LCS is - # available. - # - def show_diff(destination, content) #:nodoc: - if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil? - actual = File.binread(destination).to_s.split("\n") - content = content.to_s.split("\n") - - Diff::LCS.sdiff(actual, content).each do |diff| - output_diff_line(diff) - end - else - super - end - end - - 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 - end - end - - # Check if Diff::LCS is loaded. If it is, use it to create pretty output - # for diff. - # - def diff_lcs_loaded? #:nodoc: - return true if defined?(Diff::LCS) - return @diff_lcs_loaded unless @diff_lcs_loaded.nil? - - @diff_lcs_loaded = begin - require "diff/lcs" - true - rescue LoadError - false - end - end end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb new file mode 100644 index 0000000000..525f9ce5bb --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb @@ -0,0 +1,134 @@ +require_relative "column_printer" +require_relative "terminal" + +class Bundler::Thor + module Shell + class TablePrinter < ColumnPrinter + BORDER_SEPARATOR = :separator + + def initialize(stdout, options = {}) + super + @formats = [] + @maximas = [] + @colwidth = options[:colwidth] + @truncate = options[:truncate] == true ? Terminal.terminal_width : options[:truncate] + @padding = 1 + end + + def print(array) + return if array.empty? + + prepare(array) + + print_border_separator if options[:borders] + + array.each do |row| + if options[:borders] && row == BORDER_SEPARATOR + print_border_separator + next + end + + sentence = "".dup + + row.each_with_index do |column, index| + sentence << format_cell(column, row.size, index) + end + + sentence = truncate(sentence) + sentence << "|" if options[:borders] + stdout.puts indentation + sentence + + end + print_border_separator if options[:borders] + end + + private + + def prepare(array) + array = array.reject{|row| row == BORDER_SEPARATOR } + + @formats << "%-#{@colwidth + 2}s".dup if @colwidth + start = @colwidth ? 1 : 0 + + colcount = array.max { |a, b| a.size <=> b.size }.size + + start.upto(colcount - 1) do |index| + maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max + + @maximas << maxima + @formats << if options[:borders] + "%-#{maxima}s".dup + elsif index == colcount - 1 + # Don't output 2 trailing spaces when printing the last column + "%-s".dup + else + "%-#{maxima + 2}s".dup + end + end + + @formats << "%s" + end + + def format_cell(column, row_size, index) + maxima = @maximas[index] + + f = if column.is_a?(Numeric) + if options[:borders] + # With borders we handle padding separately + "%#{maxima}s" + elsif index == row_size - 1 + # Don't output 2 trailing spaces when printing the last column + "%#{maxima}s" + else + "%#{maxima}s " + end + else + @formats[index] + end + + cell = "".dup + cell << "|" + " " * @padding if options[:borders] + cell << f % column.to_s + cell << " " * @padding if options[:borders] + cell + end + + def print_border_separator + separator = @maximas.map do |maxima| + "+" + "-" * (maxima + 2 * @padding) + end + stdout.puts indentation + separator.join + "+" + end + + def truncate(string) + return string unless @truncate + as_unicode do + chars = string.chars.to_a + if chars.length <= @truncate + chars.join + else + chars[0, @truncate - 3 - @indent].join + "..." + end + end + end + + def indentation + " " * @indent + end + + if "".respond_to?(:encode) + def as_unicode + yield + end + else + def as_unicode + old = $KCODE # rubocop:disable Style/GlobalVars + $KCODE = "U" # rubocop:disable Style/GlobalVars + yield + ensure + $KCODE = old # rubocop:disable Style/GlobalVars + end + end + end + end +end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb b/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb new file mode 100644 index 0000000000..2c60684308 --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/terminal.rb @@ -0,0 +1,42 @@ +class Bundler::Thor + module Shell + module Terminal + DEFAULT_TERMINAL_WIDTH = 80 + + class << self + # This code was copied from Rake, available under MIT-LICENSE + # Copyright (c) 2003, 2004 Jim Weirich + def terminal_width + result = if ENV["THOR_COLUMNS"] + ENV["THOR_COLUMNS"].to_i + else + unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH + end + result < 10 ? DEFAULT_TERMINAL_WIDTH : result + rescue + DEFAULT_TERMINAL_WIDTH + end + + def unix? + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i + end + + private + + # Calculate the dynamic width of the terminal + def dynamic_width + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty + `stty size 2>/dev/null`.split[1].to_i + end + + def dynamic_width_tput + `tput cols 2>/dev/null`.to_i + end + + end + end + end +end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb new file mode 100644 index 0000000000..ba88e952db --- /dev/null +++ b/lib/bundler/vendor/thor/lib/thor/shell/wrapped_printer.rb @@ -0,0 +1,38 @@ +require_relative "column_printer" +require_relative "terminal" + +class Bundler::Thor + module Shell + class WrappedPrinter < ColumnPrinter + def print(message) + width = Terminal.terminal_width - @indent + paras = message.split("\n\n") + + paras.map! do |unwrapped| + words = unwrapped.split(" ") + counter = words.first.length + words.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| + stdout.puts line.insert(0, " " * @indent) + end + stdout.puts unless para == paras.last + end + end + end + end +end + diff --git a/lib/bundler/vendor/thor/lib/thor/util.rb b/lib/bundler/vendor/thor/lib/thor/util.rb index ddf4d21b90..68916daf2e 100644 --- a/lib/bundler/vendor/thor/lib/thor/util.rb +++ b/lib/bundler/vendor/thor/lib/thor/util.rb @@ -90,7 +90,7 @@ class Bundler::Thor def snake_case(str) return str.downcase if str =~ /^[A-Z_]+$/ str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/ - $+.downcase + Regexp.last_match(-1).downcase end # Receives a string and convert it to camel case. camel_case returns CamelCase. @@ -130,9 +130,10 @@ class Bundler::Thor # 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 = Bundler::Thor::Util.find_by_namespace(pieces.join(":")) + *pieces, command = namespace.split(":") + namespace = pieces.join(":") + namespace = "default" if namespace.empty? + klass = Bundler::Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.commands.keys.include?(command) } end unless klass # look for a Bundler::Thor::Group with the right name klass = Bundler::Thor::Util.find_by_namespace(namespace) @@ -150,7 +151,7 @@ class Bundler::Thor # inside the sandbox to avoid namespacing conflicts. # def load_thorfile(path, content = nil, debug = false) - content ||= File.binread(path) + content ||= File.read(path) begin Bundler::Thor::Sandbox.class_eval(content, path) @@ -189,7 +190,7 @@ class Bundler::Thor # Returns the root where thor files are located, depending on the OS. # def thor_root - File.join(user_home, ".thor").tr('\\', "/") + File.join(user_home, ".thor").tr("\\", "/") end # Returns the files in the thor root. On Windows thor_root will be something @@ -211,7 +212,7 @@ class Bundler::Thor # def globs_for(path) path = escape_globs(path) - ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] + ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/**/*.thor"] end # Return the path to the ruby interpreter taking into account multiple @@ -236,7 +237,7 @@ class Bundler::Thor # symlink points to 'ruby_install_name' ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby end - rescue NotImplementedError # rubocop:disable HandleExceptions + rescue NotImplementedError # rubocop:disable Lint/HandleExceptions # just ignore on windows end end diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb index a3efa9f762..1fb00017ed 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 = "1.1.0" + VERSION = "1.3.0" end |