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
data/lib/thor/group.rb CHANGED
@@ -1,9 +1,9 @@
1
- require 'thor/base'
1
+ require "thor/base"
2
2
 
3
3
  # Thor has a special class called Thor::Group. The main difference to Thor class
4
- # is that it invokes all tasks at once. It also include some methods that allows
4
+ # is that it invokes all commands at once. It also include some methods that allows
5
5
  # invocations to be done at the class method, which are not available to Thor
6
- # tasks.
6
+ # commands.
7
7
  class Thor::Group
8
8
  class << self
9
9
  # The description for this Thor::Group. If none is provided, but a source root
@@ -13,12 +13,11 @@ class Thor::Group
13
13
  # ==== Parameters
14
14
  # description<String>:: The description for this Thor::Group.
15
15
  #
16
- def desc(description=nil)
17
- case description
18
- when nil
19
- @desc ||= from_superclass(:desc, nil)
20
- else
21
- @desc = description
16
+ def desc(description = nil)
17
+ if description
18
+ @desc = description
19
+ else
20
+ @desc ||= from_superclass(:desc, nil)
22
21
  end
23
22
  end
24
23
 
@@ -32,7 +31,7 @@ class Thor::Group
32
31
  shell.say " #{banner}\n"
33
32
  shell.say
34
33
  class_options_help(shell)
35
- shell.say self.desc if self.desc
34
+ shell.say desc if desc
36
35
  end
37
36
 
38
37
  # Stores invocations for this class merging with superclass values.
@@ -48,7 +47,7 @@ class Thor::Group
48
47
  end
49
48
 
50
49
  # Invoke the given namespace or class given. It adds an instance
51
- # method that will invoke the klass and task. You can give a block to
50
+ # method that will invoke the klass and command. You can give a block to
52
51
  # configure how it will be invoked.
53
52
  #
54
53
  # The namespace/class given will have its options showed on the help
@@ -64,12 +63,12 @@ class Thor::Group
64
63
 
65
64
  class_eval <<-METHOD, __FILE__, __LINE__
66
65
  def _invoke_#{name.to_s.gsub(/\W/, '_')}
67
- klass, task = self.class.prepare_for_invocation(nil, #{name.inspect})
66
+ klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
68
67
 
69
68
  if klass
70
69
  say_status :invoke, #{name.inspect}, #{verbose.inspect}
71
70
  block = self.class.invocation_blocks[#{name.inspect}]
72
- _invoke_for_class_method klass, task, &block
71
+ _invoke_for_class_method klass, command, &block
73
72
  else
74
73
  say_status :error, %(#{name.inspect} [not found]), :red
75
74
  end
@@ -100,11 +99,11 @@ class Thor::Group
100
99
  # In some cases you want to customize how a specified hook is going to be
101
100
  # invoked. You can do that by overwriting the class method
102
101
  # prepare_for_invocation. The class method must necessarily return a klass
103
- # and an optional task.
102
+ # and an optional command.
104
103
  #
105
104
  # ==== Custom invocations
106
105
  #
107
- # You can also supply a block to customize how the option is giong to be
106
+ # You can also supply a block to customize how the option is going to be
108
107
  # invoked. The block receives two parameters, an instance of the current
109
108
  # class and the klass to be invoked.
110
109
  #
@@ -114,8 +113,8 @@ class Thor::Group
114
113
 
115
114
  names.each do |name|
116
115
  unless class_options.key?(name)
117
- raise ArgumentError, "You have to define the option #{name.inspect} " <<
118
- "before setting invoke_from_option."
116
+ raise ArgumentError, "You have to define the option #{name.inspect} " \
117
+ "before setting invoke_from_option."
119
118
  end
120
119
 
121
120
  invocations[name] = true
@@ -127,12 +126,12 @@ class Thor::Group
127
126
 
128
127
  value = options[#{name.inspect}]
129
128
  value = #{name.inspect} if TrueClass === value
130
- klass, task = self.class.prepare_for_invocation(#{name.inspect}, value)
129
+ klass, command = self.class.prepare_for_invocation(#{name.inspect}, value)
131
130
 
132
131
  if klass
133
132
  say_status :invoke, value, #{verbose.inspect}
134
133
  block = self.class.invocation_blocks[#{name.inspect}]
135
- _invoke_for_class_method klass, task, &block
134
+ _invoke_for_class_method klass, command, &block
136
135
  else
137
136
  say_status :error, %(\#{value} [not found]), :red
138
137
  end
@@ -149,7 +148,7 @@ class Thor::Group
149
148
  #
150
149
  def remove_invocation(*names)
151
150
  names.each do |name|
152
- remove_task(name)
151
+ remove_command(name)
153
152
  remove_class_option(name)
154
153
  invocations.delete(name)
155
154
  invocation_blocks.delete(name)
@@ -159,7 +158,7 @@ class Thor::Group
159
158
  # Overwrite class options help to allow invoked generators options to be
160
159
  # shown recursively when invoking a generator.
161
160
  #
162
- def class_options_help(shell, groups={}) #:nodoc:
161
+ def class_options_help(shell, groups = {}) #:nodoc:
163
162
  get_options_from_invocations(groups, class_options) do |klass|
164
163
  klass.send(:get_options_from_invocations, groups, class_options)
165
164
  end
@@ -170,7 +169,7 @@ class Thor::Group
170
169
  # options are added to group_options hash. Options that already exists
171
170
  # in base_options are not added twice.
172
171
  #
173
- def get_options_from_invocations(group_options, base_options) #:nodoc:
172
+ def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength
174
173
  invocations.each do |name, from_option|
175
174
  value = if from_option
176
175
  option = class_options[name]
@@ -180,93 +179,102 @@ class Thor::Group
180
179
  end
181
180
  next unless value
182
181
 
183
- klass, task = prepare_for_invocation(name, value)
182
+ klass, _ = prepare_for_invocation(name, value)
184
183
  next unless klass && klass.respond_to?(:class_options)
185
184
 
186
185
  value = value.to_s
187
186
  human_name = value.respond_to?(:classify) ? value.classify : value
188
187
 
189
188
  group_options[human_name] ||= []
190
- group_options[human_name] += klass.class_options.values.select do |option|
191
- base_options[option.name.to_sym].nil? && option.group.nil? &&
192
- !group_options.values.flatten.any? { |i| i.name == option.name }
189
+ group_options[human_name] += klass.class_options.values.select do |class_option|
190
+ base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
191
+ !group_options.values.flatten.any? { |i| i.name == class_option.name }
193
192
  end
194
193
 
195
194
  yield klass if block_given?
196
195
  end
197
196
  end
198
197
 
199
- # Returns tasks ready to be printed.
200
- def printable_tasks(*)
198
+ # Returns commands ready to be printed.
199
+ def printable_commands(*)
201
200
  item = []
202
201
  item << banner
203
- item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
202
+ item << (desc ? "# #{desc.gsub(/\s+/m, ' ')}" : "")
204
203
  [item]
205
204
  end
205
+ alias_method :printable_tasks, :printable_commands
206
206
 
207
- def handle_argument_error(task, error) #:nodoc:
208
- raise error, "#{task.name.inspect} was called incorrectly. Are you sure it has arity equals to 0?"
207
+ def handle_argument_error(command, error, _args, arity) #:nodoc:
208
+ msg = "#{basename} #{command.name} takes #{arity} argument"
209
+ msg << "s" if arity > 1
210
+ msg << ", but it should not."
211
+ raise error, msg
209
212
  end
210
213
 
211
- protected
214
+ protected
212
215
 
213
- # The method responsible for dispatching given the args.
214
- def dispatch(task, given_args, given_opts, config) #:nodoc:
215
- if Thor::HELP_MAPPINGS.include?(given_args.first)
216
- help(config[:shell])
217
- return
218
- end
216
+ # The method responsible for dispatching given the args.
217
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
218
+ if Thor::HELP_MAPPINGS.include?(given_args.first)
219
+ help(config[:shell])
220
+ return
221
+ end
219
222
 
220
- args, opts = Thor::Options.split(given_args)
221
- opts = given_opts || opts
223
+ args, opts = Thor::Options.split(given_args)
224
+ opts = given_opts || opts
222
225
 
223
- if task
224
- new(args, opts, config).invoke_task(all_tasks[task])
225
- else
226
- new(args, opts, config).invoke_all
227
- end
228
- end
226
+ instance = new(args, opts, config)
227
+ yield instance if block_given?
229
228
 
230
- # The banner for this class. You can customize it if you are invoking the
231
- # thor class by another ways which is not the Thor::Runner.
232
- def banner
233
- "#{basename} #{self_task.formatted_usage(self, false)}"
229
+ if command
230
+ instance.invoke_command(all_commands[command])
231
+ else
232
+ instance.invoke_all
234
233
  end
234
+ end
235
235
 
236
- # Represents the whole class as a task.
237
- def self_task #:nodoc:
238
- Thor::DynamicTask.new(self.namespace, class_options)
239
- end
236
+ # The banner for this class. You can customize it if you are invoking the
237
+ # thor class by another ways which is not the Thor::Runner.
238
+ def banner
239
+ "#{basename} #{self_command.formatted_usage(self, false)}"
240
+ end
240
241
 
241
- def baseclass #:nodoc:
242
- Thor::Group
243
- end
242
+ # Represents the whole class as a command.
243
+ def self_command #:nodoc:
244
+ Thor::DynamicCommand.new(namespace, class_options)
245
+ end
246
+ alias_method :self_task, :self_command
244
247
 
245
- def create_task(meth) #:nodoc:
246
- tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, nil)
247
- true
248
- end
248
+ def baseclass #:nodoc:
249
+ Thor::Group
250
+ end
251
+
252
+ def create_command(meth) #:nodoc:
253
+ commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil)
254
+ true
255
+ end
256
+ alias_method :create_task, :create_command
249
257
  end
250
258
 
251
259
  include Thor::Base
252
260
 
253
- protected
261
+ protected
254
262
 
255
263
  # Shortcut to invoke with padding and block handling. Use internally by
256
264
  # invoke and invoke_from_option class methods.
257
- def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
265
+ def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc:
258
266
  with_padding do
259
267
  if block
260
268
  case block.arity
261
269
  when 3
262
- block.call(self, klass, task)
270
+ yield(self, klass, command)
263
271
  when 2
264
- block.call(self, klass)
272
+ yield(self, klass)
265
273
  when 1
266
274
  instance_exec(klass, &block)
267
275
  end
268
276
  else
269
- invoke klass, task, *args
277
+ invoke klass, command, *args
270
278
  end
271
279
  end
272
280
  end
@@ -6,12 +6,12 @@ class Thor
6
6
 
7
7
  module ClassMethods
8
8
  # This method is responsible for receiving a name and find the proper
9
- # class and task for it. The key is an optional parameter which is
9
+ # class and command for it. The key is an optional parameter which is
10
10
  # available only in class methods invocations (i.e. in Thor::Group).
11
11
  def prepare_for_invocation(key, name) #:nodoc:
12
12
  case name
13
13
  when Symbol, String
14
- Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
14
+ Thor::Util.find_class_and_command_by_namespace(name.to_s, !key)
15
15
  else
16
16
  name
17
17
  end
@@ -19,32 +19,37 @@ class Thor
19
19
  end
20
20
 
21
21
  # Make initializer aware of invocations and the initialization args.
22
- def initialize(args=[], options={}, config={}, &block) #:nodoc:
23
- @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
24
- @_initializer = [ args, options, config ]
22
+ def initialize(args = [], options = {}, config = {}, &block) #:nodoc:
23
+ @_invocations = config[:invocations] || Hash.new { |h, k| h[k] = [] }
24
+ @_initializer = [args, options, config]
25
25
  super
26
26
  end
27
27
 
28
- # Receives a name and invokes it. The name can be a string (either "task" or
29
- # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
30
- # cannot be guessed by name, it can also be supplied as second argument.
28
+ # Make the current command chain accessible with in a Thor-(sub)command
29
+ def current_command_chain
30
+ @_invocations.values.flatten.map(&:to_sym)
31
+ end
32
+
33
+ # Receives a name and invokes it. The name can be a string (either "command" or
34
+ # "namespace:command"), a Thor::Command, a Class or a Thor instance. If the
35
+ # command cannot be guessed by name, it can also be supplied as second argument.
31
36
  #
32
37
  # You can also supply the arguments, options and configuration values for
33
- # the task to be invoked, if none is given, the same values used to
38
+ # the command to be invoked, if none is given, the same values used to
34
39
  # initialize the invoker are used to initialize the invoked.
35
40
  #
36
- # When no name is given, it will invoke the default task of the current class.
41
+ # When no name is given, it will invoke the default command of the current class.
37
42
  #
38
43
  # ==== Examples
39
44
  #
40
45
  # class A < Thor
41
46
  # def foo
42
47
  # invoke :bar
43
- # invoke "b:hello", ["José"]
48
+ # invoke "b:hello", ["Erik"]
44
49
  # end
45
50
  #
46
51
  # def bar
47
- # invoke "b:hello", ["José"]
52
+ # invoke "b:hello", ["Erik"]
48
53
  # end
49
54
  # end
50
55
  #
@@ -54,16 +59,16 @@ class Thor
54
59
  # end
55
60
  # end
56
61
  #
57
- # You can notice that the method "foo" above invokes two tasks: "bar",
62
+ # You can notice that the method "foo" above invokes two commands: "bar",
58
63
  # which belongs to the same class and "hello" which belongs to the class B.
59
64
  #
60
- # By using an invocation system you ensure that a task is invoked only once.
65
+ # By using an invocation system you ensure that a command is invoked only once.
61
66
  # In the example above, invoking "foo" will invoke "b:hello" just once, even
62
67
  # if it's invoked later by "bar" method.
63
68
  #
64
69
  # When class A invokes class B, all arguments used on A initialization are
65
70
  # supplied to B. This allows lazy parse of options. Let's suppose you have
66
- # some rspec tasks:
71
+ # some rspec commands:
67
72
  #
68
73
  # class Rspec < Thor::Group
69
74
  # class_option :mock_framework, :type => :string, :default => :rr
@@ -85,7 +90,7 @@ class Thor
85
90
  # that it's going to use.
86
91
  #
87
92
  # If you want Rspec::RR to be initialized with its own set of options, you
88
- # have to do that explicitely:
93
+ # have to do that explicitly:
89
94
  #
90
95
  # invoke "rspec:rr", [], :style => :foo
91
96
  #
@@ -93,35 +98,39 @@ class Thor
93
98
  #
94
99
  # invoke Rspec::RR, [], :style => :foo
95
100
  #
96
- def invoke(name=nil, *args)
101
+ def invoke(name = nil, *args)
97
102
  if name.nil?
98
103
  warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
99
104
  return invoke_all
100
105
  end
101
106
 
102
- args.unshift(nil) if Array === args.first || NilClass === args.first
103
- task, args, opts, config = args
107
+ args.unshift(nil) if args.first.is_a?(Array) || args.first.nil?
108
+ command, args, opts, config = args
104
109
 
105
- klass, task = _retrieve_class_and_task(name, task)
110
+ klass, command = _retrieve_class_and_command(name, command)
111
+ raise "Missing Thor class for invoke #{name}" unless klass
106
112
  raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
107
113
 
108
114
  args, opts, config = _parse_initialization_options(args, opts, config)
109
- klass.send(:dispatch, task, args, opts, config)
115
+ klass.send(:dispatch, command, args, opts, config) do |instance|
116
+ instance.parent_options = options
117
+ end
110
118
  end
111
119
 
112
- # Invoke the given task if the given args.
113
- def invoke_task(task, *args) #:nodoc:
120
+ # Invoke the given command if the given args.
121
+ def invoke_command(command, *args) #:nodoc:
114
122
  current = @_invocations[self.class]
115
123
 
116
- unless current.include?(task.name)
117
- current << task.name
118
- task.run(self, *args)
124
+ unless current.include?(command.name)
125
+ current << command.name
126
+ command.run(self, *args)
119
127
  end
120
128
  end
129
+ alias_method :invoke_task, :invoke_command
121
130
 
122
- # Invoke all tasks for the current instance.
131
+ # Invoke all commands for the current instance.
123
132
  def invoke_all #:nodoc:
124
- self.class.all_tasks.map { |_, task| invoke_task(task) }
133
+ self.class.all_commands.map { |_, command| invoke_command(command) }
125
134
  end
126
135
 
127
136
  # Invokes using shell padding.
@@ -129,40 +138,40 @@ class Thor
129
138
  with_padding { invoke(*args) }
130
139
  end
131
140
 
132
- protected
141
+ protected
133
142
 
134
- # Configuration values that are shared between invocations.
135
- def _shared_configuration #:nodoc:
136
- { :invocations => @_invocations }
137
- end
143
+ # Configuration values that are shared between invocations.
144
+ def _shared_configuration #:nodoc:
145
+ {:invocations => @_invocations}
146
+ end
138
147
 
139
- # This method simply retrieves the class and task to be invoked.
140
- # If the name is nil or the given name is a task in the current class,
141
- # use the given name and return self as class. Otherwise, call
142
- # prepare_for_invocation in the current class.
143
- def _retrieve_class_and_task(name, sent_task=nil) #:nodoc:
144
- case
145
- when name.nil?
146
- [self.class, nil]
147
- when self.class.all_tasks[name.to_s]
148
- [self.class, name.to_s]
149
- else
150
- klass, task = self.class.prepare_for_invocation(nil, name)
151
- [klass, task || sent_task]
152
- end
148
+ # This method simply retrieves the class and command to be invoked.
149
+ # If the name is nil or the given name is a command in the current class,
150
+ # use the given name and return self as class. Otherwise, call
151
+ # prepare_for_invocation in the current class.
152
+ def _retrieve_class_and_command(name, sent_command = nil) #:nodoc:
153
+ if name.nil?
154
+ [self.class, nil]
155
+ elsif self.class.all_commands[name.to_s]
156
+ [self.class, name.to_s]
157
+ else
158
+ klass, command = self.class.prepare_for_invocation(nil, name)
159
+ [klass, command || sent_command]
153
160
  end
161
+ end
162
+ alias_method :_retrieve_class_and_task, :_retrieve_class_and_command
154
163
 
155
- # Initialize klass using values stored in the @_initializer.
156
- def _parse_initialization_options(args, opts, config) #:nodoc:
157
- stored_args, stored_opts, stored_config = @_initializer
164
+ # Initialize klass using values stored in the @_initializer.
165
+ def _parse_initialization_options(args, opts, config) #:nodoc:
166
+ stored_args, stored_opts, stored_config = @_initializer
158
167
 
159
- args ||= stored_args.dup
160
- opts ||= stored_opts.dup
168
+ args ||= stored_args.dup
169
+ opts ||= stored_opts.dup
161
170
 
162
- config ||= {}
163
- config = stored_config.merge(_shared_configuration).merge!(config)
171
+ config ||= {}
172
+ config = stored_config.merge(_shared_configuration).merge!(config)
164
173
 
165
- [ args, opts, config ]
166
- end
174
+ [args, opts, config]
175
+ end
167
176
  end
168
177
  end
@@ -0,0 +1,35 @@
1
+ class Thor
2
+ module LineEditor
3
+ class Basic
4
+ attr_reader :prompt, :options
5
+
6
+ def self.available?
7
+ true
8
+ end
9
+
10
+ def initialize(prompt, options)
11
+ @prompt = prompt
12
+ @options = options
13
+ end
14
+
15
+ def readline
16
+ $stdout.print(prompt)
17
+ get_input
18
+ end
19
+
20
+ private
21
+
22
+ def get_input
23
+ if echo?
24
+ $stdin.gets
25
+ else
26
+ $stdin.noecho(&:gets)
27
+ end
28
+ end
29
+
30
+ def echo?
31
+ options.fetch(:echo, true)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,88 @@
1
+ begin
2
+ require "readline"
3
+ rescue LoadError
4
+ end
5
+
6
+ class Thor
7
+ module LineEditor
8
+ class Readline < Basic
9
+ def self.available?
10
+ Object.const_defined?(:Readline)
11
+ end
12
+
13
+ def readline
14
+ if echo?
15
+ ::Readline.completion_append_character = nil
16
+ # Ruby 1.8.7 does not allow Readline.completion_proc= to receive nil.
17
+ if complete = completion_proc
18
+ ::Readline.completion_proc = complete
19
+ end
20
+ ::Readline.readline(prompt, add_to_history?)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def add_to_history?
29
+ options.fetch(:add_to_history, true)
30
+ end
31
+
32
+ def completion_proc
33
+ if use_path_completion?
34
+ proc { |text| PathCompletion.new(text).matches }
35
+ elsif completion_options.any?
36
+ proc do |text|
37
+ completion_options.select { |option| option.start_with?(text) }
38
+ end
39
+ end
40
+ end
41
+
42
+ def completion_options
43
+ options.fetch(:limited_to, [])
44
+ end
45
+
46
+ def use_path_completion?
47
+ options.fetch(:path, false)
48
+ end
49
+
50
+ class PathCompletion
51
+ attr_reader :text
52
+ private :text
53
+
54
+ def initialize(text)
55
+ @text = text
56
+ end
57
+
58
+ def matches
59
+ relative_matches
60
+ end
61
+
62
+ private
63
+
64
+ def relative_matches
65
+ absolute_matches.map { |path| path.sub(base_path, "") }
66
+ end
67
+
68
+ def absolute_matches
69
+ Dir[glob_pattern].map do |path|
70
+ if File.directory?(path)
71
+ "#{path}/"
72
+ else
73
+ path
74
+ end
75
+ end
76
+ end
77
+
78
+ def glob_pattern
79
+ "#{base_path}#{text}*"
80
+ end
81
+
82
+ def base_path
83
+ "#{Dir.pwd}/"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ require "thor/line_editor/basic"
2
+ require "thor/line_editor/readline"
3
+
4
+ class Thor
5
+ module LineEditor
6
+ def self.readline(prompt, options = {})
7
+ best_available.new(prompt, options).readline
8
+ end
9
+
10
+ def self.best_available
11
+ [
12
+ Thor::LineEditor::Readline,
13
+ Thor::LineEditor::Basic
14
+ ].detect(&:available?)
15
+ end
16
+ end
17
+ end