aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bundler/vendor/thor/task.rb
blob: ebb2d5a5561dcd3ce6b51daf50ba2a4d1080b3c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class Thor
  class Task < Struct.new(:name, :description, :long_description, :usage, :options)
    FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/

    # 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, name.to_s, options)
      end

      def run(instance, args=[])
        if (instance.methods & [name.to_s, name.to_sym]).empty?
          super
        else
          instance.class.handle_no_task_error(name)
        end
      end
    end

    def initialize(name, description, long_description, usage, options=nil)
      super(name.to_s, description, long_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=[])
      public_method?(instance) ?
        instance.send(name, *args) : instance.class.handle_no_task_error(name)
    rescue ArgumentError => e
      handle_argument_error?(instance, e, caller) ?
        instance.class.handle_argument_error(self, e) : (raise e)
    rescue NoMethodError => e
      handle_no_method_error?(instance, e, caller) ?
        instance.class.handle_no_task_error(name) : (raise 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 not_debugging?(instance)
        !(instance.class.respond_to?(:debugging) && instance.class.debugging)
      end

      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

      def sans_backtrace(backtrace, caller) #:nodoc:
        saned  = backtrace.reject { |frame| frame =~ FILE_REGEXP }
        saned -= caller
      end

      def handle_argument_error?(instance, error, caller)
        not_debugging?(instance) && error.message =~ /wrong number of arguments/ &&
          sans_backtrace(error.backtrace, caller).empty?
      end

      def handle_no_method_error?(instance, error, caller)
        not_debugging?(instance) &&
          error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
      end

  end
end