josevalim-thor 0.10.10 → 0.10.11
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/thor/actions.rb +3 -3
- data/lib/thor/base.rb +91 -224
- data/lib/thor/group.rb +24 -20
- data/lib/thor/invocation.rb +148 -0
- data/lib/thor/options.rb +1 -1
- data/lib/thor/shell/basic.rb +20 -22
- data/lib/thor/shell.rb +71 -0
- data/lib/thor.rb +12 -17
- metadata +4 -2
data/lib/thor/actions.rb
CHANGED
@@ -306,10 +306,10 @@ class Thor
|
|
306
306
|
|
307
307
|
protected
|
308
308
|
|
309
|
-
#
|
309
|
+
# Allow current root to be sent as configuration value to the invoked class.
|
310
310
|
#
|
311
|
-
def
|
312
|
-
super.merge!(:
|
311
|
+
def _overrides_config #:nodoc:
|
312
|
+
super.merge!(:root => self.root)
|
313
313
|
end
|
314
314
|
|
315
315
|
end
|
data/lib/thor/base.rb
CHANGED
@@ -1,64 +1,81 @@
|
|
1
1
|
require 'thor/core_ext/hash_with_indifferent_access'
|
2
2
|
require 'thor/core_ext/ordered_hash'
|
3
|
-
require 'thor/shell/basic'
|
4
3
|
require 'thor/error'
|
4
|
+
require 'thor/shell'
|
5
|
+
require 'thor/invocation'
|
5
6
|
require 'thor/options'
|
6
7
|
require 'thor/task'
|
7
8
|
require 'thor/util'
|
8
9
|
|
9
10
|
class Thor
|
10
11
|
HELP_MAPPINGS = %w(-h -? --help -D)
|
11
|
-
|
12
|
+
THOR_RESERVED_WORDS = %w(all invoke shell options behavior root destination_root relative_root source_root)
|
12
13
|
|
13
14
|
class Maxima < Struct.new(:usage, :options, :class_options)
|
14
15
|
end
|
15
16
|
|
16
17
|
module Base
|
18
|
+
attr_accessor :options
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
base.send :include, SingletonMethods
|
21
|
-
end
|
22
|
-
|
23
|
-
# Returns the classes that inherits from Thor or Thor::Group.
|
20
|
+
# It receives arguments in an Array and two hashes, one for options and
|
21
|
+
# other for configuration.
|
24
22
|
#
|
25
|
-
#
|
26
|
-
#
|
23
|
+
# Notice that it does not check arguments type neither if all required
|
24
|
+
# arguments were supplied. It should be done by the parser.
|
27
25
|
#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# Returns the files where the subclasses are kept.
|
26
|
+
# ==== Parameters
|
27
|
+
# args<Array[Object]>:: An array of objects. The objects are applied to their
|
28
|
+
# respective accessors declared with <tt>argument</tt>.
|
33
29
|
#
|
34
|
-
#
|
35
|
-
#
|
30
|
+
# options<Hash>:: An options hash that will be available as self.options.
|
31
|
+
# The hash given is converted to a hash with indifferent
|
32
|
+
# access, magic predicates (options.skip?) and then frozen.
|
36
33
|
#
|
37
|
-
|
38
|
-
@subclass_files ||= Hash.new{ |h,k| h[k] = [] }
|
39
|
-
end
|
40
|
-
|
41
|
-
# Returns the shell used in all Thor classes.
|
34
|
+
# config<Hash>:: Configuration for this Thor class.
|
42
35
|
#
|
43
|
-
def
|
44
|
-
|
45
|
-
|
36
|
+
def initialize(args=[], options={}, config={})
|
37
|
+
self.class.arguments.zip(args).each do |argument, value|
|
38
|
+
send("#{argument.human_name}=", value)
|
39
|
+
end
|
46
40
|
|
47
|
-
|
48
|
-
#
|
49
|
-
def self.shell=(klass)
|
50
|
-
@shell = klass
|
41
|
+
self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).freeze
|
51
42
|
end
|
52
43
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
class << self
|
45
|
+
def included(base) #:nodoc:
|
46
|
+
base.send :extend, ClassMethods
|
47
|
+
base.send :include, Invocation
|
48
|
+
base.send :include, Shell
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the classes that inherits from Thor or Thor::Group.
|
52
|
+
#
|
53
|
+
# ==== Returns
|
54
|
+
# Array[Class]
|
55
|
+
#
|
56
|
+
def subclasses
|
57
|
+
@subclasses ||= []
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the files where the subclasses are kept.
|
61
|
+
#
|
62
|
+
# ==== Returns
|
63
|
+
# Hash[path<String> => Class]
|
64
|
+
#
|
65
|
+
def subclass_files
|
66
|
+
@subclass_files ||= Hash.new{ |h,k| h[k] = [] }
|
67
|
+
end
|
59
68
|
|
60
|
-
|
61
|
-
|
69
|
+
# Whenever a class inherits from Thor or Thor::Group, we should track the
|
70
|
+
# class and the file on Thor::Base. This is the method responsable for it.
|
71
|
+
#
|
72
|
+
def register_klass_file(klass) #:nodoc:
|
73
|
+
file = caller[1].match(/(.*):\d+/)[1]
|
74
|
+
Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass)
|
75
|
+
|
76
|
+
file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
|
77
|
+
file_subclasses << klass unless file_subclasses.include?(klass)
|
78
|
+
end
|
62
79
|
end
|
63
80
|
|
64
81
|
module ClassMethods
|
@@ -97,6 +114,7 @@ class Thor
|
|
97
114
|
# ArgumentError:: Raised if you supply a required argument after a non required one.
|
98
115
|
#
|
99
116
|
def argument(name, options={})
|
117
|
+
is_thor_reserved_word?(name, :argument)
|
100
118
|
no_tasks { attr_accessor name }
|
101
119
|
|
102
120
|
required = if options.key?(:optional)
|
@@ -308,6 +326,26 @@ class Thor
|
|
308
326
|
end
|
309
327
|
end
|
310
328
|
|
329
|
+
# Parses the task and options from the given args, instantiate the class
|
330
|
+
# and invoke the task. This method is used when the arguments must be parsed
|
331
|
+
# from an array. If you are inside Ruby and want to use a Thor class, you
|
332
|
+
# can simply initialize it:
|
333
|
+
#
|
334
|
+
# script = MyScript.new(args, options, config)
|
335
|
+
# script.invoke(:task, first_arg, second_arg, third_arg)
|
336
|
+
#
|
337
|
+
def start(args=ARGV, config={})
|
338
|
+
config[:shell] ||= Thor::Base.shell.new
|
339
|
+
|
340
|
+
task = normalize_arguments(args, config)
|
341
|
+
return unless task
|
342
|
+
|
343
|
+
instance, trailing = prepare(task, args, config)
|
344
|
+
instance.invoke(task, trailing)
|
345
|
+
rescue Thor::Error => e
|
346
|
+
config[:shell].error e.message
|
347
|
+
end
|
348
|
+
|
311
349
|
protected
|
312
350
|
|
313
351
|
# Prints the class optins per group. If a class options does not belong
|
@@ -350,6 +388,13 @@ class Thor
|
|
350
388
|
end
|
351
389
|
end
|
352
390
|
|
391
|
+
# Raises an error if the word given is a Thor reserved word.
|
392
|
+
#
|
393
|
+
def is_thor_reserved_word?(word, type)
|
394
|
+
return false unless THOR_RESERVED_WORDS.include?(word.to_s)
|
395
|
+
raise ScriptError, "'#{word}' is a Thor reserved word and cannot be defined as #{type}"
|
396
|
+
end
|
397
|
+
|
353
398
|
# Build an option and adds it to the given scope.
|
354
399
|
#
|
355
400
|
# ==== Parameters
|
@@ -408,11 +453,7 @@ class Thor
|
|
408
453
|
end
|
409
454
|
|
410
455
|
return if @no_tasks || !valid_task?(meth)
|
411
|
-
|
412
|
-
if RESERVED_TASK_NAMES.include?(meth)
|
413
|
-
raise ScriptError, "'#{meth}' is a Thor reserved word and cannot be defined as task"
|
414
|
-
end
|
415
|
-
|
456
|
+
is_thor_reserved_word?(meth, :task)
|
416
457
|
Thor::Base.register_klass_file(self)
|
417
458
|
create_task(meth)
|
418
459
|
end
|
@@ -445,192 +486,18 @@ class Thor
|
|
445
486
|
# class.
|
446
487
|
def initialize_added #:nodoc:
|
447
488
|
end
|
448
|
-
end
|
449
|
-
|
450
|
-
module SingletonMethods
|
451
|
-
attr_accessor :options
|
452
|
-
|
453
|
-
SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
|
454
489
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
# arguments were supplied. It should be done by the parser.
|
460
|
-
#
|
461
|
-
# ==== Parameters
|
462
|
-
# args<Array[Object]>:: An array of objects. The objects are applied to their
|
463
|
-
# respective accessors declared with <tt>argument</tt>.
|
464
|
-
#
|
465
|
-
# options<Hash>:: An options hash that will be available as self.options.
|
466
|
-
# The hash given is converted to a hash with indifferent
|
467
|
-
# access, magic predicates (options.skip?) and then frozen.
|
468
|
-
#
|
469
|
-
# config<Hash>:: Configuration for this Thor class.
|
470
|
-
#
|
471
|
-
# ==== Configuration
|
472
|
-
# shell<Object>:: An instance of the shell to be used.
|
473
|
-
#
|
474
|
-
# ==== Examples
|
475
|
-
#
|
476
|
-
# class MyScript < Thor
|
477
|
-
# argument :first, :type => :numeric
|
478
|
-
# end
|
479
|
-
#
|
480
|
-
# MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
|
481
|
-
#
|
482
|
-
def initialize(args=[], options={}, config={})
|
483
|
-
self.class.arguments.zip(args).each do |argument, value|
|
484
|
-
send("#{argument.human_name}=", value)
|
490
|
+
# SIGNATURE: Normalize arguments when invoked through start. Should
|
491
|
+
# return the name of the task to be invoked. Returning nil makes the
|
492
|
+
# start process to exist without error message.
|
493
|
+
def normalize_arguments(args, config) #:nodoc:
|
485
494
|
end
|
486
495
|
|
487
|
-
|
488
|
-
|
489
|
-
#
|
490
|
-
|
491
|
-
self.shell.base ||= self if self.shell.respond_to?(:base)
|
492
|
-
end
|
493
|
-
|
494
|
-
# Holds the shell for the given Thor instance. If no shell is given,
|
495
|
-
# it gets a default shell from Thor::Base.shell.
|
496
|
-
#
|
497
|
-
def shell
|
498
|
-
@shell ||= Thor::Base.shell.new
|
499
|
-
end
|
500
|
-
|
501
|
-
# Sets the shell for this thor class.
|
502
|
-
#
|
503
|
-
def shell=(shell)
|
504
|
-
@shell = shell
|
505
|
-
end
|
506
|
-
|
507
|
-
# Common methods that are delegated to the shell.
|
508
|
-
#
|
509
|
-
SHELL_DELEGATED_METHODS.each do |method|
|
510
|
-
module_eval <<-METHOD, __FILE__, __LINE__
|
511
|
-
def #{method}(*args)
|
512
|
-
shell.#{method}(*args)
|
513
|
-
end
|
514
|
-
METHOD
|
515
|
-
end
|
516
|
-
|
517
|
-
# Receives a name and invokes it. The name can be either a namespaced name,
|
518
|
-
# a current class task or even a class. Arguments are given in an array and
|
519
|
-
# options given are merged with the invoker options.
|
520
|
-
#
|
521
|
-
# ==== Examples
|
522
|
-
#
|
523
|
-
# class A < Thor
|
524
|
-
# def foo
|
525
|
-
# invoke :bar
|
526
|
-
# invoke "b:lib", ["merb", "rails"]
|
527
|
-
# end
|
528
|
-
#
|
529
|
-
# def bar
|
530
|
-
# invoke "b:lib", ["merb", "rails"]
|
531
|
-
# # magic
|
532
|
-
# end
|
533
|
-
# end
|
534
|
-
#
|
535
|
-
# class B < Thor
|
536
|
-
# argument :preferred_framework, :type => :string
|
537
|
-
#
|
538
|
-
# def lib(second_framework)
|
539
|
-
# # magic
|
540
|
-
# end
|
541
|
-
# end
|
542
|
-
#
|
543
|
-
# You can notice that the method "foo" above invokes two tasks: "bar",
|
544
|
-
# which belongs to the same class and "lib" that belongs to the class B.
|
545
|
-
#
|
546
|
-
# By using an invocation system you ensure that a task is invoked only once.
|
547
|
-
# In the example above, invoking foo will invoke "b:lib" just once, even if
|
548
|
-
# it's invoked later by "bar" method.
|
549
|
-
#
|
550
|
-
# When invoking another class, there are a few things to keep in mind:
|
551
|
-
#
|
552
|
-
# 1) Class arguments are parsed first. In the example above, preferred
|
553
|
-
# framework is going to consume "merb" and second framework is going
|
554
|
-
# to be set to "rails".
|
555
|
-
#
|
556
|
-
# 2) All options and configurations are sent to the invoked class.
|
557
|
-
# So the invoked class is going to use the same shell instance, will
|
558
|
-
# have the same behavior (:invoke or :revoke) and so on.
|
559
|
-
#
|
560
|
-
# Invoking a Thor::Group happens in the same away as above:
|
561
|
-
#
|
562
|
-
# class C < Thor::Group
|
563
|
-
# def one
|
564
|
-
# end
|
565
|
-
# end
|
566
|
-
#
|
567
|
-
# Is invoked as:
|
568
|
-
#
|
569
|
-
# invoke "c"
|
570
|
-
#
|
571
|
-
# Or even as:
|
572
|
-
#
|
573
|
-
# invoke C
|
574
|
-
#
|
575
|
-
def invoke(name, method_args=[], options={})
|
576
|
-
@_invocations ||= Hash.new { |h,k| h[k] = [] }
|
577
|
-
instance, task = _setup_for_invoke(name, method_args, options)
|
578
|
-
|
579
|
-
current = @_invocations[instance.class]
|
580
|
-
return if current.include?("all")
|
581
|
-
|
582
|
-
if task
|
583
|
-
task = self.class.all_tasks[task.to_s] || Task.dynamic(task) unless task.is_a?(Thor::Task)
|
584
|
-
return if current.include?(task.name)
|
585
|
-
|
586
|
-
current << task.name
|
587
|
-
task.run(instance, method_args)
|
588
|
-
else
|
589
|
-
current << "all"
|
590
|
-
instance.class.all_tasks.collect { |_, task| task.run(instance) }
|
496
|
+
# SIGNATURE: Receives a task, arguments to be parsed and configuration
|
497
|
+
# values and initializes the current class. Trailing arguments are
|
498
|
+
# returned to be sent to the invoked task.
|
499
|
+
def prepare(task, args, config) #:nodoc:
|
591
500
|
end
|
592
|
-
end
|
593
|
-
|
594
|
-
protected
|
595
|
-
|
596
|
-
# This is the method responsable for retrieving and setting up an
|
597
|
-
# instance to be used in invoke.
|
598
|
-
#
|
599
|
-
def _setup_for_invoke(name, method_args, options) #:nodoc:
|
600
|
-
case name
|
601
|
-
when NilClass, Thor::Task
|
602
|
-
# Do nothing, we already have what we want
|
603
|
-
when Class
|
604
|
-
klass = name
|
605
|
-
else
|
606
|
-
name = name.to_s
|
607
|
-
unless self.class.all_tasks[name]
|
608
|
-
klass, task = Thor::Util.namespace_to_thor_class(name) rescue Thor::Error
|
609
|
-
end
|
610
|
-
end
|
611
|
-
|
612
|
-
if klass.nil?
|
613
|
-
return self, name
|
614
|
-
elsif klass <= Thor::Base
|
615
|
-
size = klass.arguments.size
|
616
|
-
class_args = method_args.slice!(0, size)
|
617
|
-
instance = klass.new(class_args, self.options.merge(options), _dump_config)
|
618
|
-
|
619
|
-
task ||= klass.default_task if klass <= Thor
|
620
|
-
instance.instance_variable_set("@_invocations", @_invocations)
|
621
|
-
|
622
|
-
return instance, task
|
623
|
-
else
|
624
|
-
raise ScriptError, "Expected Thor class, got #{klass}"
|
625
|
-
end
|
626
|
-
end
|
627
|
-
|
628
|
-
# Dump the configuration values for this current class.
|
629
|
-
#
|
630
|
-
def _dump_config #:nodoc:
|
631
|
-
{ :shell => self.shell }
|
632
|
-
end
|
633
|
-
|
634
501
|
end
|
635
502
|
end
|
636
503
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -3,8 +3,8 @@ class Thor::Group
|
|
3
3
|
class << self
|
4
4
|
|
5
5
|
# The descrition for this Thor::Group. If none is provided, but a source root
|
6
|
-
# exists
|
7
|
-
#
|
6
|
+
# exists, tries to find the USAGE one folder above it, otherwise searches
|
7
|
+
# in the superclass.
|
8
8
|
#
|
9
9
|
# ==== Parameters
|
10
10
|
# description<String>:: The description for this Thor::Group.
|
@@ -12,8 +12,9 @@ class Thor::Group
|
|
12
12
|
def desc(description=nil)
|
13
13
|
case description
|
14
14
|
when nil
|
15
|
-
|
16
|
-
|
15
|
+
usage = File.join(source_root, "..", "USAGE") if respond_to?(:source_root)
|
16
|
+
@desc ||= if usage && File.exist?(usage)
|
17
|
+
File.read(usage)
|
17
18
|
else
|
18
19
|
from_superclass(:desc, nil)
|
19
20
|
end
|
@@ -22,22 +23,17 @@ class Thor::Group
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
#
|
26
|
-
# class and does not have to parse task options.
|
26
|
+
# Implements the prepare interface being used by start.
|
27
27
|
#
|
28
|
-
def
|
29
|
-
|
28
|
+
def prepare(task, args, config) #:nodoc:
|
29
|
+
opts = Thor::Options.new(class_options)
|
30
|
+
opts.parse(args)
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
opts = Thor::Options.new(class_options)
|
35
|
-
opts.parse(args)
|
36
|
-
|
37
|
-
new(opts.arguments, opts.options, config).invoke(:all)
|
32
|
+
instance = new(opts.arguments, opts.options, config) do |klass, invoke|
|
33
|
+
klass.prepare(invoke, args, config)
|
38
34
|
end
|
39
|
-
|
40
|
-
|
35
|
+
|
36
|
+
return instance, nil
|
41
37
|
end
|
42
38
|
|
43
39
|
# Prints help information.
|
@@ -77,6 +73,15 @@ class Thor::Group
|
|
77
73
|
def create_task(meth) #:nodoc:
|
78
74
|
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
|
79
75
|
end
|
76
|
+
|
77
|
+
def normalize_arguments(args, config) #:nodoc:
|
78
|
+
if Thor::HELP_MAPPINGS.include?(args.first)
|
79
|
+
help(config[:shell])
|
80
|
+
nil
|
81
|
+
else
|
82
|
+
:all
|
83
|
+
end
|
84
|
+
end
|
80
85
|
end
|
81
86
|
|
82
87
|
include Thor::Base
|
@@ -86,9 +91,8 @@ class Thor::Group
|
|
86
91
|
# Overwrite _setup_for_invoke to force invocation of all tasks when :all is
|
87
92
|
# supplied.
|
88
93
|
#
|
89
|
-
def _setup_for_invoke(
|
90
|
-
|
91
|
-
super(name, method_args, options)
|
94
|
+
def _setup_for_invoke(object, task=nil)
|
95
|
+
super(object.to_s == "all" ? nil : object)
|
92
96
|
end
|
93
97
|
|
94
98
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
class Thor
|
2
|
+
module Invocation
|
3
|
+
|
4
|
+
# Make initializer aware of invocations and the initializer proc.
|
5
|
+
#
|
6
|
+
def initialize(args=[], options={}, config={}, &block) #:nodoc:
|
7
|
+
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
|
8
|
+
@_initializer = block || lambda do |klass, invoke, overrides|
|
9
|
+
klass.new(args, options, config.merge(overrides))
|
10
|
+
end
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
# Receives a name and invokes it. The name can be a string (either "task" or
|
15
|
+
# "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
|
16
|
+
# cannot be guessed name, it can also be supplied as second argument. The
|
17
|
+
# arguments used to invoke the task are always supplied as the last argument.
|
18
|
+
#
|
19
|
+
# ==== Examples
|
20
|
+
#
|
21
|
+
# class A < Thor
|
22
|
+
# def foo
|
23
|
+
# invoke :bar
|
24
|
+
# invoke "b:hello", ["José"]
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def bar
|
28
|
+
# invoke "b:hello", ["José"]
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# class B < Thor
|
33
|
+
# def hello(name)
|
34
|
+
# puts "hello #{name}"
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# You can notice that the method "foo" above invokes two tasks: "bar",
|
39
|
+
# which belongs to the same class and "hello" which belongs to the class B.
|
40
|
+
#
|
41
|
+
# By using an invocation system you ensure that a task is invoked only once.
|
42
|
+
# In the example above, invoking "foo" will invoke "b:hello" just once, even
|
43
|
+
# if it's invoked later by "bar" method.
|
44
|
+
#
|
45
|
+
# When class A invokes class B, all arguments used on A initialization are
|
46
|
+
# supplied to B. This allows lazy parse of options. Let's suppose you have
|
47
|
+
# some rspec tasks:
|
48
|
+
#
|
49
|
+
# class Rspec < Thor::Group
|
50
|
+
# class_option :mock_framework, :type => :string, :default => :rr
|
51
|
+
#
|
52
|
+
# def invoke_mock_framework
|
53
|
+
# invoke "rspec:#{options[:mock_framework]}"
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# As you notice, it invokes the given mock framework, which might have its
|
58
|
+
# own options:
|
59
|
+
#
|
60
|
+
# class Rspec::RR < Thor::Group
|
61
|
+
# class_option :style, :type => :string, :default => :mock
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# Since it's not rspec concern to parse mock framework options, when RR
|
65
|
+
# is invoked all options are parsed again, so RR can extract only the options
|
66
|
+
# that it's going to use.
|
67
|
+
#
|
68
|
+
# If you want Rspec::RR to be initialized with its own set of options, you
|
69
|
+
# have to do that explicitely:
|
70
|
+
#
|
71
|
+
# invoke Rspec::RR.new([], :style => :foo)
|
72
|
+
#
|
73
|
+
# Besides giving an instance, you can also give a class to invoke:
|
74
|
+
#
|
75
|
+
# invoke Rspec::RR
|
76
|
+
#
|
77
|
+
# Or even a class, the task to invoke from it and its arguments:
|
78
|
+
#
|
79
|
+
# invoke Rspec::RR, :foo, [ args ]
|
80
|
+
#
|
81
|
+
def invoke(name, task=nil, method_args=nil)
|
82
|
+
task, method_args = nil, task if task.is_a?(Array)
|
83
|
+
|
84
|
+
object, task = _setup_for_invoke(name, task)
|
85
|
+
klass = object.is_a?(Class) ? object : object.class
|
86
|
+
|
87
|
+
current = @_invocations[klass]
|
88
|
+
return if current.include?("all")
|
89
|
+
|
90
|
+
if object.is_a?(Class)
|
91
|
+
instance, trailing = @_initializer.call(klass, task, _overrides_config)
|
92
|
+
method_args ||= trailing
|
93
|
+
else
|
94
|
+
instance = object
|
95
|
+
end
|
96
|
+
|
97
|
+
method_args ||= []
|
98
|
+
|
99
|
+
if task
|
100
|
+
return if current.include?(task.name)
|
101
|
+
current << task.name
|
102
|
+
task.run(instance, method_args)
|
103
|
+
else
|
104
|
+
current << "all"
|
105
|
+
klass.all_tasks.collect { |_, task| task.run(instance) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
# Values that are sent to overwrite defined configuration values.
|
112
|
+
#
|
113
|
+
def _overrides_config #:nodoc:
|
114
|
+
{ :invocations => @_invocations }
|
115
|
+
end
|
116
|
+
|
117
|
+
# This is the method responsable for retrieving and setting up an
|
118
|
+
# instance to be used in invoke.
|
119
|
+
#
|
120
|
+
def _setup_for_invoke(name, sent_task=nil) #:nodoc:
|
121
|
+
case name
|
122
|
+
when Thor::Task
|
123
|
+
task = name
|
124
|
+
when Symbol, String
|
125
|
+
name = name.to_s
|
126
|
+
|
127
|
+
begin
|
128
|
+
task = self.class.all_tasks[name]
|
129
|
+
object, task = Thor::Util.namespace_to_thor_class(name) unless task
|
130
|
+
task = task || sent_task
|
131
|
+
rescue Thor::Error
|
132
|
+
task = name
|
133
|
+
end
|
134
|
+
else
|
135
|
+
object, task = name, sent_task
|
136
|
+
end
|
137
|
+
|
138
|
+
object ||= self
|
139
|
+
klass = object.is_a?(Class) ? object : object.class
|
140
|
+
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
141
|
+
|
142
|
+
task ||= klass.default_task if klass <= Thor
|
143
|
+
task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task)
|
144
|
+
return object, task
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
data/lib/thor/options.rb
CHANGED
data/lib/thor/shell/basic.rb
CHANGED
@@ -125,30 +125,28 @@ class Thor
|
|
125
125
|
#
|
126
126
|
def file_collision(destination)
|
127
127
|
return true if @always_force
|
128
|
-
|
129
128
|
options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
129
|
+
|
130
|
+
while true
|
131
|
+
answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
|
132
|
+
|
133
|
+
case answer
|
134
|
+
when is?(:yes), is?(:force)
|
135
|
+
return true
|
136
|
+
when is?(:no), is?(:skip)
|
137
|
+
return false
|
138
|
+
when is?(:always)
|
139
|
+
return @always_force = true
|
140
|
+
when is?(:quit)
|
141
|
+
say 'Aborting...'
|
142
|
+
raise SystemExit
|
143
|
+
when is?(:diff)
|
144
|
+
show_diff(destination, yield) if block_given?
|
145
|
+
say 'Retrying...'
|
146
|
+
else
|
147
|
+
say file_collision_help
|
148
|
+
end
|
149
149
|
end
|
150
|
-
rescue ScriptError
|
151
|
-
retry
|
152
150
|
end
|
153
151
|
|
154
152
|
# Called if something goes wrong during the execution. This is used by Thor
|
data/lib/thor/shell.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'thor/shell/basic'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Base
|
5
|
+
# Returns the shell used in all Thor classes.
|
6
|
+
#
|
7
|
+
def self.shell
|
8
|
+
@shell || Thor::Shell::Basic
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sets the shell used in all Thor classes.
|
12
|
+
#
|
13
|
+
def self.shell=(klass)
|
14
|
+
@shell = klass
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Shell
|
19
|
+
SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
|
20
|
+
|
21
|
+
# Add shell to initialize config values.
|
22
|
+
#
|
23
|
+
# ==== Configuration
|
24
|
+
# shell<Object>:: An instance of the shell to be used.
|
25
|
+
#
|
26
|
+
# ==== Examples
|
27
|
+
#
|
28
|
+
# class MyScript < Thor
|
29
|
+
# argument :first, :type => :numeric
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
|
33
|
+
#
|
34
|
+
def initialize(args=[], options={}, config={})
|
35
|
+
super
|
36
|
+
self.shell = config[:shell]
|
37
|
+
self.shell.base ||= self if self.shell.respond_to?(:base)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Holds the shell for the given Thor instance. If no shell is given,
|
41
|
+
# it gets a default shell from Thor::Base.shell.
|
42
|
+
#
|
43
|
+
def shell
|
44
|
+
@shell ||= Thor::Base.shell.new
|
45
|
+
end
|
46
|
+
|
47
|
+
# Sets the shell for this thor class.
|
48
|
+
#
|
49
|
+
def shell=(shell)
|
50
|
+
@shell = shell
|
51
|
+
end
|
52
|
+
|
53
|
+
# Common methods that are delegated to the shell.
|
54
|
+
#
|
55
|
+
SHELL_DELEGATED_METHODS.each do |method|
|
56
|
+
module_eval <<-METHOD, __FILE__, __LINE__
|
57
|
+
def #{method}(*args)
|
58
|
+
shell.#{method}(*args)
|
59
|
+
end
|
60
|
+
METHOD
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
# Send the current shell to be used by the invoked class.
|
66
|
+
#
|
67
|
+
def _overrides_config #:nodoc:
|
68
|
+
super.merge!(:shell => self.shell)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/thor.rb
CHANGED
@@ -115,28 +115,18 @@ class Thor
|
|
115
115
|
build_option(name, options, scope)
|
116
116
|
end
|
117
117
|
|
118
|
-
#
|
119
|
-
# and invoke the task. This method is used when the arguments must be parsed
|
120
|
-
# from an array. If you are inside Ruby and want to use a Thor class, you
|
121
|
-
# can simply initialize it:
|
118
|
+
# Implements the prepare interface being used by start.
|
122
119
|
#
|
123
|
-
|
124
|
-
# script.invoke(:task, first_arg, second_arg, third_arg)
|
125
|
-
#
|
126
|
-
def start(args=ARGV, config={})
|
127
|
-
config[:shell] ||= Thor::Base.shell.new
|
128
|
-
|
129
|
-
meth = normalize_task_name(args.shift)
|
130
|
-
task = all_tasks[meth] || Task.dynamic(meth)
|
131
|
-
|
120
|
+
def prepare(task, args, config) #:nodoc:
|
132
121
|
options = class_options.merge(task.options)
|
133
122
|
opts = Thor::Options.new(options)
|
134
123
|
opts.parse(args)
|
135
124
|
|
136
|
-
instance = new(opts.arguments, opts.options, config)
|
137
|
-
|
138
|
-
|
139
|
-
|
125
|
+
instance = new(opts.arguments, opts.options, config) do |klass, invoke, overrides|
|
126
|
+
klass.prepare(invoke, args, config.merge(overrides))
|
127
|
+
end
|
128
|
+
|
129
|
+
return instance, opts.trailing
|
140
130
|
end
|
141
131
|
|
142
132
|
# Prints help information. If a task name is given, it shows information
|
@@ -207,6 +197,11 @@ class Thor
|
|
207
197
|
@method_options = nil
|
208
198
|
end
|
209
199
|
|
200
|
+
def normalize_arguments(args, config) #:nodoc:
|
201
|
+
meth = normalize_task_name(args.shift)
|
202
|
+
all_tasks[meth] || Task.dynamic(meth)
|
203
|
+
end
|
204
|
+
|
210
205
|
# Receives a task name (can be nil), and try to get a map from it.
|
211
206
|
# If a map can't be found use the sent name or the default task.
|
212
207
|
#
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: josevalim-thor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yehuda Katz
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-06-
|
12
|
+
date: 2009-06-23 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- lib/thor/options.rb
|
53
53
|
- lib/thor/shell
|
54
54
|
- lib/thor/shell/basic.rb
|
55
|
+
- lib/thor/invocation.rb
|
55
56
|
- lib/thor/tasks.rb
|
56
57
|
- lib/thor/core_ext
|
57
58
|
- lib/thor/core_ext/hash_with_indifferent_access.rb
|
@@ -60,6 +61,7 @@ files:
|
|
60
61
|
- lib/thor/tasks/install.rb
|
61
62
|
- lib/thor/tasks/spec.rb
|
62
63
|
- lib/thor/tasks/package.rb
|
64
|
+
- lib/thor/shell.rb
|
63
65
|
- lib/thor/task.rb
|
64
66
|
has_rdoc: true
|
65
67
|
homepage: http://yehudakatz.com
|