thor 0.14.6 → 0.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/{CHANGELOG.rdoc → CHANGELOG.md} +91 -31
  4. data/CONTRIBUTING.md +15 -0
  5. data/{LICENSE → LICENSE.md} +2 -2
  6. data/README.md +36 -296
  7. data/bin/thor +1 -1
  8. data/lib/thor/actions/create_file.rb +33 -35
  9. data/lib/thor/actions/create_link.rb +7 -5
  10. data/lib/thor/actions/directory.rb +49 -24
  11. data/lib/thor/actions/empty_directory.rb +68 -67
  12. data/lib/thor/actions/file_manipulation.rb +85 -28
  13. data/lib/thor/actions/inject_into_file.rb +26 -32
  14. data/lib/thor/actions.rb +70 -66
  15. data/lib/thor/base.rb +314 -237
  16. data/lib/thor/command.rb +133 -0
  17. data/lib/thor/core_ext/hash_with_indifferent_access.rb +34 -24
  18. data/lib/thor/core_ext/io_binary_read.rb +12 -0
  19. data/lib/thor/core_ext/ordered_hash.rb +94 -65
  20. data/lib/thor/error.rb +10 -8
  21. data/lib/thor/group.rb +74 -66
  22. data/lib/thor/invocation.rb +65 -56
  23. data/lib/thor/line_editor/basic.rb +35 -0
  24. data/lib/thor/line_editor/readline.rb +88 -0
  25. data/lib/thor/line_editor.rb +17 -0
  26. data/lib/thor/parser/argument.rb +32 -29
  27. data/lib/thor/parser/arguments.rb +107 -93
  28. data/lib/thor/parser/option.rb +39 -13
  29. data/lib/thor/parser/options.rb +144 -97
  30. data/lib/thor/parser.rb +4 -4
  31. data/lib/thor/rake_compat.rb +21 -16
  32. data/lib/thor/runner.rb +166 -153
  33. data/lib/thor/shell/basic.rb +268 -122
  34. data/lib/thor/shell/color.rb +84 -43
  35. data/lib/thor/shell/html.rb +71 -66
  36. data/lib/thor/shell.rb +30 -37
  37. data/lib/thor/util.rb +227 -188
  38. data/lib/thor/version.rb +1 -1
  39. data/lib/thor.rb +289 -131
  40. data/thor.gemspec +21 -0
  41. metadata +50 -189
  42. data/Thorfile +0 -24
  43. data/bin/rake2thor +0 -86
  44. data/lib/thor/core_ext/file_binary_read.rb +0 -9
  45. data/lib/thor/task.rb +0 -114
  46. data/spec/actions/create_file_spec.rb +0 -170
  47. data/spec/actions/directory_spec.rb +0 -136
  48. data/spec/actions/empty_directory_spec.rb +0 -98
  49. data/spec/actions/file_manipulation_spec.rb +0 -310
  50. data/spec/actions/inject_into_file_spec.rb +0 -135
  51. data/spec/actions_spec.rb +0 -322
  52. data/spec/base_spec.rb +0 -269
  53. data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
  54. data/spec/core_ext/ordered_hash_spec.rb +0 -115
  55. data/spec/fixtures/application.rb +0 -2
  56. data/spec/fixtures/bundle/execute.rb +0 -6
  57. data/spec/fixtures/bundle/main.thor +0 -1
  58. data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
  59. data/spec/fixtures/doc/README +0 -3
  60. data/spec/fixtures/doc/block_helper.rb +0 -3
  61. data/spec/fixtures/doc/components/.empty_directory +0 -0
  62. data/spec/fixtures/doc/config.rb +0 -1
  63. data/spec/fixtures/group.thor +0 -114
  64. data/spec/fixtures/invoke.thor +0 -112
  65. data/spec/fixtures/path with spaces +0 -0
  66. data/spec/fixtures/script.thor +0 -184
  67. data/spec/fixtures/task.thor +0 -10
  68. data/spec/group_spec.rb +0 -178
  69. data/spec/invocation_spec.rb +0 -100
  70. data/spec/parser/argument_spec.rb +0 -47
  71. data/spec/parser/arguments_spec.rb +0 -64
  72. data/spec/parser/option_spec.rb +0 -202
  73. data/spec/parser/options_spec.rb +0 -319
  74. data/spec/rake_compat_spec.rb +0 -68
  75. data/spec/register_spec.rb +0 -92
  76. data/spec/runner_spec.rb +0 -210
  77. data/spec/shell/basic_spec.rb +0 -223
  78. data/spec/shell/color_spec.rb +0 -41
  79. data/spec/shell/html_spec.rb +0 -27
  80. data/spec/shell_spec.rb +0 -47
  81. data/spec/spec_helper.rb +0 -54
  82. data/spec/task_spec.rb +0 -69
  83. data/spec/thor_spec.rb +0 -334
  84. data/spec/util_spec.rb +0 -163
@@ -0,0 +1,133 @@
1
+ class Thor
2
+ class Command < Struct.new(:name, :description, :long_description, :usage, :options, :disable_class_options)
3
+ FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
4
+
5
+ def initialize(name, description, long_description, usage, options = nil, disable_class_options = false)
6
+ super(name.to_s, description, long_description, usage, options || {}, disable_class_options)
7
+ end
8
+
9
+ def initialize_copy(other) #:nodoc:
10
+ super(other)
11
+ self.options = other.options.dup if other.options
12
+ end
13
+
14
+ def hidden?
15
+ false
16
+ end
17
+
18
+ # By default, a command invokes a method in the thor class. You can change this
19
+ # implementation to create custom commands.
20
+ def run(instance, args = [])
21
+ arity = nil
22
+
23
+ if private_method?(instance)
24
+ instance.class.handle_no_command_error(name)
25
+ elsif public_method?(instance)
26
+ arity = instance.method(name).arity
27
+ instance.__send__(name, *args)
28
+ elsif local_method?(instance, :method_missing)
29
+ instance.__send__(:method_missing, name.to_sym, *args)
30
+ else
31
+ instance.class.handle_no_command_error(name)
32
+ end
33
+ rescue ArgumentError => e
34
+ handle_argument_error?(instance, e, caller) ? instance.class.handle_argument_error(self, e, args, arity) : (raise e)
35
+ rescue NoMethodError => e
36
+ handle_no_method_error?(instance, e, caller) ? instance.class.handle_no_command_error(name) : (raise e)
37
+ end
38
+
39
+ # Returns the formatted usage by injecting given required arguments
40
+ # and required options into the given usage.
41
+ def formatted_usage(klass, namespace = true, subcommand = false)
42
+ if namespace
43
+ namespace = klass.namespace
44
+ formatted = "#{namespace.gsub(/^(default)/, '')}:"
45
+ end
46
+ formatted = "#{klass.namespace.split(':').last} " if subcommand
47
+
48
+ formatted ||= ""
49
+
50
+ # Add usage with required arguments
51
+ formatted << if klass && !klass.arguments.empty?
52
+ usage.to_s.gsub(/^#{name}/) do |match|
53
+ match << " " << klass.arguments.map(&:usage).compact.join(" ")
54
+ end
55
+ else
56
+ usage.to_s
57
+ end
58
+
59
+ # Add required options
60
+ formatted << " #{required_options}"
61
+
62
+ # Strip and go!
63
+ formatted.strip
64
+ end
65
+
66
+ protected
67
+
68
+ def not_debugging?(instance)
69
+ !(instance.class.respond_to?(:debugging) && instance.class.debugging)
70
+ end
71
+
72
+ def required_options
73
+ @required_options ||= options.map { |_, o| o.usage if o.required? }.compact.sort.join(" ")
74
+ end
75
+
76
+ # Given a target, checks if this class name is a public method.
77
+ def public_method?(instance) #:nodoc:
78
+ !(instance.public_methods & [name.to_s, name.to_sym]).empty?
79
+ end
80
+
81
+ def private_method?(instance)
82
+ !(instance.private_methods & [name.to_s, name.to_sym]).empty?
83
+ end
84
+
85
+ def local_method?(instance, name)
86
+ methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false)
87
+ !(methods & [name.to_s, name.to_sym]).empty?
88
+ end
89
+
90
+ def sans_backtrace(backtrace, caller) #:nodoc:
91
+ saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) || (frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/) }
92
+ saned - caller
93
+ end
94
+
95
+ def handle_argument_error?(instance, error, caller)
96
+ not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
97
+ saned = sans_backtrace(error.backtrace, caller)
98
+ # Ruby 1.9 always include the called method in the backtrace
99
+ saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
100
+ end
101
+ end
102
+
103
+ def handle_no_method_error?(instance, error, caller)
104
+ not_debugging?(instance) &&
105
+ error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
106
+ end
107
+ end
108
+ Task = Command
109
+
110
+ # A command that is hidden in help messages but still invocable.
111
+ class HiddenCommand < Command
112
+ def hidden?
113
+ true
114
+ end
115
+ end
116
+ HiddenTask = HiddenCommand
117
+
118
+ # A dynamic command that handles method missing scenarios.
119
+ class DynamicCommand < Command
120
+ def initialize(name, options = nil)
121
+ super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options)
122
+ end
123
+
124
+ def run(instance, args = [])
125
+ if (instance.methods & [name.to_s, name.to_sym]).empty?
126
+ super
127
+ else
128
+ instance.class.handle_no_command_error(name)
129
+ end
130
+ end
131
+ end
132
+ DynamicTask = DynamicCommand
133
+ end
@@ -1,6 +1,5 @@
1
1
  class Thor
2
2
  module CoreExt #:nodoc:
3
-
4
3
  # A hash with indifferent access and magic predicates.
5
4
  #
6
5
  # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
@@ -10,8 +9,7 @@ class Thor
10
9
  # hash.foo? #=> true
11
10
  #
12
11
  class HashWithIndifferentAccess < ::Hash #:nodoc:
13
-
14
- def initialize(hash={})
12
+ def initialize(hash = {})
15
13
  super()
16
14
  hash.each do |key, value|
17
15
  self[convert_key(key)] = value
@@ -30,8 +28,16 @@ class Thor
30
28
  super(convert_key(key))
31
29
  end
32
30
 
31
+ def fetch(key, *args)
32
+ super(convert_key(key), *args)
33
+ end
34
+
35
+ def key?(key)
36
+ super(convert_key(key))
37
+ end
38
+
33
39
  def values_at(*indices)
34
- indices.collect { |key| self[convert_key(key)] }
40
+ indices.map { |key| self[convert_key(key)] }
35
41
  end
36
42
 
37
43
  def merge(other)
@@ -45,31 +51,35 @@ class Thor
45
51
  self
46
52
  end
47
53
 
48
- protected
54
+ # Convert to a Hash with String keys.
55
+ def to_hash
56
+ Hash.new(default).merge!(self)
57
+ end
49
58
 
50
- def convert_key(key)
51
- key.is_a?(Symbol) ? key.to_s : key
52
- end
59
+ protected
60
+
61
+ def convert_key(key)
62
+ key.is_a?(Symbol) ? key.to_s : key
63
+ end
53
64
 
54
- # Magic predicates. For instance:
55
- #
56
- # options.force? # => !!options['force']
57
- # options.shebang # => "/usr/lib/local/ruby"
58
- # options.test_framework?(:rspec) # => options[:test_framework] == :rspec
59
- #
60
- def method_missing(method, *args, &block)
61
- method = method.to_s
62
- if method =~ /^(\w+)\?$/
63
- if args.empty?
64
- !!self[$1]
65
- else
66
- self[$1] == args.first
67
- end
65
+ # Magic predicates. For instance:
66
+ #
67
+ # options.force? # => !!options['force']
68
+ # options.shebang # => "/usr/lib/local/ruby"
69
+ # options.test_framework?(:rspec) # => options[:test_framework] == :rspec
70
+ #
71
+ def method_missing(method, *args)
72
+ method = method.to_s
73
+ if method =~ /^(\w+)\?$/
74
+ if args.empty?
75
+ !!self[$1]
68
76
  else
69
- self[method]
77
+ self[$1] == args.first
70
78
  end
79
+ else
80
+ self[method]
71
81
  end
72
-
82
+ end
73
83
  end
74
84
  end
75
85
  end
@@ -0,0 +1,12 @@
1
+ class IO #:nodoc:
2
+ class << self
3
+ unless method_defined? :binread
4
+ def binread(file, *args)
5
+ raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
6
+ File.open(file, "rb") do |f|
7
+ f.read(*args)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,100 +1,129 @@
1
1
  class Thor
2
- module CoreExt #:nodoc:
3
-
4
- if RUBY_VERSION >= '1.9'
5
- class OrderedHash < ::Hash
6
- end
7
- else
8
- # This class is based on the Ruby 1.9 ordered hashes.
9
- #
10
- # It keeps the semantics and most of the efficiency of normal hashes
11
- # while also keeping track of the order in which elements were set.
12
- #
13
- class OrderedHash #:nodoc:
14
- include Enumerable
15
-
16
- Node = Struct.new(:key, :value, :next, :prev)
17
-
18
- def initialize
19
- @hash = {}
2
+ module CoreExt
3
+ class OrderedHash < ::Hash
4
+ if RUBY_VERSION < "1.9"
5
+ def initialize(*args, &block)
6
+ super
7
+ @keys = []
20
8
  end
21
9
 
22
- def [](key)
23
- @hash[key] && @hash[key].value
10
+ def initialize_copy(other)
11
+ super
12
+ # make a deep copy of keys
13
+ @keys = other.keys
24
14
  end
25
15
 
26
16
  def []=(key, value)
27
- if node = @hash[key]
28
- node.value = value
29
- else
30
- node = Node.new(key, value)
31
-
32
- if @first.nil?
33
- @first = @last = node
34
- else
35
- node.prev = @last
36
- @last.next = node
37
- @last = node
38
- end
39
- end
40
-
41
- @hash[key] = node
42
- value
17
+ @keys << key unless key?(key)
18
+ super
43
19
  end
44
20
 
45
21
  def delete(key)
46
- if node = @hash[key]
47
- prev_node = node.prev
48
- next_node = node.next
49
-
50
- next_node.prev = prev_node if next_node
51
- prev_node.next = next_node if prev_node
22
+ if key? key
23
+ index = @keys.index(key)
24
+ @keys.delete_at index
25
+ end
26
+ super
27
+ end
52
28
 
53
- @first = next_node if @first == node
54
- @last = prev_node if @last == node
29
+ def delete_if
30
+ super
31
+ sync_keys!
32
+ self
33
+ end
55
34
 
56
- value = node.value
57
- end
35
+ alias_method :reject!, :delete_if
58
36
 
59
- @hash.delete(key)
60
- value
37
+ def reject(&block)
38
+ dup.reject!(&block)
61
39
  end
62
40
 
63
41
  def keys
64
- self.map { |k, v| k }
42
+ @keys.dup
65
43
  end
66
44
 
67
45
  def values
68
- self.map { |k, v| v }
46
+ @keys.map { |key| self[key] }
47
+ end
48
+
49
+ def to_hash
50
+ self
51
+ end
52
+
53
+ def to_a
54
+ @keys.map { |key| [key, self[key]] }
55
+ end
56
+
57
+ def each_key
58
+ return to_enum(:each_key) unless block_given?
59
+ @keys.each { |key| yield(key) }
60
+ self
61
+ end
62
+
63
+ def each_value
64
+ return to_enum(:each_value) unless block_given?
65
+ @keys.each { |key| yield(self[key]) }
66
+ self
69
67
  end
70
68
 
71
69
  def each
72
- return unless @first
73
- yield [@first.key, @first.value]
74
- node = @first
75
- yield [node.key, node.value] while node = node.next
70
+ return to_enum(:each) unless block_given?
71
+ @keys.each { |key| yield([key, self[key]]) }
76
72
  self
77
73
  end
78
74
 
79
- def merge(other)
80
- hash = self.class.new
75
+ def each_pair
76
+ return to_enum(:each_pair) unless block_given?
77
+ @keys.each { |key| yield(key, self[key]) }
78
+ self
79
+ end
81
80
 
82
- self.each do |key, value|
83
- hash[key] = value
84
- end
81
+ alias_method :select, :find_all
85
82
 
86
- other.each do |key, value|
87
- hash[key] = value
83
+ def clear
84
+ super
85
+ @keys.clear
86
+ self
87
+ end
88
+
89
+ def shift
90
+ k = @keys.first
91
+ v = delete(k)
92
+ [k, v]
93
+ end
94
+
95
+ def merge!(other_hash)
96
+ if block_given?
97
+ other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
98
+ else
99
+ other_hash.each { |k, v| self[k] = v }
88
100
  end
101
+ self
102
+ end
103
+
104
+ alias_method :update, :merge!
105
+
106
+ def merge(other_hash, &block)
107
+ dup.merge!(other_hash, &block)
108
+ end
89
109
 
90
- hash
110
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
111
+ def replace(other)
112
+ super
113
+ @keys = other.keys
114
+ self
91
115
  end
92
116
 
93
- def empty?
94
- @hash.empty?
117
+ def inspect
118
+ "#<#{self.class} #{super}>"
119
+ end
120
+
121
+ private
122
+
123
+ def sync_keys!
124
+ @keys.delete_if { |k| !key?(k) }
95
125
  end
96
126
  end
97
127
  end
98
-
99
128
  end
100
129
  end
data/lib/thor/error.rb CHANGED
@@ -1,21 +1,23 @@
1
1
  class Thor
2
2
  # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
3
- # errors have their backtrace supressed and are nicely shown to the user.
3
+ # errors have their backtrace suppressed and are nicely shown to the user.
4
4
  #
5
5
  # Errors that are caused by the developer, like declaring a method which
6
- # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
6
+ # overwrites a thor keyword, SHOULD NOT raise a Thor::Error. This way, we
7
7
  # ensure that developer errors are shown with full backtrace.
8
- #
9
8
  class Error < StandardError
10
9
  end
11
10
 
12
- # Raised when a task was not found.
13
- #
14
- class UndefinedTaskError < Error
11
+ # Raised when a command was not found.
12
+ class UndefinedCommandError < Error
15
13
  end
14
+ UndefinedTaskError = UndefinedCommandError
16
15
 
17
- # Raised when a task was found, but not invoked properly.
18
- #
16
+ class AmbiguousCommandError < Error
17
+ end
18
+ AmbiguousTaskError = AmbiguousCommandError
19
+
20
+ # Raised when a command was found, but not invoked properly.
19
21
  class InvocationError < Error
20
22
  end
21
23