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/base.rb CHANGED
@@ -1,15 +1,17 @@
1
- require 'thor/core_ext/hash_with_indifferent_access'
2
- require 'thor/core_ext/ordered_hash'
3
- require 'thor/error'
4
- require 'thor/shell'
5
- require 'thor/invocation'
6
- require 'thor/parser'
7
- require 'thor/task'
8
- require 'thor/util'
1
+ require "thor/command"
2
+ require "thor/core_ext/hash_with_indifferent_access"
3
+ require "thor/core_ext/ordered_hash"
4
+ require "thor/error"
5
+ require "thor/invocation"
6
+ require "thor/parser"
7
+ require "thor/shell"
8
+ require "thor/line_editor"
9
+ require "thor/util"
9
10
 
10
11
  class Thor
11
- autoload :Actions, 'thor/actions'
12
- autoload :RakeCompat, 'thor/rake_compat'
12
+ autoload :Actions, "thor/actions"
13
+ autoload :RakeCompat, "thor/rake_compat"
14
+ autoload :Group, "thor/group"
13
15
 
14
16
  # Shortcuts for help.
15
17
  HELP_MAPPINGS = %w(-h -? --help -D)
@@ -18,8 +20,10 @@ class Thor
18
20
  THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
19
21
  action add_file create_file in_root inside run run_ruby_script)
20
22
 
23
+ TEMPLATE_EXTNAME = ".tt"
24
+
21
25
  module Base
22
- attr_accessor :options
26
+ attr_accessor :options, :parent_options, :args
23
27
 
24
28
  # It receives arguments in an Array and two hashes, one for options and
25
29
  # other for configuration.
@@ -37,28 +41,54 @@ class Thor
37
41
  #
38
42
  # config<Hash>:: Configuration for this Thor class.
39
43
  #
40
- def initialize(args=[], options={}, config={})
41
- args = Thor::Arguments.parse(self.class.arguments, args)
42
- args.each { |key, value| send("#{key}=", value) }
43
-
44
- parse_options = self.class.class_options
45
-
46
- if options.is_a?(Array)
47
- task_options = config.delete(:task_options) # hook for start
48
- parse_options = parse_options.merge(task_options) if task_options
49
- array_options, hash_options = options, {}
44
+ def initialize(args = [], local_options = {}, config = {})
45
+ parse_options = config[:current_command] && config[:current_command].disable_class_options ? {} : self.class.class_options
46
+
47
+ # The start method splits inbound arguments at the first argument
48
+ # that looks like an option (starts with - or --). It then calls
49
+ # new, passing in the two halves of the arguments Array as the
50
+ # first two parameters.
51
+
52
+ command_options = config.delete(:command_options) # hook for start
53
+ parse_options = parse_options.merge(command_options) if command_options
54
+ if local_options.is_a?(Array)
55
+ array_options = local_options
56
+ hash_options = {}
50
57
  else
51
- array_options, hash_options = [], options
58
+ # Handle the case where the class was explicitly instantiated
59
+ # with pre-parsed options.
60
+ array_options = []
61
+ hash_options = local_options
52
62
  end
53
63
 
54
- opts = Thor::Options.new(parse_options, hash_options)
64
+ # Let Thor::Options parse the options first, so it can remove
65
+ # declared options from the array. This will leave us with
66
+ # a list of arguments that weren't declared.
67
+ stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
68
+ opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown)
55
69
  self.options = opts.parse(array_options)
70
+ self.options = config[:class_options].merge(options) if config[:class_options]
71
+
72
+ # If unknown options are disallowed, make sure that none of the
73
+ # remaining arguments looks like an option.
56
74
  opts.check_unknown! if self.class.check_unknown_options?(config)
75
+
76
+ # Add the remaining arguments from the options parser to the
77
+ # arguments passed in to initialize. Then remove any positional
78
+ # arguments declared using #argument (this is primarily used
79
+ # by Thor::Group). Tis will leave us with the remaining
80
+ # positional arguments.
81
+ to_parse = args
82
+ to_parse += opts.remaining unless self.class.strict_args_position?(config)
83
+
84
+ thor_args = Thor::Arguments.new(self.class.arguments)
85
+ thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) }
86
+ @args = thor_args.remaining
57
87
  end
58
88
 
59
89
  class << self
60
90
  def included(base) #:nodoc:
61
- base.send :extend, ClassMethods
91
+ base.extend ClassMethods
62
92
  base.send :include, Invocation
63
93
  base.send :include, Shell
64
94
  end
@@ -78,7 +108,7 @@ class Thor
78
108
  # Hash[path<String> => Class]
79
109
  #
80
110
  def subclass_files
81
- @subclass_files ||= Hash.new{ |h,k| h[k] = [] }
111
+ @subclass_files ||= Hash.new { |h, k| h[k] = [] }
82
112
  end
83
113
 
84
114
  # Whenever a class inherits from Thor or Thor::Group, we should track the
@@ -94,18 +124,16 @@ class Thor
94
124
  end
95
125
 
96
126
  module ClassMethods
97
- attr_accessor :debugging
98
-
99
127
  def attr_reader(*) #:nodoc:
100
- no_tasks { super }
128
+ no_commands { super }
101
129
  end
102
130
 
103
131
  def attr_writer(*) #:nodoc:
104
- no_tasks { super }
132
+ no_commands { super }
105
133
  end
106
134
 
107
135
  def attr_accessor(*) #:nodoc:
108
- no_tasks { super }
136
+ no_commands { super }
109
137
  end
110
138
 
111
139
  # If you want to raise an error for unknown options, call check_unknown_options!
@@ -122,17 +150,39 @@ class Thor
122
150
  !!check_unknown_options
123
151
  end
124
152
 
153
+ # If true, option parsing is suspended as soon as an unknown option or a
154
+ # regular argument is encountered. All remaining arguments are passed to
155
+ # the command as regular arguments.
156
+ def stop_on_unknown_option?(command_name) #:nodoc:
157
+ false
158
+ end
159
+
160
+ # If you want only strict string args (useful when cascading thor classes),
161
+ # call strict_args_position! This is disabled by default to allow dynamic
162
+ # invocations.
163
+ def strict_args_position!
164
+ @strict_args_position = true
165
+ end
166
+
167
+ def strict_args_position #:nodoc:
168
+ @strict_args_position ||= from_superclass(:strict_args_position, false)
169
+ end
170
+
171
+ def strict_args_position?(config) #:nodoc:
172
+ !!strict_args_position
173
+ end
174
+
125
175
  # Adds an argument to the class and creates an attr_accessor for it.
126
176
  #
127
177
  # Arguments are different from options in several aspects. The first one
128
178
  # is how they are parsed from the command line, arguments are retrieved
129
179
  # from position:
130
180
  #
131
- # thor task NAME
181
+ # thor command NAME
132
182
  #
133
183
  # Instead of:
134
184
  #
135
- # thor task --name=NAME
185
+ # thor command --name=NAME
136
186
  #
137
187
  # Besides, arguments are used inside your code as an accessor (self.argument),
138
188
  # while options are all kept in a hash (self.options).
@@ -157,9 +207,9 @@ class Thor
157
207
  # ==== Errors
158
208
  # ArgumentError:: Raised if you supply a required argument after a non required one.
159
209
  #
160
- def argument(name, options={})
210
+ def argument(name, options = {})
161
211
  is_thor_reserved_word?(name, :argument)
162
- no_tasks { attr_accessor name }
212
+ no_commands { attr_accessor name }
163
213
 
164
214
  required = if options.key?(:optional)
165
215
  !options[:optional]
@@ -171,14 +221,17 @@ class Thor
171
221
 
172
222
  remove_argument name
173
223
 
174
- arguments.each do |argument|
175
- next if argument.required?
176
- raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " <<
177
- "the non-required argument #{argument.human_name.inspect}."
178
- end if required
224
+ if required
225
+ arguments.each do |argument|
226
+ next if argument.required?
227
+ raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \
228
+ "the non-required argument #{argument.human_name.inspect}."
229
+ end
230
+ end
231
+
232
+ options[:required] = required
179
233
 
180
- arguments << Thor::Argument.new(name, options[:desc], required, options[:type],
181
- options[:default], options[:banner])
234
+ arguments << Thor::Argument.new(name, options)
182
235
  end
183
236
 
184
237
  # Returns this class arguments, looking up in the ancestors chain.
@@ -199,7 +252,7 @@ class Thor
199
252
  # ==== Parameters
200
253
  # Hash[Symbol => Object]
201
254
  #
202
- def class_options(options=nil)
255
+ def class_options(options = nil)
203
256
  @class_options ||= from_superclass(:class_options, {})
204
257
  build_options(options, @class_options) if options
205
258
  @class_options
@@ -212,22 +265,23 @@ class Thor
212
265
  # options<Hash>:: Described below.
213
266
  #
214
267
  # ==== Options
215
- # :desc - Description for the argument.
216
- # :required - If the argument is required or not.
217
- # :default - Default value for this argument.
218
- # :group - The group for this options. Use by class options to output options in different levels.
219
- # :aliases - Aliases for this option.
220
- # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
221
- # :banner - String to show on usage notes.
222
- #
223
- def class_option(name, options={})
268
+ # :desc:: -- Description for the argument.
269
+ # :required:: -- If the argument is required or not.
270
+ # :default:: -- Default value for this argument.
271
+ # :group:: -- The group for this options. Use by class options to output options in different levels.
272
+ # :aliases:: -- Aliases for this option. <b>Note:</b> Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead.
273
+ # :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
274
+ # :banner:: -- String to show on usage notes.
275
+ # :hide:: -- If you want to hide this option from the help.
276
+ #
277
+ def class_option(name, options = {})
224
278
  build_option(name, options, class_options)
225
279
  end
226
280
 
227
281
  # Removes a previous defined argument. If :undefine is given, undefine
228
282
  # accessors as well.
229
283
  #
230
- # ==== Paremeters
284
+ # ==== Parameters
231
285
  # names<Array>:: Arguments to be removed
232
286
  #
233
287
  # ==== Examples
@@ -246,7 +300,7 @@ class Thor
246
300
 
247
301
  # Removes a previous defined class option.
248
302
  #
249
- # ==== Paremeters
303
+ # ==== Parameters
250
304
  # names<Array>:: Class options to be removed
251
305
  #
252
306
  # ==== Examples
@@ -261,88 +315,91 @@ class Thor
261
315
  end
262
316
 
263
317
  # Defines the group. This is used when thor list is invoked so you can specify
264
- # that only tasks from a pre-defined group will be shown. Defaults to standard.
318
+ # that only commands from a pre-defined group will be shown. Defaults to standard.
265
319
  #
266
320
  # ==== Parameters
267
321
  # name<String|Symbol>
268
322
  #
269
- def group(name=nil)
270
- case name
271
- when nil
272
- @group ||= from_superclass(:group, 'standard')
273
- else
274
- @group = name.to_s
323
+ def group(name = nil)
324
+ if name
325
+ @group = name.to_s
326
+ else
327
+ @group ||= from_superclass(:group, "standard")
275
328
  end
276
329
  end
277
330
 
278
- # Returns the tasks for this Thor class.
331
+ # Returns the commands for this Thor class.
279
332
  #
280
333
  # ==== Returns
281
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
334
+ # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
282
335
  # objects as values.
283
336
  #
284
- def tasks
285
- @tasks ||= Thor::CoreExt::OrderedHash.new
337
+ def commands
338
+ @commands ||= Thor::CoreExt::OrderedHash.new
286
339
  end
340
+ alias_method :tasks, :commands
287
341
 
288
- # Returns the tasks for this Thor class and all subclasses.
342
+ # Returns the commands for this Thor class and all subclasses.
289
343
  #
290
344
  # ==== Returns
291
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
345
+ # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
292
346
  # objects as values.
293
347
  #
294
- def all_tasks
295
- @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new)
296
- @all_tasks.merge(tasks)
348
+ def all_commands
349
+ @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
350
+ @all_commands.merge!(commands)
297
351
  end
352
+ alias_method :all_tasks, :all_commands
298
353
 
299
- # Removes a given task from this Thor class. This is usually done if you
354
+ # Removes a given command from this Thor class. This is usually done if you
300
355
  # are inheriting from another class and don't want it to be available
301
356
  # anymore.
302
357
  #
303
- # By default it only remove the mapping to the task. But you can supply
358
+ # By default it only remove the mapping to the command. But you can supply
304
359
  # :undefine => true to undefine the method from the class as well.
305
360
  #
306
361
  # ==== Parameters
307
- # name<Symbol|String>:: The name of the task to be removed
308
- # options<Hash>:: You can give :undefine => true if you want tasks the method
362
+ # name<Symbol|String>:: The name of the command to be removed
363
+ # options<Hash>:: You can give :undefine => true if you want commands the method
309
364
  # to be undefined from the class as well.
310
365
  #
311
- def remove_task(*names)
366
+ def remove_command(*names)
312
367
  options = names.last.is_a?(Hash) ? names.pop : {}
313
368
 
314
369
  names.each do |name|
315
- tasks.delete(name.to_s)
316
- all_tasks.delete(name.to_s)
370
+ commands.delete(name.to_s)
371
+ all_commands.delete(name.to_s)
317
372
  undef_method name if options[:undefine]
318
373
  end
319
374
  end
375
+ alias_method :remove_task, :remove_command
320
376
 
321
- # All methods defined inside the given block are not added as tasks.
377
+ # All methods defined inside the given block are not added as commands.
322
378
  #
323
379
  # So you can do:
324
380
  #
325
381
  # class MyScript < Thor
326
- # no_tasks do
327
- # def this_is_not_a_task
382
+ # no_commands do
383
+ # def this_is_not_a_command
328
384
  # end
329
385
  # end
330
386
  # end
331
387
  #
332
- # You can also add the method and remove it from the task list:
388
+ # You can also add the method and remove it from the command list:
333
389
  #
334
390
  # class MyScript < Thor
335
- # def this_is_not_a_task
391
+ # def this_is_not_a_command
336
392
  # end
337
- # remove_task :this_is_not_a_task
393
+ # remove_command :this_is_not_a_command
338
394
  # end
339
395
  #
340
- def no_tasks
341
- @no_tasks = true
396
+ def no_commands
397
+ @no_commands = true
342
398
  yield
343
399
  ensure
344
- @no_tasks = false
400
+ @no_commands = false
345
401
  end
402
+ alias_method :no_tasks, :no_commands
346
403
 
347
404
  # Sets the namespace for the Thor or Thor::Group class. By default the
348
405
  # namespace is retrieved from the class name. If your Thor class is named
@@ -354,7 +411,7 @@ class Thor
354
411
  #
355
412
  # namespace :my_scripts
356
413
  #
357
- # You change how your tasks are invoked:
414
+ # You change how your commands are invoked:
358
415
  #
359
416
  # thor my_scripts -h
360
417
  #
@@ -362,218 +419,238 @@ class Thor
362
419
  #
363
420
  # namespace :default
364
421
  #
365
- # Your tasks can be invoked with a shortcut. Instead of:
422
+ # Your commands can be invoked with a shortcut. Instead of:
366
423
  #
367
- # thor :my_task
424
+ # thor :my_command
368
425
  #
369
- def namespace(name=nil)
370
- case name
371
- when nil
372
- @namespace ||= Thor::Util.namespace_from_thor_class(self)
373
- else
426
+ def namespace(name = nil)
427
+ if name
374
428
  @namespace = name.to_s
429
+ else
430
+ @namespace ||= Thor::Util.namespace_from_thor_class(self)
375
431
  end
376
432
  end
377
433
 
378
- # Parses the task and options from the given args, instantiate the class
379
- # and invoke the task. This method is used when the arguments must be parsed
434
+ # Parses the command and options from the given args, instantiate the class
435
+ # and invoke the command. This method is used when the arguments must be parsed
380
436
  # from an array. If you are inside Ruby and want to use a Thor class, you
381
437
  # can simply initialize it:
382
438
  #
383
439
  # script = MyScript.new(args, options, config)
384
- # script.invoke(:task, first_arg, second_arg, third_arg)
440
+ # script.invoke(:command, first_arg, second_arg, third_arg)
385
441
  #
386
- def start(given_args=ARGV, config={})
387
- self.debugging = given_args.delete("--debug")
442
+ def start(given_args = ARGV, config = {})
388
443
  config[:shell] ||= Thor::Base.shell.new
389
444
  dispatch(nil, given_args.dup, nil, config)
390
445
  rescue Thor::Error => e
391
- debugging ? (raise e) : config[:shell].error(e.message)
446
+ config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
392
447
  exit(1) if exit_on_failure?
448
+ rescue Errno::EPIPE
449
+ # This happens if a thor command is piped to something like `head`,
450
+ # which closes the pipe when it's done reading. This will also
451
+ # mean that if the pipe is closed, further unnecessary
452
+ # computation will not occur.
453
+ exit(0)
393
454
  end
394
455
 
395
- # Allows to use private methods from parent in child classes as tasks.
456
+ # Allows to use private methods from parent in child classes as commands.
396
457
  #
397
- # ==== Paremeters
398
- # names<Array>:: Method names to be used as tasks
458
+ # ==== Parameters
459
+ # names<Array>:: Method names to be used as commands
399
460
  #
400
461
  # ==== Examples
401
462
  #
402
- # public_task :foo
403
- # public_task :foo, :bar, :baz
463
+ # public_command :foo
464
+ # public_command :foo, :bar, :baz
404
465
  #
405
- def public_task(*names)
466
+ def public_command(*names)
406
467
  names.each do |name|
407
468
  class_eval "def #{name}(*); super end"
408
469
  end
409
470
  end
471
+ alias_method :public_task, :public_command
410
472
 
411
- def handle_no_task_error(task) #:nodoc:
412
- if $thor_runner
413
- raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
414
- else
415
- raise UndefinedTaskError, "Could not find task #{task.inspect}."
416
- end
473
+ def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
474
+ raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace
475
+ raise UndefinedCommandError, "Could not find command #{command.inspect}."
417
476
  end
418
-
419
- def handle_argument_error(task, error) #:nodoc:
420
- raise InvocationError, "#{task.name.inspect} was called incorrectly. Call as #{self.banner(task).inspect}."
477
+ alias_method :handle_no_task_error, :handle_no_command_error
478
+
479
+ def handle_argument_error(command, error, args, arity) #:nodoc:
480
+ msg = "ERROR: \"#{basename} #{command.name}\" was called with "
481
+ msg << "no arguments" if args.empty?
482
+ msg << "arguments " << args.inspect unless args.empty?
483
+ msg << "\nUsage: #{banner(command).inspect}"
484
+ raise InvocationError, msg
421
485
  end
422
486
 
423
- protected
487
+ protected
424
488
 
425
- # Prints the class options per group. If an option does not belong to
426
- # any group, it's printed as Class option.
427
- #
428
- def class_options_help(shell, groups={}) #:nodoc:
429
- # Group options by group
430
- class_options.each do |_, value|
431
- groups[value.group] ||= []
432
- groups[value.group] << value
433
- end
489
+ # Prints the class options per group. If an option does not belong to
490
+ # any group, it's printed as Class option.
491
+ #
492
+ def class_options_help(shell, groups = {}) #:nodoc:
493
+ # Group options by group
494
+ class_options.each do |_, value|
495
+ groups[value.group] ||= []
496
+ groups[value.group] << value
497
+ end
434
498
 
435
- # Deal with default group
436
- global_options = groups.delete(nil) || []
437
- print_options(shell, global_options)
499
+ # Deal with default group
500
+ global_options = groups.delete(nil) || []
501
+ print_options(shell, global_options)
438
502
 
439
- # Print all others
440
- groups.each do |group_name, options|
441
- print_options(shell, options, group_name)
442
- end
503
+ # Print all others
504
+ groups.each do |group_name, options|
505
+ print_options(shell, options, group_name)
443
506
  end
507
+ end
444
508
 
445
- # Receives a set of options and print them.
446
- def print_options(shell, options, group_name=nil)
447
- return if options.empty?
509
+ # Receives a set of options and print them.
510
+ def print_options(shell, options, group_name = nil)
511
+ return if options.empty?
448
512
 
449
- list = []
450
- padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
513
+ list = []
514
+ padding = options.map { |o| o.aliases.size }.max.to_i * 4
451
515
 
452
- options.each do |option|
453
- item = [ option.usage(padding) ]
454
- item.push(option.description ? "# #{option.description}" : "")
516
+ options.each do |option|
517
+ next if option.hide
518
+ item = [option.usage(padding)]
519
+ item.push(option.description ? "# #{option.description}" : "")
455
520
 
456
- list << item
457
- list << [ "", "# Default: #{option.default}" ] if option.show_default?
458
- end
459
-
460
- shell.say(group_name ? "#{group_name} options:" : "Options:")
461
- shell.print_table(list, :ident => 2)
462
- shell.say ""
521
+ list << item
522
+ list << ["", "# Default: #{option.default}"] if option.show_default?
523
+ list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum
463
524
  end
464
525
 
465
- # Raises an error if the word given is a Thor reserved word.
466
- def is_thor_reserved_word?(word, type) #:nodoc:
467
- return false unless THOR_RESERVED_WORDS.include?(word.to_s)
468
- raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
469
- end
526
+ shell.say(group_name ? "#{group_name} options:" : "Options:")
527
+ shell.print_table(list, :indent => 2)
528
+ shell.say ""
529
+ end
470
530
 
471
- # Build an option and adds it to the given scope.
472
- #
473
- # ==== Parameters
474
- # name<Symbol>:: The name of the argument.
475
- # options<Hash>:: Described in both class_option and method_option.
476
- def build_option(name, options, scope) #:nodoc:
477
- scope[name] = Thor::Option.new(name, options[:desc], options[:required],
478
- options[:type], options[:default], options[:banner],
479
- options[:lazy_default], options[:group], options[:aliases])
480
- end
531
+ # Raises an error if the word given is a Thor reserved word.
532
+ def is_thor_reserved_word?(word, type) #:nodoc:
533
+ return false unless THOR_RESERVED_WORDS.include?(word.to_s)
534
+ raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
535
+ end
481
536
 
482
- # Receives a hash of options, parse them and add to the scope. This is a
483
- # fast way to set a bunch of options:
484
- #
485
- # build_options :foo => true, :bar => :required, :baz => :string
486
- #
487
- # ==== Parameters
488
- # Hash[Symbol => Object]
489
- def build_options(options, scope) #:nodoc:
490
- options.each do |key, value|
491
- scope[key] = Thor::Option.parse(key, value)
492
- end
493
- end
537
+ # Build an option and adds it to the given scope.
538
+ #
539
+ # ==== Parameters
540
+ # name<Symbol>:: The name of the argument.
541
+ # options<Hash>:: Described in both class_option and method_option.
542
+ # scope<Hash>:: Options hash that is being built up
543
+ def build_option(name, options, scope) #:nodoc:
544
+ scope[name] = Thor::Option.new(name, options)
545
+ end
494
546
 
495
- # Finds a task with the given name. If the task belongs to the current
496
- # class, just return it, otherwise dup it and add the fresh copy to the
497
- # current task hash.
498
- def find_and_refresh_task(name) #:nodoc:
499
- task = if task = tasks[name.to_s]
500
- task
501
- elsif task = all_tasks[name.to_s]
502
- tasks[name.to_s] = task.clone
503
- else
504
- raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found."
505
- end
547
+ # Receives a hash of options, parse them and add to the scope. This is a
548
+ # fast way to set a bunch of options:
549
+ #
550
+ # build_options :foo => true, :bar => :required, :baz => :string
551
+ #
552
+ # ==== Parameters
553
+ # Hash[Symbol => Object]
554
+ def build_options(options, scope) #:nodoc:
555
+ options.each do |key, value|
556
+ scope[key] = Thor::Option.parse(key, value)
506
557
  end
558
+ end
507
559
 
508
- # Everytime someone inherits from a Thor class, register the klass
509
- # and file into baseclass.
510
- def inherited(klass)
511
- Thor::Base.register_klass_file(klass)
560
+ # Finds a command with the given name. If the command belongs to the current
561
+ # class, just return it, otherwise dup it and add the fresh copy to the
562
+ # current command hash.
563
+ def find_and_refresh_command(name) #:nodoc:
564
+ if commands[name.to_s]
565
+ commands[name.to_s]
566
+ elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition
567
+ commands[name.to_s] = command.clone
568
+ else
569
+ raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
512
570
  end
571
+ end
572
+ alias_method :find_and_refresh_task, :find_and_refresh_command
513
573
 
514
- # Fire this callback whenever a method is added. Added methods are
515
- # tracked as tasks by invoking the create_task method.
516
- def method_added(meth)
517
- meth = meth.to_s
574
+ # Everytime someone inherits from a Thor class, register the klass
575
+ # and file into baseclass.
576
+ def inherited(klass)
577
+ Thor::Base.register_klass_file(klass)
578
+ klass.instance_variable_set(:@no_commands, false)
579
+ end
518
580
 
519
- if meth == "initialize"
520
- initialize_added
521
- return
522
- end
581
+ # Fire this callback whenever a method is added. Added methods are
582
+ # tracked as commands by invoking the create_command method.
583
+ def method_added(meth)
584
+ meth = meth.to_s
523
585
 
524
- # Return if it's not a public instance method
525
- return unless public_instance_methods.include?(meth) ||
526
- public_instance_methods.include?(meth.to_sym)
586
+ if meth == "initialize"
587
+ initialize_added
588
+ return
589
+ end
527
590
 
528
- return if @no_tasks || !create_task(meth)
591
+ # Return if it's not a public instance method
592
+ return unless public_method_defined?(meth.to_sym)
529
593
 
530
- is_thor_reserved_word?(meth, :task)
531
- Thor::Base.register_klass_file(self)
532
- end
594
+ @no_commands ||= false
595
+ return if @no_commands || !create_command(meth)
596
+
597
+ is_thor_reserved_word?(meth, :command)
598
+ Thor::Base.register_klass_file(self)
599
+ end
533
600
 
534
- # Retrieves a value from superclass. If it reaches the baseclass,
535
- # returns default.
536
- def from_superclass(method, default=nil)
537
- if self == baseclass || !superclass.respond_to?(method, true)
538
- default
539
- else
540
- value = superclass.send(method)
541
- value.dup if value
601
+ # Retrieves a value from superclass. If it reaches the baseclass,
602
+ # returns default.
603
+ def from_superclass(method, default = nil)
604
+ if self == baseclass || !superclass.respond_to?(method, true)
605
+ default
606
+ else
607
+ value = superclass.send(method)
608
+
609
+ # Ruby implements `dup` on Object, but raises a `TypeError`
610
+ # if the method is called on immediates. As a result, we
611
+ # don't have a good way to check whether dup will succeed
612
+ # without calling it and rescuing the TypeError.
613
+ begin
614
+ value.dup
615
+ rescue TypeError
616
+ value
542
617
  end
543
- end
544
618
 
545
- # A flag that makes the process exit with status 1 if any error happens.
546
- def exit_on_failure?
547
- false
548
619
  end
620
+ end
549
621
 
550
- #
551
- # The basename of the program invoking the thor class.
552
- #
553
- def basename
554
- File.basename($0).split(' ').first
555
- end
622
+ # A flag that makes the process exit with status 1 if any error happens.
623
+ def exit_on_failure?
624
+ false
625
+ end
556
626
 
557
- # SIGNATURE: Sets the baseclass. This is where the superclass lookup
558
- # finishes.
559
- def baseclass #:nodoc:
560
- end
627
+ #
628
+ # The basename of the program invoking the thor class.
629
+ #
630
+ def basename
631
+ File.basename($PROGRAM_NAME).split(" ").first
632
+ end
561
633
 
562
- # SIGNATURE: Creates a new task if valid_task? is true. This method is
563
- # called when a new method is added to the class.
564
- def create_task(meth) #:nodoc:
565
- end
634
+ # SIGNATURE: Sets the baseclass. This is where the superclass lookup
635
+ # finishes.
636
+ def baseclass #:nodoc:
637
+ end
566
638
 
567
- # SIGNATURE: Defines behavior when the initialize method is added to the
568
- # class.
569
- def initialize_added #:nodoc:
570
- end
639
+ # SIGNATURE: Creates a new command if valid_command? is true. This method is
640
+ # called when a new method is added to the class.
641
+ def create_command(meth) #:nodoc:
642
+ end
643
+ alias_method :create_task, :create_command
571
644
 
572
- # SIGNATURE: The hook invoked by start.
573
- def dispatch(task, given_args, given_opts, config) #:nodoc:
574
- raise NotImplementedError
575
- end
645
+ # SIGNATURE: Defines behavior when the initialize method is added to the
646
+ # class.
647
+ def initialize_added #:nodoc:
648
+ end
576
649
 
650
+ # SIGNATURE: The hook invoked by start.
651
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
652
+ raise NotImplementedError
653
+ end
577
654
  end
578
655
  end
579
656
  end