lib/thor/task.rb
class Thor class Task < Struct.new(:name, :description, :usage, :options) FILE_REGEXP = /^#{Regexp.escape(File.expand_path(__FILE__))}:[\w:]+ `run'$/ # A dynamic task that handles method missing scenarios. class Dynamic < Task def initialize(name, options=nil) super(name.to_s, "A dynamically-generated task", name.to_s, options) end def run(instance, args=[]) unless (instance.methods & [name.to_s, name.to_sym]).empty? raise Error, "could not find Thor class or task '#{name}'" end super end end def initialize(name, description, usage, options=nil) super(name.to_s, description, usage, options || {}) end def initialize_copy(other) #:nodoc: super(other) self.options = other.options.dup if other.options end # By default, a task invokes a method in the thor class. You can change this # implementation to create custom tasks. def run(instance, args=[]) raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance) instance.send(name, *args) rescue ArgumentError => e raise e if instance.class.respond_to?(:debugging) && instance.class.debugging parse_argument_error(instance, e, caller) rescue NoMethodError => e raise e if instance.class.respond_to?(:debugging) && instance.class.debugging parse_no_method_error(instance, e) end # Returns the formatted usage by injecting given required arguments # and required options into the given usage. def formatted_usage(klass, namespace=true) namespace = klass.namespace unless namespace == false # Add namespace formatted = if namespace "#{namespace.gsub(/^(default|thor:runner:)/,'')}:" else "" end # Add usage with required arguments formatted << if klass && !klass.arguments.empty? usage.to_s.gsub(/^#{name}/) do |match| match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ') end else usage.to_s end # Add required options formatted << " #{required_options}" # Strip and go! formatted.strip end protected def required_options @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ") end # Given a target, checks if this class name is not a private/protected method. def public_method?(instance) #:nodoc: collection = instance.private_methods + instance.protected_methods (collection & [name.to_s, name.to_sym]).empty? end # For Ruby <= 1.8.7, we have to match the method name that we are trying to call. # In Ruby >= 1.9.1, we have to match the method run in this file. def backtrace_match?(backtrace) #:nodoc: method_name = /`#{Regexp.escape(name.split(':').last)}'/ backtrace =~ method_name || backtrace =~ FILE_REGEXP end def parse_argument_error(instance, e, caller) #:nodoc: if e.message =~ /wrong number of arguments/ && backtrace_match?(e.backtrace.first.to_s) if instance.is_a?(Thor::Group) raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?" else raise InvocationError, "'#{name}' was called incorrectly. Call as " << "'#{formatted_usage(instance.class)}'" end else raise e end end def parse_no_method_error(instance, e) #:nodoc: if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ raise UndefinedTaskError, "The #{instance.class.namespace} namespace " << "doesn't have a '#{name}' task" else raise e end end end end