lib/thor/base.rb in thor-0.18.1 vs lib/thor/base.rb in thor-0.19.0

- old
+ new

@@ -1,26 +1,29 @@ -require 'thor/command' -require 'thor/core_ext/hash_with_indifferent_access' -require 'thor/core_ext/ordered_hash' -require 'thor/error' -require 'thor/invocation' -require 'thor/parser' -require 'thor/shell' -require 'thor/util' +require "thor/command" +require "thor/core_ext/hash_with_indifferent_access" +require "thor/core_ext/ordered_hash" +require "thor/error" +require "thor/invocation" +require "thor/parser" +require "thor/shell" +require "thor/line_editor" +require "thor/util" class Thor - autoload :Actions, 'thor/actions' - autoload :RakeCompat, 'thor/rake_compat' - autoload :Group, 'thor/group' + autoload :Actions, "thor/actions" + autoload :RakeCompat, "thor/rake_compat" + autoload :Group, "thor/group" # Shortcuts for help. - HELP_MAPPINGS = %w(-h -? --help -D) + HELP_MAPPINGS = %w[-h -? --help -D] # Thor methods that should not be overwritten by the user. - THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root - action add_file create_file in_root inside run run_ruby_script) + THOR_RESERVED_WORDS = %w[invoke shell options behavior root destination_root relative_root + action add_file create_file in_root inside run run_ruby_script] + TEMPLATE_EXTNAME = ".tt" + module Base attr_accessor :options, :parent_options, :args # It receives arguments in an Array and two hashes, one for options and # other for configuration. @@ -36,35 +39,35 @@ # The hash given is converted to a hash with indifferent # access, magic predicates (options.skip?) and then frozen. # # config<Hash>:: Configuration for this Thor class. # - def initialize(args=[], options={}, config={}) + def initialize(args = [], local_options = {}, config = {}) # rubocop:disable MethodLength parse_options = self.class.class_options # The start method splits inbound arguments at the first argument # that looks like an option (starts with - or --). It then calls # new, passing in the two halves of the arguments Array as the # first two parameters. - if options.is_a?(Array) - command_options = config.delete(:command_options) # hook for start - parse_options = parse_options.merge(command_options) if command_options - array_options, hash_options = options, {} + 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, hash_options = local_options, {} else # Handle the case where the class was explicitly instantiated # with pre-parsed options. - array_options, hash_options = [], options + array_options, hash_options = [], local_options end # 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_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] + self.options = config[:class_options].merge(options) if config[:class_options] # If unknown options are disallowed, make sure that none of the # remaining arguments looks like an option. opts.check_unknown! if self.class.check_unknown_options?(config) @@ -75,17 +78,17 @@ # positional arguments. to_parse = args 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 class << self def included(base) #:nodoc: - base.send :extend, ClassMethods + base.extend ClassMethods base.send :include, Invocation base.send :include, Shell end # Returns the classes that inherits from Thor or Thor::Group. @@ -101,11 +104,11 @@ # # ==== Returns # Hash[path<String> => Class] # def subclass_files - @subclass_files ||= Hash.new{ |h,k| h[k] = [] } + @subclass_files ||= Hash.new { |h, k| h[k] = [] } end # Whenever a class inherits from Thor or Thor::Group, we should track the # class and the file on Thor::Base. This is the method responsable for it. # @@ -200,11 +203,11 @@ # :banner - String to show on usage notes. # # ==== Errors # ArgumentError:: Raised if you supply a required argument after a non required one. # - def argument(name, options={}) + def argument(name, options = {}) # rubocop:disable MethodLength is_thor_reserved_word?(name, :argument) no_commands { attr_accessor name } required = if options.key?(:optional) !options[:optional] @@ -216,11 +219,11 @@ remove_argument name arguments.each do |argument| next if argument.required? - raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " << + fail ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " << "the non-required argument #{argument.human_name.inspect}." end if required options[:required] = required @@ -243,11 +246,11 @@ # If you prefer more detailed declaration, check class_option. # # ==== Parameters # Hash[Symbol => Object] # - def class_options(options=nil) + def class_options(options = nil) @class_options ||= from_superclass(:class_options, {}) build_options(options, @class_options) if options @class_options end @@ -265,11 +268,11 @@ # :aliases:: -- Aliases for this option. <b>Note:</b> Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead. # :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean. # :banner:: -- String to show on usage notes. # :hide:: -- If you want to hide this option from the help. # - def class_option(name, options={}) + def class_option(name, options = {}) build_option(name, options, class_options) end # Removes a previous defined argument. If :undefine is given, undefine # accessors as well. @@ -311,16 +314,15 @@ # that only commands from a pre-defined group will be shown. Defaults to standard. # # ==== Parameters # name<String|Symbol> # - def group(name=nil) - @group = case name - when nil - @group || from_superclass(:group, 'standard') + def group(name = nil) + if name + @group = name.to_s else - name.to_s + @group ||= from_superclass(:group, "standard") end end # Returns the commands for this Thor class. # @@ -329,11 +331,11 @@ # objects as values. # def commands @commands ||= Thor::CoreExt::OrderedHash.new end - alias tasks commands + alias_method :tasks, :commands # Returns the commands for this Thor class and all subclasses. # # ==== Returns # OrderedHash:: An ordered hash with commands names as keys and Thor::Command @@ -341,11 +343,11 @@ # def all_commands @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new) @all_commands.merge(commands) end - alias all_tasks all_commands + alias_method :all_tasks, :all_commands # 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. # @@ -364,11 +366,11 @@ commands.delete(name.to_s) all_commands.delete(name.to_s) undef_method name if options[:undefine] end end - alias remove_task remove_command + alias_method :remove_task, :remove_command # All methods defined inside the given block are not added as commands. # # So you can do: # @@ -391,11 +393,11 @@ @no_commands = true yield ensure @no_commands = false end - alias no_tasks no_commands + alias_method :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 # Scripts::MyScript, the help method, for example, will be called as: # @@ -415,16 +417,15 @@ # # Your commands can be invoked with a shortcut. Instead of: # # thor :my_command # - def namespace(name=nil) - @namespace = case name - when nil - @namespace || Thor::Util.namespace_from_thor_class(self) - else + def namespace(name = nil) + if name @namespace = name.to_s + else + @namespace ||= Thor::Util.namespace_from_thor_class(self) end end # 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 @@ -432,15 +433,15 @@ # can simply initialize it: # # script = MyScript.new(args, options, config) # script.invoke(:command, first_arg, second_arg, third_arg) # - def start(given_args=ARGV, config={}) + def start(given_args = ARGV, config = {}) config[:shell] ||= Thor::Base.shell.new dispatch(nil, given_args.dup, nil, config) rescue Thor::Error => e - ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message) + config[:debug] || 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 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 @@ -461,192 +462,195 @@ def public_command(*names) names.each do |name| class_eval "def #{name}(*); super end" end end - alias public_task public_command + alias_method :public_task, :public_command def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc: if has_namespace - raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." + fail UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." else - raise UndefinedCommandError, "Could not find command #{command.inspect}." + fail UndefinedCommandError, "Could not find command #{command.inspect}." end end - alias handle_no_task_error handle_no_command_error + alias_method :handle_no_task_error, :handle_no_command_error 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 + msg = "ERROR: \"#{basename} #{command.name}\" was called with " + msg << "no arguments" if args.empty? + msg << "arguments " << args.inspect unless args.empty? + msg << "\nUsage: #{banner(command).inspect}" + fail InvocationError, msg end - protected + protected - # Prints the class options per group. If an option does not belong to - # any group, it's printed as Class option. - # - def class_options_help(shell, groups={}) #:nodoc: - # Group options by group - class_options.each do |_, value| - groups[value.group] ||= [] - groups[value.group] << value - end + # Prints the class options per group. If an option does not belong to + # any group, it's printed as Class option. + # + def class_options_help(shell, groups = {}) #:nodoc: + # Group options by group + class_options.each do |_, value| + groups[value.group] ||= [] + groups[value.group] << value + end - # Deal with default group - global_options = groups.delete(nil) || [] - print_options(shell, global_options) + # Deal with default group + global_options = groups.delete(nil) || [] + print_options(shell, global_options) - # Print all others - groups.each do |group_name, options| - print_options(shell, options, group_name) - end + # Print all others + groups.each do |group_name, options| + print_options(shell, options, group_name) end + end - # Receives a set of options and print them. - def print_options(shell, options, group_name=nil) - return if options.empty? + # Receives a set of options and print them. + def print_options(shell, options, group_name = nil) + return if options.empty? - list = [] - padding = options.collect{ |o| o.aliases.size }.max.to_i * 4 + list = [] + padding = options.map { |o| o.aliases.size }.max.to_i * 4 - options.each do |option| - unless option.hide - item = [ option.usage(padding) ] - item.push(option.description ? "# #{option.description}" : "") + options.each do |option| + unless 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 - end + list << item + list << ["", "# Default: #{option.default}"] if option.show_default? + list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum end - - shell.say(group_name ? "#{group_name} options:" : "Options:") - shell.print_table(list, :indent => 2) - shell.say "" end - # Raises an error if the word given is a Thor reserved word. - def is_thor_reserved_word?(word, type) #:nodoc: - return false unless THOR_RESERVED_WORDS.include?(word.to_s) - raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}" - end + shell.say(group_name ? "#{group_name} options:" : "Options:") + shell.print_table(list, :indent => 2) + shell.say "" + end - # Build an option and adds it to the given scope. - # - # ==== Parameters - # name<Symbol>:: The name of the argument. - # 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] = Thor::Option.new(name, options) - end + # Raises an error if the word given is a Thor reserved word. + def is_thor_reserved_word?(word, type) #:nodoc: + return false unless THOR_RESERVED_WORDS.include?(word.to_s) + fail "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}" + end - # Receives a hash of options, parse them and add to the scope. This is a - # fast way to set a bunch of options: - # - # build_options :foo => true, :bar => :required, :baz => :string - # - # ==== Parameters - # Hash[Symbol => Object] - def build_options(options, scope) #:nodoc: - options.each do |key, value| - scope[key] = Thor::Option.parse(key, value) - end - end + # Build an option and adds it to the given scope. + # + # ==== Parameters + # name<Symbol>:: The name of the argument. + # 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] = Thor::Option.new(name, options) + end - # 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 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 command #{name.inspect} could not be found." - end + # Receives a hash of options, parse them and add to the scope. This is a + # fast way to set a bunch of options: + # + # build_options :foo => true, :bar => :required, :baz => :string + # + # ==== Parameters + # Hash[Symbol => Object] + def build_options(options, scope) #:nodoc: + options.each do |key, value| + scope[key] = Thor::Option.parse(key, value) end - alias find_and_refresh_task find_and_refresh_command + end - # 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_commands, false) + # 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 command hash. + 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 + commands[name.to_s] = command.clone + else + fail ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." end + end + alias_method :find_and_refresh_task, :find_and_refresh_command - # Fire this callback whenever a method is added. Added methods are - # tracked as commands by invoking the create_command method. - def method_added(meth) - meth = meth.to_s + # 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_commands, false) + end - if meth == "initialize" - initialize_added - return - end + # Fire this callback whenever a method is added. Added methods are + # tracked as commands by invoking the create_command method. + def method_added(meth) + meth = meth.to_s - # Return if it's not a public instance method - return unless public_method_defined?(meth.to_sym) + if meth == "initialize" + initialize_added + return + end - return if @no_commands || !create_command(meth) + # Return if it's not a public instance method + return unless public_method_defined?(meth.to_sym) - is_thor_reserved_word?(meth, :command) - Thor::Base.register_klass_file(self) - end + @no_commands ||= false + return if @no_commands || !create_command(meth) - # Retrieves a value from superclass. If it reaches the baseclass, - # returns default. - def from_superclass(method, default=nil) - if self == baseclass || !superclass.respond_to?(method, true) - default - else - value = superclass.send(method) + is_thor_reserved_word?(meth, :command) + Thor::Base.register_klass_file(self) + end - if value - if value.is_a?(TrueClass) || value.is_a?(Symbol) - value - else - value.dup - end - end + # Retrieves a value from superclass. If it reaches the baseclass, + # returns default. + def from_superclass(method, default = nil) + if self == baseclass || !superclass.respond_to?(method, true) + default + else + value = superclass.send(method) + + # 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 - # A flag that makes the process exit with status 1 if any error happens. - def exit_on_failure? - false end + end - # - # The basename of the program invoking the thor class. - # - def basename - File.basename($0).split(' ').first - end + # A flag that makes the process exit with status 1 if any error happens. + def exit_on_failure? + false + end - # SIGNATURE: Sets the baseclass. This is where the superclass lookup - # finishes. - def baseclass #:nodoc: - end + # + # The basename of the program invoking the thor class. + # + def basename + File.basename($PROGRAM_NAME).split(" ").first + end - # 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_command(meth) #:nodoc: - end - alias create_task create_command + # SIGNATURE: Sets the baseclass. This is where the superclass lookup + # finishes. + def baseclass #:nodoc: + end - # SIGNATURE: Defines behavior when the initialize method is added to the - # class. - def initialize_added #:nodoc: - end + # 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_command(meth) #:nodoc: + end + alias_method :create_task, :create_command - # SIGNATURE: The hook invoked by start. - def dispatch(command, given_args, given_opts, config) #:nodoc: - raise NotImplementedError - end + # SIGNATURE: Defines behavior when the initialize method is added to the + # class. + def initialize_added #:nodoc: + end + # SIGNATURE: The hook invoked by start. + def dispatch(command, given_args, given_opts, config) #:nodoc: + fail NotImplementedError + end end end end