thor 0.16.0 → 1.2.1

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