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.rb CHANGED
@@ -1,22 +1,31 @@
1
- require 'thor/base'
1
+ require "set"
2
+ require "thor/base"
2
3
 
3
4
  class Thor
4
5
  class << self
5
- # Sets the default task when thor is executed without an explicit task to be called.
6
+ # Allows for custom "Command" package naming.
7
+ #
8
+ # === Parameters
9
+ # name<String>
10
+ # options<Hash>
11
+ #
12
+ def package_name(name, _ = {})
13
+ @package_name = name.nil? || name == "" ? nil : name
14
+ end
15
+
16
+ # Sets the default command when thor is executed without an explicit command to be called.
6
17
  #
7
18
  # ==== Parameters
8
- # meth<Symbol>:: name of the defaut task
9
- #
10
- def default_task(meth=nil)
11
- case meth
12
- when :none
13
- @default_task = 'help'
14
- when nil
15
- @default_task ||= from_superclass(:default_task, 'help')
16
- else
17
- @default_task = meth.to_s
19
+ # meth<Symbol>:: name of the default command
20
+ #
21
+ def default_command(meth = nil)
22
+ if meth
23
+ @default_command = meth == :none ? "help" : meth.to_s
24
+ else
25
+ @default_command ||= from_superclass(:default_command, "help")
18
26
  end
19
27
  end
28
+ alias_method :default_task, :default_command
20
29
 
21
30
  # Registers another Thor subclass as a command.
22
31
  #
@@ -25,48 +34,50 @@ class Thor
25
34
  # command<String>:: Subcommand name to use
26
35
  # usage<String>:: Short usage for the subcommand
27
36
  # description<String>:: Description for the subcommand
28
- def register(klass, subcommand_name, usage, description, options={})
37
+ def register(klass, subcommand_name, usage, description, options = {})
29
38
  if klass <= Thor::Group
30
39
  desc usage, description, options
31
- define_method(subcommand_name) { invoke klass }
40
+ define_method(subcommand_name) { |*args| invoke(klass, args) }
32
41
  else
33
42
  desc usage, description, options
34
43
  subcommand subcommand_name, klass
35
44
  end
36
45
  end
37
46
 
38
- # Defines the usage and the description of the next task.
47
+ # Defines the usage and the description of the next command.
39
48
  #
40
49
  # ==== Parameters
41
50
  # usage<String>
42
51
  # description<String>
43
52
  # options<String>
44
53
  #
45
- def desc(usage, description, options={})
54
+ def desc(usage, description, options = {})
46
55
  if options[:for]
47
- task = find_and_refresh_task(options[:for])
48
- task.usage = usage if usage
49
- task.description = description if description
56
+ command = find_and_refresh_command(options[:for])
57
+ command.usage = usage if usage
58
+ command.description = description if description
50
59
  else
51
- @usage, @desc, @hide = usage, description, options[:hide] || false
60
+ @usage = usage
61
+ @desc = description
62
+ @hide = options[:hide] || false
52
63
  end
53
64
  end
54
65
 
55
- # Defines the long description of the next task.
66
+ # Defines the long description of the next command.
56
67
  #
57
68
  # ==== Parameters
58
69
  # long description<String>
59
70
  #
60
- def long_desc(long_description, options={})
71
+ def long_desc(long_description, options = {})
61
72
  if options[:for]
62
- task = find_and_refresh_task(options[:for])
63
- task.long_description = long_description if long_description
73
+ command = find_and_refresh_command(options[:for])
74
+ command.long_description = long_description if long_description
64
75
  else
65
76
  @long_desc = long_description
66
77
  end
67
78
  end
68
79
 
69
- # Maps an input to a task. If you define:
80
+ # Maps an input to a command. If you define:
70
81
  #
71
82
  # map "-T" => "list"
72
83
  #
@@ -74,18 +85,18 @@ class Thor
74
85
  #
75
86
  # thor -T
76
87
  #
77
- # Will invoke the list task.
88
+ # Will invoke the list command.
78
89
  #
79
90
  # ==== Parameters
80
- # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
91
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
81
92
  #
82
- def map(mappings=nil)
93
+ def map(mappings = nil)
83
94
  @map ||= from_superclass(:map, {})
84
95
 
85
96
  if mappings
86
97
  mappings.each do |key, value|
87
98
  if key.respond_to?(:each)
88
- key.each {|subkey| @map[subkey] = value}
99
+ key.each { |subkey| @map[subkey] = value }
89
100
  else
90
101
  @map[key] = value
91
102
  end
@@ -95,29 +106,31 @@ class Thor
95
106
  @map
96
107
  end
97
108
 
98
- # Declares the options for the next task to be declared.
109
+ # Declares the options for the next command to be declared.
99
110
  #
100
111
  # ==== Parameters
101
112
  # Hash[Symbol => Object]:: The hash key is the name of the option and the value
102
113
  # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
103
114
  # or :required (string). If you give a value, the type of the value is used.
104
115
  #
105
- def method_options(options=nil)
116
+ def method_options(options = nil)
106
117
  @method_options ||= {}
107
118
  build_options(options, @method_options) if options
108
119
  @method_options
109
120
  end
110
121
 
122
+ alias_method :options, :method_options
123
+
111
124
  # Adds an option to the set of method options. If :for is given as option,
112
- # it allows you to change the options from a previous defined task.
125
+ # it allows you to change the options from a previous defined command.
113
126
  #
114
- # def previous_task
127
+ # def previous_command
115
128
  # # magic
116
129
  # end
117
130
  #
118
- # method_option :foo => :bar, :for => :previous_task
131
+ # method_option :foo => :bar, :for => :previous_command
119
132
  #
120
- # def next_task
133
+ # def next_command
121
134
  # # magic
122
135
  # end
123
136
  #
@@ -132,39 +145,46 @@ class Thor
132
145
  # :aliases - Aliases for this option.
133
146
  # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
134
147
  # :banner - String to show on usage notes.
148
+ # :hide - If you want to hide this option from the help.
135
149
  #
136
- def method_option(name, options={})
150
+ def method_option(name, options = {})
137
151
  scope = if options[:for]
138
- find_and_refresh_task(options[:for]).options
152
+ find_and_refresh_command(options[:for]).options
139
153
  else
140
154
  method_options
141
155
  end
142
156
 
143
157
  build_option(name, options, scope)
144
158
  end
159
+ alias_method :option, :method_option
145
160
 
146
- # Prints help information for the given task.
161
+ def disable_class_options
162
+ @disable_class_options = true
163
+ end
164
+
165
+ # Prints help information for the given command.
147
166
  #
148
167
  # ==== Parameters
149
168
  # shell<Thor::Shell>
150
- # task_name<String>
169
+ # command_name<String>
151
170
  #
152
- def task_help(shell, task_name)
153
- meth = normalize_task_name(task_name)
154
- task = all_tasks[meth]
155
- handle_no_task_error(meth) unless task
171
+ def command_help(shell, command_name)
172
+ meth = normalize_command_name(command_name)
173
+ command = all_commands[meth]
174
+ handle_no_command_error(meth) unless command
156
175
 
157
176
  shell.say "Usage:"
158
- shell.say " #{banner(task)}"
177
+ shell.say " #{banner(command)}"
159
178
  shell.say
160
- class_options_help(shell, nil => task.options.map { |_, o| o })
161
- if task.long_description
179
+ class_options_help(shell, nil => command.options.values)
180
+ if command.long_description
162
181
  shell.say "Description:"
163
- shell.print_wrapped(task.long_description, :ident => 2)
182
+ shell.print_wrapped(command.long_description, :indent => 2)
164
183
  else
165
- shell.say task.description
184
+ shell.say command.description
166
185
  end
167
186
  end
187
+ alias_method :task_help, :command_help
168
188
 
169
189
  # Prints help information for this class.
170
190
  #
@@ -172,45 +192,64 @@ class Thor
172
192
  # shell<Thor::Shell>
173
193
  #
174
194
  def help(shell, subcommand = false)
175
- list = printable_tasks(true, subcommand)
195
+ list = printable_commands(true, subcommand)
176
196
  Thor::Util.thor_classes_in(self).each do |klass|
177
- list += klass.printable_tasks(false)
197
+ list += klass.printable_commands(false)
198
+ end
199
+ list.sort! { |a, b| a[0] <=> b[0] }
200
+
201
+ if defined?(@package_name) && @package_name
202
+ shell.say "#{@package_name} commands:"
203
+ else
204
+ shell.say "Commands:"
178
205
  end
179
- list.sort!{ |a,b| a[0] <=> b[0] }
180
206
 
181
- shell.say "Tasks:"
182
- shell.print_table(list, :ident => 2, :truncate => true)
207
+ shell.print_table(list, :indent => 2, :truncate => true)
183
208
  shell.say
184
209
  class_options_help(shell)
185
210
  end
186
211
 
187
- # Returns tasks ready to be printed.
188
- def printable_tasks(all = true, subcommand = false)
189
- (all ? all_tasks : tasks).map do |_, task|
190
- next if task.hidden?
212
+ # Returns commands ready to be printed.
213
+ def printable_commands(all = true, subcommand = false)
214
+ (all ? all_commands : commands).map do |_, command|
215
+ next if command.hidden?
191
216
  item = []
192
- item << banner(task, false, subcommand)
193
- item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
217
+ item << banner(command, false, subcommand)
218
+ item << (command.description ? "# #{command.description.gsub(/\s+/m, ' ')}" : "")
194
219
  item
195
220
  end.compact
196
221
  end
222
+ alias_method :printable_tasks, :printable_commands
197
223
 
198
224
  def subcommands
199
225
  @subcommands ||= from_superclass(:subcommands, [])
200
226
  end
227
+ alias_method :subtasks, :subcommands
228
+
229
+ def subcommand_classes
230
+ @subcommand_classes ||= {}
231
+ end
201
232
 
202
233
  def subcommand(subcommand, subcommand_class)
203
- self.subcommands << subcommand.to_s
234
+ subcommands << subcommand.to_s
204
235
  subcommand_class.subcommand_help subcommand
205
- define_method(subcommand) { |*args| invoke subcommand_class, args }
236
+ subcommand_classes[subcommand.to_s] = subcommand_class
237
+
238
+ define_method(subcommand) do |*args|
239
+ args, opts = Thor::Arguments.split(args)
240
+ invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}]
241
+ invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
242
+ invoke subcommand_class, *invoke_args
243
+ end
206
244
  end
245
+ alias_method :subtask, :subcommand
207
246
 
208
247
  # Extend check unknown options to accept a hash of conditions.
209
248
  #
210
249
  # === Parameters
211
250
  # options<Hash>: A hash containing :only and/or :except keys
212
- def check_unknown_options!(options={})
213
- @check_unknown_options ||= Hash.new
251
+ def check_unknown_options!(options = {})
252
+ @check_unknown_options ||= {}
214
253
  options.each do |key, value|
215
254
  if value
216
255
  @check_unknown_options[key] = Array(value)
@@ -226,10 +265,10 @@ class Thor
226
265
  options = check_unknown_options
227
266
  return false unless options
228
267
 
229
- task = config[:current_task]
230
- return true unless task
268
+ command = config[:current_command]
269
+ return true unless command
231
270
 
232
- name = task.name
271
+ name = command.name
233
272
 
234
273
  if subcommands.include?(name)
235
274
  false
@@ -242,93 +281,212 @@ class Thor
242
281
  end
243
282
  end
244
283
 
245
- protected
246
-
247
- # The method responsible for dispatching given the args.
248
- def dispatch(meth, given_args, given_opts, config) #:nodoc:
249
- meth ||= retrieve_task_name(given_args)
250
- task = all_tasks[normalize_task_name(meth)]
284
+ # Stop parsing of options as soon as an unknown option or a regular
285
+ # argument is encountered. All remaining arguments are passed to the command.
286
+ # This is useful if you have a command that can receive arbitrary additional
287
+ # options, and where those additional options should not be handled by
288
+ # Thor.
289
+ #
290
+ # ==== Example
291
+ #
292
+ # To better understand how this is useful, let's consider a command that calls
293
+ # an external command. A user may want to pass arbitrary options and
294
+ # arguments to that command. The command itself also accepts some options,
295
+ # which should be handled by Thor.
296
+ #
297
+ # class_option "verbose", :type => :boolean
298
+ # stop_on_unknown_option! :exec
299
+ # check_unknown_options! :except => :exec
300
+ #
301
+ # desc "exec", "Run a shell command"
302
+ # def exec(*args)
303
+ # puts "diagnostic output" if options[:verbose]
304
+ # Kernel.exec(*args)
305
+ # end
306
+ #
307
+ # Here +exec+ can be called with +--verbose+ to get diagnostic output,
308
+ # e.g.:
309
+ #
310
+ # $ thor exec --verbose echo foo
311
+ # diagnostic output
312
+ # foo
313
+ #
314
+ # But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
315
+ #
316
+ # $ thor exec echo --verbose foo
317
+ # --verbose foo
318
+ #
319
+ # ==== Parameters
320
+ # Symbol ...:: A list of commands that should be affected.
321
+ def stop_on_unknown_option!(*command_names)
322
+ stop_on_unknown_option.merge(command_names)
323
+ end
251
324
 
252
- if task
253
- args, opts = Thor::Options.split(given_args)
254
- else
255
- args, opts = given_args, nil
256
- task = Thor::DynamicTask.new(meth)
257
- end
325
+ def stop_on_unknown_option?(command) #:nodoc:
326
+ command && stop_on_unknown_option.include?(command.name.to_sym)
327
+ end
258
328
 
259
- opts = given_opts || opts || []
260
- config.merge!(:current_task => task, :task_options => task.options)
329
+ protected
261
330
 
262
- trailing = args[Range.new(arguments.size, -1)]
263
- new(args, opts, config).invoke_task(task, trailing || [])
264
- end
331
+ def stop_on_unknown_option #:nodoc:
332
+ @stop_on_unknown_option ||= Set.new
333
+ end
265
334
 
266
- # The banner for this class. You can customize it if you are invoking the
267
- # thor class by another ways which is not the Thor::Runner. It receives
268
- # the task that is going to be invoked and a boolean which indicates if
269
- # the namespace should be displayed as arguments.
270
- #
271
- def banner(task, namespace = nil, subcommand = false)
272
- "#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
273
- end
335
+ # The method responsible for dispatching given the args.
336
+ def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
337
+ meth ||= retrieve_command_name(given_args)
338
+ command = all_commands[normalize_command_name(meth)]
274
339
 
275
- def baseclass #:nodoc:
276
- Thor
340
+ if !command && config[:invoked_via_subcommand]
341
+ # We're a subcommand and our first argument didn't match any of our
342
+ # commands. So we put it back and call our default command.
343
+ given_args.unshift(meth)
344
+ command = all_commands[normalize_command_name(default_command)]
277
345
  end
278
346
 
279
- def create_task(meth) #:nodoc:
280
- if @usage && @desc
281
- base_class = @hide ? Thor::HiddenTask : Thor::Task
282
- tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
283
- @usage, @desc, @long_desc, @method_options, @hide = nil
284
- true
285
- elsif self.all_tasks[meth] || meth == "method_missing"
286
- true
287
- else
288
- puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
289
- "Call desc if you want this method to be available as task or declare it inside a " <<
290
- "no_tasks{} block. Invoked from #{caller[1].inspect}."
291
- false
347
+ if command
348
+ args, opts = Thor::Options.split(given_args)
349
+ if stop_on_unknown_option?(command) && !args.empty?
350
+ # given_args starts with a non-option, so we treat everything as
351
+ # ordinary arguments
352
+ args.concat opts
353
+ opts.clear
292
354
  end
355
+ else
356
+ args = given_args
357
+ opts = nil
358
+ command = dynamic_command_class.new(meth)
293
359
  end
294
360
 
295
- def initialize_added #:nodoc:
296
- class_options.merge!(method_options)
297
- @method_options = nil
298
- end
361
+ opts = given_opts || opts || []
362
+ config[:current_command] = command
363
+ config[:command_options] = command.options
299
364
 
300
- # Retrieve the task name from given args.
301
- def retrieve_task_name(args) #:nodoc:
302
- meth = args.first.to_s unless args.empty?
365
+ instance = new(args, opts, config)
366
+ yield instance if block_given?
367
+ args = instance.args
368
+ trailing = args[Range.new(arguments.size, -1)]
369
+ instance.invoke_command(command, trailing || [])
370
+ end
303
371
 
304
- if meth && (map[meth] || meth !~ /^\-/)
305
- args.shift
306
- else
307
- nil
308
- end
372
+ # The banner for this class. You can customize it if you are invoking the
373
+ # thor class by another ways which is not the Thor::Runner. It receives
374
+ # the command that is going to be invoked and a boolean which indicates if
375
+ # the namespace should be displayed as arguments.
376
+ #
377
+ def banner(command, namespace = nil, subcommand = false)
378
+ "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
379
+ end
380
+
381
+ def baseclass #:nodoc:
382
+ Thor
383
+ end
384
+
385
+ def dynamic_command_class #:nodoc:
386
+ Thor::DynamicCommand
387
+ end
388
+
389
+ def create_command(meth) #:nodoc:
390
+ @usage ||= nil
391
+ @desc ||= nil
392
+ @long_desc ||= nil
393
+ @disable_class_options ||= nil
394
+
395
+ if @usage && @desc
396
+ base_class = @hide ? Thor::HiddenCommand : Thor::Command
397
+ commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options, @disable_class_options)
398
+ @usage, @desc, @long_desc, @method_options, @hide, @disable_class_options = nil
399
+ true
400
+ elsif all_commands[meth] || meth == "method_missing"
401
+ true
402
+ else
403
+ puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \
404
+ "Call desc if you want this method to be available as command or declare it inside a " \
405
+ "no_commands{} block. Invoked from #{caller[1].inspect}."
406
+ false
309
407
  end
408
+ end
409
+ alias_method :create_task, :create_command
410
+
411
+ def initialize_added #:nodoc:
412
+ class_options.merge!(method_options)
413
+ @method_options = nil
414
+ end
310
415
 
311
- # Receives a task name (can be nil), and try to get a map from it.
312
- # If a map can't be found use the sent name or the default task.
313
- def normalize_task_name(meth) #:nodoc:
314
- meth = map[meth.to_s] || meth || default_task
315
- meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
416
+ # Retrieve the command name from given args.
417
+ def retrieve_command_name(args) #:nodoc:
418
+ meth = args.first.to_s unless args.empty?
419
+ args.shift if meth && (map[meth] || meth !~ /^\-/)
420
+ end
421
+ alias_method :retrieve_task_name, :retrieve_command_name
422
+
423
+ # receives a (possibly nil) command name and returns a name that is in
424
+ # the commands hash. In addition to normalizing aliases, this logic
425
+ # will determine if a shortened command is an unambiguous substring of
426
+ # a command or alias.
427
+ #
428
+ # +normalize_command_name+ also converts names like +animal-prison+
429
+ # into +animal_prison+.
430
+ def normalize_command_name(meth) #:nodoc:
431
+ return default_command.to_s.tr("-", "_") unless meth
432
+
433
+ possibilities = find_command_possibilities(meth)
434
+ raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" if possibilities.size > 1
435
+
436
+ if possibilities.empty?
437
+ meth ||= default_command
438
+ elsif map[meth]
439
+ meth = map[meth]
440
+ else
441
+ meth = possibilities.first
316
442
  end
317
443
 
318
- def subcommand_help(cmd)
319
- desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
320
- class_eval <<-RUBY
321
- def help(task = nil, subcommand = true); super; end
322
- RUBY
444
+ meth.to_s.tr("-", "_") # treat foo-bar as foo_bar
445
+ end
446
+ alias_method :normalize_task_name, :normalize_command_name
447
+
448
+ # this is the logic that takes the command name passed in by the user
449
+ # and determines whether it is an unambiguous substrings of a command or
450
+ # alias name.
451
+ def find_command_possibilities(meth)
452
+ len = meth.to_s.length
453
+ possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
454
+ unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
455
+
456
+ if possibilities.include?(meth)
457
+ [meth]
458
+ elsif unique_possibilities.size == 1
459
+ unique_possibilities
460
+ else
461
+ possibilities
323
462
  end
463
+ end
464
+ alias_method :find_task_possibilities, :find_command_possibilities
465
+
466
+ def subcommand_help(cmd)
467
+ desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
468
+ class_eval "
469
+ def help(command = nil, subcommand = true); super; end
470
+ "
471
+ end
472
+ alias_method :subtask_help, :subcommand_help
324
473
  end
325
474
 
326
475
  include Thor::Base
327
476
 
328
477
  map HELP_MAPPINGS => :help
329
478
 
330
- desc "help [TASK]", "Describe available tasks or one specific task"
331
- def help(task = nil, subcommand = false)
332
- task ? self.class.task_help(shell, task) : self.class.help(shell, subcommand)
479
+ desc "help [COMMAND]", "Describe available commands or one specific command"
480
+ disable_class_options
481
+ def help(command = nil, subcommand = false)
482
+ if command
483
+ if self.class.subcommands.include? command
484
+ self.class.subcommand_classes[command].help(shell, true)
485
+ else
486
+ self.class.command_help(shell, command)
487
+ end
488
+ else
489
+ self.class.help(shell, subcommand)
490
+ end
333
491
  end
334
492
  end
data/thor.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib/", __FILE__)
3
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
4
+ require "thor/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.add_development_dependency "bundler", "~> 1.0"
8
+ spec.authors = ["Yehuda Katz", "José Valim"]
9
+ spec.description = "Thor is a toolkit for building powerful command-line interfaces."
10
+ spec.email = "ruby-thor@googlegroups.com"
11
+ spec.executables = %w(thor)
12
+ spec.files = %w(.document thor.gemspec) + Dir["*.md", "bin/*", "lib/**/*.rb"]
13
+ spec.homepage = "http://whatisthor.com/"
14
+ spec.licenses = %w(MIT)
15
+ spec.name = "thor"
16
+ spec.require_paths = %w(lib)
17
+ spec.required_ruby_version = ">= 1.8.7"
18
+ spec.required_rubygems_version = ">= 1.3.5"
19
+ spec.summary = spec.description
20
+ spec.version = Thor::VERSION
21
+ end