thor 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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