thor 0.17.0 → 0.18.0

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 (70) hide show
  1. data/{CHANGELOG.rdoc → CHANGELOG.md} +30 -36
  2. data/README.md +10 -3
  3. data/lib/thor.rb +205 -180
  4. data/lib/thor/actions.rb +5 -5
  5. data/lib/thor/actions/create_link.rb +3 -0
  6. data/lib/thor/actions/directory.rb +2 -0
  7. data/lib/thor/actions/file_manipulation.rb +1 -1
  8. data/lib/thor/base.rb +84 -95
  9. data/lib/thor/{task.rb → command.rb} +17 -13
  10. data/lib/thor/core_ext/io_binary_read.rb +12 -0
  11. data/lib/thor/error.rb +4 -7
  12. data/lib/thor/group.rb +30 -33
  13. data/lib/thor/invocation.rb +28 -26
  14. data/lib/thor/parser/options.rb +3 -1
  15. data/lib/thor/runner.rb +21 -20
  16. data/lib/thor/shell/basic.rb +5 -1
  17. data/lib/thor/shell/color.rb +4 -0
  18. data/lib/thor/shell/html.rb +4 -0
  19. data/lib/thor/util.rb +214 -210
  20. data/lib/thor/version.rb +1 -1
  21. data/spec/actions/create_file_spec.rb +1 -1
  22. data/spec/actions/create_link_spec.rb +15 -1
  23. data/spec/actions/directory_spec.rb +18 -5
  24. data/spec/actions/empty_directory_spec.rb +1 -1
  25. data/spec/actions/file_manipulation_spec.rb +13 -13
  26. data/spec/actions/inject_into_file_spec.rb +1 -1
  27. data/spec/actions_spec.rb +1 -1
  28. data/spec/base_spec.rb +40 -24
  29. data/spec/command_spec.rb +80 -0
  30. data/spec/core_ext/hash_with_indifferent_access_spec.rb +1 -1
  31. data/spec/core_ext/ordered_hash_spec.rb +1 -1
  32. data/spec/exit_condition_spec.rb +3 -3
  33. data/spec/fixtures/{task.thor → command.thor} +0 -0
  34. data/spec/fixtures/doc/%file_name%.rb.tt +1 -0
  35. data/spec/fixtures/doc/COMMENTER +11 -0
  36. data/spec/fixtures/doc/README +3 -0
  37. data/spec/fixtures/doc/block_helper.rb +3 -0
  38. data/spec/fixtures/doc/config.rb +1 -0
  39. data/spec/fixtures/doc/config.yaml.tt +1 -0
  40. data/spec/fixtures/doc/excluding/%file_name%.rb.tt +1 -0
  41. data/spec/fixtures/group.thor +24 -10
  42. data/spec/fixtures/invoke.thor +3 -3
  43. data/spec/fixtures/script.thor +40 -15
  44. data/spec/fixtures/subcommand.thor +17 -0
  45. data/spec/group_spec.rb +13 -13
  46. data/spec/{spec_helper.rb → helper.rb} +11 -7
  47. data/spec/invocation_spec.rb +16 -16
  48. data/spec/parser/argument_spec.rb +4 -4
  49. data/spec/parser/arguments_spec.rb +1 -1
  50. data/spec/parser/option_spec.rb +1 -1
  51. data/spec/parser/options_spec.rb +6 -1
  52. data/spec/rake_compat_spec.rb +1 -1
  53. data/spec/register_spec.rb +12 -12
  54. data/spec/runner_spec.rb +36 -36
  55. data/spec/shell/basic_spec.rb +9 -10
  56. data/spec/shell/color_spec.rb +16 -2
  57. data/spec/shell/html_spec.rb +1 -1
  58. data/spec/shell_spec.rb +1 -1
  59. data/spec/subcommand_spec.rb +30 -0
  60. data/spec/thor_spec.rb +81 -78
  61. data/spec/util_spec.rb +10 -10
  62. data/thor.gemspec +22 -18
  63. metadata +49 -38
  64. data/.gitignore +0 -44
  65. data/.rspec +0 -3
  66. data/.travis.yml +0 -8
  67. data/Gemfile +0 -19
  68. data/bin/rake2thor +0 -86
  69. data/lib/thor/core_ext/file_binary_read.rb +0 -9
  70. data/spec/task_spec.rb +0 -80
@@ -1,6 +1,6 @@
1
1
  require 'fileutils'
2
2
  require 'uri'
3
- require 'thor/core_ext/file_binary_read'
3
+ require 'thor/core_ext/io_binary_read'
4
4
  require 'thor/actions/create_file'
5
5
  require 'thor/actions/create_link'
6
6
  require 'thor/actions/directory'
@@ -268,8 +268,8 @@ class Thor
268
268
  # switches.
269
269
  #
270
270
  # ==== Parameters
271
- # task<String>:: the task to be invoked
272
- # args<Array>:: arguments to the task
271
+ # command<String>:: the command to be invoked
272
+ # args<Array>:: arguments to the command
273
273
  # config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output.
274
274
  # Other options are given as parameter to Thor.
275
275
  #
@@ -282,13 +282,13 @@ class Thor
282
282
  # thor :list, :all => true, :substring => 'rails'
283
283
  # #=> thor list --all --substring=rails
284
284
  #
285
- def thor(task, *args)
285
+ def thor(command, *args)
286
286
  config = args.last.is_a?(Hash) ? args.pop : {}
287
287
  verbose = config.key?(:verbose) ? config.delete(:verbose) : true
288
288
  pretend = config.key?(:pretend) ? config.delete(:pretend) : false
289
289
  capture = config.key?(:capture) ? config.delete(:capture) : false
290
290
 
291
- args.unshift task
291
+ args.unshift(command)
292
292
  args.push Thor::Options.to_switches(config)
293
293
  command = args.join(' ').strip
294
294
 
@@ -52,6 +52,9 @@ class Thor
52
52
  given_destination
53
53
  end
54
54
 
55
+ def exists?
56
+ super || File.symlink?(destination)
57
+ end
55
58
  end
56
59
  end
57
60
  end
@@ -39,6 +39,7 @@ class Thor
39
39
  # config<Hash>:: give :verbose => false to not log the status.
40
40
  # If :recursive => false, does not look for paths recursively.
41
41
  # If :mode => :preserve, preserve the file mode from the source.
42
+ # If :exclude_pattern => /regexp/, prevents copying files that match that regexp.
42
43
  #
43
44
  # ==== Examples
44
45
  #
@@ -78,6 +79,7 @@ class Thor
78
79
 
79
80
  files(lookup).sort.each do |file_source|
80
81
  next if File.directory?(file_source)
82
+ next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
81
83
  file_destination = File.join(given_destination, file_source.gsub(source, '.'))
82
84
  file_destination.gsub!('/./', '/')
83
85
 
@@ -129,7 +129,7 @@ class Thor
129
129
  #
130
130
  # ==== Example
131
131
  #
132
- # chmod "script/*", 0755
132
+ # chmod "script/server", 0755
133
133
  #
134
134
  def chmod(path, mode, config={})
135
135
  return unless behavior == :invoke
@@ -1,10 +1,10 @@
1
+ require 'thor/command'
1
2
  require 'thor/core_ext/hash_with_indifferent_access'
2
3
  require 'thor/core_ext/ordered_hash'
3
4
  require 'thor/error'
4
- require 'thor/shell'
5
5
  require 'thor/invocation'
6
6
  require 'thor/parser'
7
- require 'thor/task'
7
+ require 'thor/shell'
8
8
  require 'thor/util'
9
9
 
10
10
  class Thor
@@ -47,8 +47,8 @@ class Thor
47
47
  # first two parameters.
48
48
 
49
49
  if options.is_a?(Array)
50
- task_options = config.delete(:task_options) # hook for start
51
- parse_options = parse_options.merge(task_options) if task_options
50
+ command_options = config.delete(:command_options) # hook for start
51
+ parse_options = parse_options.merge(command_options) if command_options
52
52
  array_options, hash_options = options, {}
53
53
  else
54
54
  # Handle the case where the class was explicitly instantiated
@@ -59,9 +59,10 @@ class Thor
59
59
  # Let Thor::Options parse the options first, so it can remove
60
60
  # declared options from the array. This will leave us with
61
61
  # a list of arguments that weren't declared.
62
- stop_on_unknown = self.class.stop_on_unknown_option? config[:current_task]
62
+ stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
63
63
  opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown)
64
64
  self.options = opts.parse(array_options)
65
+ self.options = config[:class_options].merge(self.options) if config[:class_options]
65
66
 
66
67
  # If unknown options are disallowed, make sure that none of the
67
68
  # remaining arguments looks like an option.
@@ -118,18 +119,6 @@ class Thor
118
119
  end
119
120
 
120
121
  module ClassMethods
121
- def attr_reader(*) #:nodoc:
122
- no_tasks { super }
123
- end
124
-
125
- def attr_writer(*) #:nodoc:
126
- no_tasks { super }
127
- end
128
-
129
- def attr_accessor(*) #:nodoc:
130
- no_tasks { super }
131
- end
132
-
133
122
  # If you want to raise an error for unknown options, call check_unknown_options!
134
123
  # This is disabled by default to allow dynamic invocations.
135
124
  def check_unknown_options!
@@ -146,8 +135,8 @@ class Thor
146
135
 
147
136
  # If true, option parsing is suspended as soon as an unknown option or a
148
137
  # regular argument is encountered. All remaining arguments are passed to
149
- # the task as regular arguments.
150
- def stop_on_unknown_option?(task_name) #:nodoc:
138
+ # the command as regular arguments.
139
+ def stop_on_unknown_option?(command_name) #:nodoc:
151
140
  false
152
141
  end
153
142
 
@@ -172,11 +161,11 @@ class Thor
172
161
  # is how they are parsed from the command line, arguments are retrieved
173
162
  # from position:
174
163
  #
175
- # thor task NAME
164
+ # thor command NAME
176
165
  #
177
166
  # Instead of:
178
167
  #
179
- # thor task --name=NAME
168
+ # thor command --name=NAME
180
169
  #
181
170
  # Besides, arguments are used inside your code as an accessor (self.argument),
182
171
  # while options are all kept in a hash (self.options).
@@ -203,7 +192,7 @@ class Thor
203
192
  #
204
193
  def argument(name, options={})
205
194
  is_thor_reserved_word?(name, :argument)
206
- no_tasks { attr_accessor name }
195
+ no_commands { attr_accessor name }
207
196
 
208
197
  required = if options.key?(:optional)
209
198
  !options[:optional]
@@ -307,7 +296,7 @@ class Thor
307
296
  end
308
297
 
309
298
  # Defines the group. This is used when thor list is invoked so you can specify
310
- # that only tasks from a pre-defined group will be shown. Defaults to standard.
299
+ # that only commands from a pre-defined group will be shown. Defaults to standard.
311
300
  #
312
301
  # ==== Parameters
313
302
  # name<String|Symbol>
@@ -321,74 +310,78 @@ class Thor
321
310
  end
322
311
  end
323
312
 
324
- # Returns the tasks for this Thor class.
313
+ # Returns the commands for this Thor class.
325
314
  #
326
315
  # ==== Returns
327
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
316
+ # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
328
317
  # objects as values.
329
318
  #
330
- def tasks
331
- @tasks ||= Thor::CoreExt::OrderedHash.new
319
+ def commands
320
+ @commands ||= Thor::CoreExt::OrderedHash.new
332
321
  end
322
+ alias tasks commands
333
323
 
334
- # Returns the tasks for this Thor class and all subclasses.
324
+ # Returns the commands for this Thor class and all subclasses.
335
325
  #
336
326
  # ==== Returns
337
- # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
327
+ # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
338
328
  # objects as values.
339
329
  #
340
- def all_tasks
341
- @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new)
342
- @all_tasks.merge(tasks)
330
+ def all_commands
331
+ @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
332
+ @all_commands.merge(commands)
343
333
  end
334
+ alias all_tasks all_commands
344
335
 
345
- # Removes a given task from this Thor class. This is usually done if you
336
+ # Removes a given command from this Thor class. This is usually done if you
346
337
  # are inheriting from another class and don't want it to be available
347
338
  # anymore.
348
339
  #
349
- # By default it only remove the mapping to the task. But you can supply
340
+ # By default it only remove the mapping to the command. But you can supply
350
341
  # :undefine => true to undefine the method from the class as well.
351
342
  #
352
343
  # ==== Parameters
353
- # name<Symbol|String>:: The name of the task to be removed
354
- # options<Hash>:: You can give :undefine => true if you want tasks the method
344
+ # name<Symbol|String>:: The name of the command to be removed
345
+ # options<Hash>:: You can give :undefine => true if you want commands the method
355
346
  # to be undefined from the class as well.
356
347
  #
357
- def remove_task(*names)
348
+ def remove_command(*names)
358
349
  options = names.last.is_a?(Hash) ? names.pop : {}
359
350
 
360
351
  names.each do |name|
361
- tasks.delete(name.to_s)
362
- all_tasks.delete(name.to_s)
352
+ commands.delete(name.to_s)
353
+ all_commands.delete(name.to_s)
363
354
  undef_method name if options[:undefine]
364
355
  end
365
356
  end
357
+ alias remove_task remove_command
366
358
 
367
- # All methods defined inside the given block are not added as tasks.
359
+ # All methods defined inside the given block are not added as commands.
368
360
  #
369
361
  # So you can do:
370
362
  #
371
363
  # class MyScript < Thor
372
- # no_tasks do
373
- # def this_is_not_a_task
364
+ # no_commands do
365
+ # def this_is_not_a_command
374
366
  # end
375
367
  # end
376
368
  # end
377
369
  #
378
- # You can also add the method and remove it from the task list:
370
+ # You can also add the method and remove it from the command list:
379
371
  #
380
372
  # class MyScript < Thor
381
- # def this_is_not_a_task
373
+ # def this_is_not_a_command
382
374
  # end
383
- # remove_task :this_is_not_a_task
375
+ # remove_command :this_is_not_a_command
384
376
  # end
385
377
  #
386
- def no_tasks
387
- @no_tasks = true
378
+ def no_commands
379
+ @no_commands = true
388
380
  yield
389
381
  ensure
390
- @no_tasks = false
382
+ @no_commands = false
391
383
  end
384
+ alias no_tasks no_commands
392
385
 
393
386
  # Sets the namespace for the Thor or Thor::Group class. By default the
394
387
  # namespace is retrieved from the class name. If your Thor class is named
@@ -400,7 +393,7 @@ class Thor
400
393
  #
401
394
  # namespace :my_scripts
402
395
  #
403
- # You change how your tasks are invoked:
396
+ # You change how your commands are invoked:
404
397
  #
405
398
  # thor my_scripts -h
406
399
  #
@@ -408,9 +401,9 @@ class Thor
408
401
  #
409
402
  # namespace :default
410
403
  #
411
- # Your tasks can be invoked with a shortcut. Instead of:
404
+ # Your commands can be invoked with a shortcut. Instead of:
412
405
  #
413
- # thor :my_task
406
+ # thor :my_command
414
407
  #
415
408
  def namespace(name=nil)
416
409
  @namespace = case name
@@ -421,13 +414,13 @@ class Thor
421
414
  end
422
415
  end
423
416
 
424
- # Parses the task and options from the given args, instantiate the class
425
- # and invoke the task. This method is used when the arguments must be parsed
417
+ # Parses the command and options from the given args, instantiate the class
418
+ # and invoke the command. This method is used when the arguments must be parsed
426
419
  # from an array. If you are inside Ruby and want to use a Thor class, you
427
420
  # can simply initialize it:
428
421
  #
429
422
  # script = MyScript.new(args, options, config)
430
- # script.invoke(:task, first_arg, second_arg, third_arg)
423
+ # script.invoke(:command, first_arg, second_arg, third_arg)
431
424
  #
432
425
  def start(given_args=ARGV, config={})
433
426
  config[:shell] ||= Thor::Base.shell.new
@@ -436,52 +429,44 @@ class Thor
436
429
  ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
437
430
  exit(1) if exit_on_failure?
438
431
  rescue Errno::EPIPE
439
- # This happens if a thor task is piped to something like `head`,
432
+ # This happens if a thor command is piped to something like `head`,
440
433
  # which closes the pipe when it's done reading. This will also
441
434
  # mean that if the pipe is closed, further unnecessary
442
435
  # computation will not occur.
443
436
  exit(0)
444
437
  end
445
438
 
446
- # Allows to use private methods from parent in child classes as tasks.
439
+ # Allows to use private methods from parent in child classes as commands.
447
440
  #
448
441
  # ==== Parameters
449
- # names<Array>:: Method names to be used as tasks
442
+ # names<Array>:: Method names to be used as commands
450
443
  #
451
444
  # ==== Examples
452
445
  #
453
- # public_task :foo
454
- # public_task :foo, :bar, :baz
446
+ # public_command :foo
447
+ # public_command :foo, :bar, :baz
455
448
  #
456
- def public_task(*names)
449
+ def public_command(*names)
457
450
  names.each do |name|
458
451
  class_eval "def #{name}(*); super end"
459
452
  end
460
453
  end
454
+ alias public_task public_command
461
455
 
462
- def handle_no_task_error(task, has_namespace = $thor_runner) #:nodoc:
456
+ def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
463
457
  if has_namespace
464
- raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
458
+ raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace."
465
459
  else
466
- raise UndefinedTaskError, "Could not find task #{task.inspect}."
460
+ raise UndefinedCommandError, "Could not find command #{command.inspect}."
467
461
  end
468
462
  end
463
+ alias handle_no_task_error handle_no_command_error
469
464
 
470
- def handle_argument_error(task, error, arity=nil) #:nodoc:
471
- msg = "#{basename} #{task.name}"
472
- if arity
473
- required = arity < 0 ? (-1 - arity) : arity
474
- if required == 0
475
- msg << " should have no arguments"
476
- else
477
- msg << " requires at least #{required} argument"
478
- msg << "s" if required > 1
479
- end
480
- else
481
- msg = "call #{msg} as"
482
- end
483
-
484
- msg << ": #{self.banner(task).inspect}."
465
+ def handle_argument_error(command, error, args, arity) #:nodoc:
466
+ msg = "ERROR: #{basename} #{command.name} was called with "
467
+ msg << 'no arguments' if args.empty?
468
+ msg << 'arguments ' << args.inspect if !args.empty?
469
+ msg << "\nUsage: #{self.banner(command).inspect}."
485
470
  raise InvocationError, msg
486
471
  end
487
472
 
@@ -559,28 +544,29 @@ class Thor
559
544
  end
560
545
  end
561
546
 
562
- # Finds a task with the given name. If the task belongs to the current
547
+ # Finds a command with the given name. If the command belongs to the current
563
548
  # class, just return it, otherwise dup it and add the fresh copy to the
564
- # current task hash.
565
- def find_and_refresh_task(name) #:nodoc:
566
- task = if task = tasks[name.to_s]
567
- task
568
- elsif task = all_tasks[name.to_s]
569
- tasks[name.to_s] = task.clone
549
+ # current command hash.
550
+ def find_and_refresh_command(name) #:nodoc:
551
+ command = if command = commands[name.to_s]
552
+ command
553
+ elsif command = all_commands[name.to_s]
554
+ commands[name.to_s] = command.clone
570
555
  else
571
- raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found."
556
+ raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
572
557
  end
573
558
  end
559
+ alias find_and_refresh_task find_and_refresh_command
574
560
 
575
561
  # Everytime someone inherits from a Thor class, register the klass
576
562
  # and file into baseclass.
577
563
  def inherited(klass)
578
564
  Thor::Base.register_klass_file(klass)
579
- klass.instance_variable_set(:@no_tasks, false)
565
+ klass.instance_variable_set(:@no_commands, false)
580
566
  end
581
567
 
582
568
  # Fire this callback whenever a method is added. Added methods are
583
- # tracked as tasks by invoking the create_task method.
569
+ # tracked as commands by invoking the create_command method.
584
570
  def method_added(meth)
585
571
  meth = meth.to_s
586
572
 
@@ -590,12 +576,14 @@ class Thor
590
576
  end
591
577
 
592
578
  # Return if it's not a public instance method
593
- return unless public_instance_methods.include?(meth) ||
594
- public_instance_methods.include?(meth.to_sym)
579
+ return unless public_method_defined?(meth.to_sym)
580
+
581
+ # Return if attr_* added the method
582
+ return if caller.first.to_s[/`attr_(reader|writer|accessor)'/]
595
583
 
596
- return if @no_tasks || !create_task(meth)
584
+ return if @no_commands || !create_command(meth)
597
585
 
598
- is_thor_reserved_word?(meth, :task)
586
+ is_thor_reserved_word?(meth, :command)
599
587
  Thor::Base.register_klass_file(self)
600
588
  end
601
589
 
@@ -634,10 +622,11 @@ class Thor
634
622
  def baseclass #:nodoc:
635
623
  end
636
624
 
637
- # SIGNATURE: Creates a new task if valid_task? is true. This method is
625
+ # SIGNATURE: Creates a new command if valid_command? is true. This method is
638
626
  # called when a new method is added to the class.
639
- def create_task(meth) #:nodoc:
627
+ def create_command(meth) #:nodoc:
640
628
  end
629
+ alias create_task create_command
641
630
 
642
631
  # SIGNATURE: Defines behavior when the initialize method is added to the
643
632
  # class.
@@ -645,7 +634,7 @@ class Thor
645
634
  end
646
635
 
647
636
  # SIGNATURE: The hook invoked by start.
648
- def dispatch(task, given_args, given_opts, config) #:nodoc:
637
+ def dispatch(command, given_args, given_opts, config) #:nodoc:
649
638
  raise NotImplementedError
650
639
  end
651
640