PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/thor/command.rb

https://gitlab.com/adamlwalker/thor
Ruby | 133 lines | 100 code | 22 blank | 11 comment | 19 complexity | 2e877a34eb94b5ccedf25e5f2ee165b1 MD5 | raw file
  1. class Thor
  2. class Command < Struct.new(:name, :description, :long_description, :usage, :options)
  3. FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
  4. def initialize(name, description, long_description, usage, options = nil)
  5. super(name.to_s, description, long_description, usage, options || {})
  6. end
  7. def initialize_copy(other) #:nodoc:
  8. super(other)
  9. self.options = other.options.dup if other.options
  10. end
  11. def hidden?
  12. false
  13. end
  14. # By default, a command invokes a method in the thor class. You can change this
  15. # implementation to create custom commands.
  16. def run(instance, args = [])
  17. arity = nil
  18. if private_method?(instance)
  19. instance.class.handle_no_command_error(name)
  20. elsif public_method?(instance)
  21. arity = instance.method(name).arity
  22. instance.__send__(name, *args)
  23. elsif local_method?(instance, :method_missing)
  24. instance.__send__(:method_missing, name.to_sym, *args)
  25. else
  26. instance.class.handle_no_command_error(name)
  27. end
  28. rescue ArgumentError => e
  29. handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
  30. rescue NoMethodError => e
  31. handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (fail e)
  32. end
  33. # Returns the formatted usage by injecting given required arguments
  34. # and required options into the given usage.
  35. def formatted_usage(klass, namespace = true, subcommand = false)
  36. if namespace
  37. namespace = klass.namespace
  38. formatted = "#{namespace.gsub(/^(default)/, '')}:"
  39. end
  40. formatted = "#{klass.namespace.split(':').last} " if subcommand
  41. formatted ||= ""
  42. # Add usage with required arguments
  43. formatted << if klass && !klass.arguments.empty?
  44. usage.to_s.gsub(/^#{name}/) do |match|
  45. match << " " << klass.arguments.map { |a| a.usage }.compact.join(" ")
  46. end
  47. else
  48. usage.to_s
  49. end
  50. # Add required options
  51. formatted << " #{required_options}"
  52. # Strip and go!
  53. formatted.strip
  54. end
  55. protected
  56. def not_debugging?(instance)
  57. !(instance.class.respond_to?(:debugging) && instance.class.debugging)
  58. end
  59. def required_options
  60. @required_options ||= options.map { |_, o| o.usage if o.required? }.compact.sort.join(" ")
  61. end
  62. # Given a target, checks if this class name is a public method.
  63. def public_method?(instance) #:nodoc:
  64. !(instance.public_methods & [name.to_s, name.to_sym]).empty?
  65. end
  66. def private_method?(instance)
  67. !(instance.private_methods & [name.to_s, name.to_sym]).empty?
  68. end
  69. def local_method?(instance, name)
  70. methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false)
  71. !(methods & [name.to_s, name.to_sym]).empty?
  72. end
  73. def sans_backtrace(backtrace, caller) #:nodoc:
  74. saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ /^kernel\// && RUBY_ENGINE =~ /rbx/) }
  75. saned - caller
  76. end
  77. def handle_argument_error?(instance, error, caller)
  78. not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
  79. saned = sans_backtrace(error.backtrace, caller)
  80. # Ruby 1.9 always include the called method in the backtrace
  81. saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
  82. end
  83. end
  84. def handle_no_method_error?(instance, error, caller)
  85. not_debugging?(instance) &&
  86. error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
  87. end
  88. end
  89. Task = Command # rubocop:disable ConstantName
  90. # A command that is hidden in help messages but still invocable.
  91. class HiddenCommand < Command
  92. def hidden?
  93. true
  94. end
  95. end
  96. HiddenTask = HiddenCommand # rubocop:disable ConstantName
  97. # A dynamic command that handles method missing scenarios.
  98. class DynamicCommand < Command
  99. def initialize(name, options = nil)
  100. super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
  101. end
  102. def run(instance, args = [])
  103. if (instance.methods & [name.to_s, name.to_sym]).empty?
  104. super
  105. else
  106. instance.class.handle_no_command_error(name)
  107. end
  108. end
  109. end
  110. DynamicTask = DynamicCommand # rubocop:disable ConstantName
  111. end