josevalim-thor 0.10.7 → 0.10.8
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.
- data/lib/thor/actions.rb +8 -0
- data/lib/thor/base.rb +135 -34
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +17 -7
- data/lib/thor/core_ext/ordered_hash.rb +1 -5
- data/lib/thor/group.rb +79 -8
- data/lib/thor/task.rb +45 -8
- data/lib/thor.rb +4 -4
- metadata +2 -2
data/lib/thor/actions.rb
CHANGED
data/lib/thor/base.rb
CHANGED
@@ -7,7 +7,8 @@ require 'thor/task'
|
|
7
7
|
require 'thor/util'
|
8
8
|
|
9
9
|
class Thor
|
10
|
-
HELP_MAPPINGS
|
10
|
+
HELP_MAPPINGS = %w(-h -? --help -D)
|
11
|
+
RESERVED_TASK_NAMES = %w(all invoke shell options behavior root destination_root relative_root source_root)
|
11
12
|
|
12
13
|
class Maxima < Struct.new(:usage, :options, :class_options)
|
13
14
|
end
|
@@ -215,19 +216,6 @@ class Thor
|
|
215
216
|
end
|
216
217
|
end
|
217
218
|
|
218
|
-
# Retrieve a specific task from this Thor class. If the desired Task cannot
|
219
|
-
# be found, returns a dynamic Thor::Task that will map to the given method.
|
220
|
-
#
|
221
|
-
# ==== Parameters
|
222
|
-
# meth<Symbol>:: the name of the task to be retrieved
|
223
|
-
#
|
224
|
-
# ==== Returns
|
225
|
-
# Task
|
226
|
-
#
|
227
|
-
def [](meth)
|
228
|
-
all_tasks[meth.to_s] || Thor::Task.dynamic(meth)
|
229
|
-
end
|
230
|
-
|
231
219
|
# All methods defined inside the given block are not added as tasks.
|
232
220
|
#
|
233
221
|
# So you can do:
|
@@ -384,6 +372,11 @@ class Thor
|
|
384
372
|
end
|
385
373
|
|
386
374
|
return if @no_tasks || !valid_task?(meth)
|
375
|
+
|
376
|
+
if RESERVED_TASK_NAMES.include?(meth)
|
377
|
+
raise ScriptError, "'#{meth}' is a Thor reserved word and cannot be defined as task"
|
378
|
+
end
|
379
|
+
|
387
380
|
Thor::Base.register_klass_file(self)
|
388
381
|
create_task(meth)
|
389
382
|
end
|
@@ -456,20 +449,10 @@ class Thor
|
|
456
449
|
end
|
457
450
|
|
458
451
|
self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).freeze
|
459
|
-
self.shell = config[:shell]
|
460
452
|
|
461
|
-
#
|
462
|
-
self.shell
|
463
|
-
|
464
|
-
|
465
|
-
# Common methods that are delegated to the shell.
|
466
|
-
#
|
467
|
-
SHELL_DELEGATED_METHODS.each do |method|
|
468
|
-
module_eval <<-METHOD, __FILE__, __LINE__
|
469
|
-
def #{method}(*args)
|
470
|
-
shell.#{method}(*args)
|
471
|
-
end
|
472
|
-
METHOD
|
453
|
+
# Configure shell and set base if not already
|
454
|
+
self.shell = config[:shell]
|
455
|
+
self.shell.base ||= self if self.shell.respond_to?(:base)
|
473
456
|
end
|
474
457
|
|
475
458
|
# Holds the shell for the given Thor instance. If no shell is given,
|
@@ -485,15 +468,133 @@ class Thor
|
|
485
468
|
@shell = shell
|
486
469
|
end
|
487
470
|
|
488
|
-
#
|
489
|
-
# This is the default interface to invoke tasks. You can always run a task
|
490
|
-
# directly, but the invocation system will be implemented in a fashion
|
491
|
-
# that a same task cannot be invoked twice (a la rake).
|
471
|
+
# Common methods that are delegated to the shell.
|
492
472
|
#
|
493
|
-
|
494
|
-
|
473
|
+
SHELL_DELEGATED_METHODS.each do |method|
|
474
|
+
module_eval <<-METHOD, __FILE__, __LINE__
|
475
|
+
def #{method}(*args)
|
476
|
+
shell.#{method}(*args)
|
477
|
+
end
|
478
|
+
METHOD
|
495
479
|
end
|
496
|
-
end
|
497
480
|
|
481
|
+
# Receives a name and invokes it. The name can be either a namespaced name,
|
482
|
+
# a current class task or even a class. Arguments are given in an array and
|
483
|
+
# options given are merged with the invoker options.
|
484
|
+
#
|
485
|
+
# ==== Examples
|
486
|
+
#
|
487
|
+
# class A < Thor
|
488
|
+
# def foo
|
489
|
+
# invoke :bar
|
490
|
+
# invoke "b:lib", ["merb", "rails"]
|
491
|
+
# end
|
492
|
+
#
|
493
|
+
# def bar
|
494
|
+
# invoke "b:lib", ["merb", "rails"]
|
495
|
+
# # magic
|
496
|
+
# end
|
497
|
+
# end
|
498
|
+
#
|
499
|
+
# class B < Thor
|
500
|
+
# argument :preferred_framework, :type => :string
|
501
|
+
#
|
502
|
+
# def lib(second_framework)
|
503
|
+
# # magic
|
504
|
+
# end
|
505
|
+
# end
|
506
|
+
#
|
507
|
+
# You can notice that the method "foo" above invokes two tasks: "bar",
|
508
|
+
# which belongs to the same class and "lib" that belongs to the class B.
|
509
|
+
#
|
510
|
+
# By using an invocation system you ensure that a task is invoked only once.
|
511
|
+
# In the example above, invoking foo will invoke "b:lib" just once, even if
|
512
|
+
# it's invoked later by "bar" method.
|
513
|
+
#
|
514
|
+
# When invoking another class, there are a few things to keep in mind:
|
515
|
+
#
|
516
|
+
# 1) Class arguments are parsed first. In the example above, preferred
|
517
|
+
# framework is going to consume "merb" and second framework is going
|
518
|
+
# to be set to "rails".
|
519
|
+
#
|
520
|
+
# 2) All options and configurations are sent to the invoked class.
|
521
|
+
# So the invoked class is going to use the same shell instance, will
|
522
|
+
# have the same behavior (:invoke or :revoke) and so on.
|
523
|
+
#
|
524
|
+
# Invoking a Thor::Group happens in the same away as above:
|
525
|
+
#
|
526
|
+
# class C < Thor::Group
|
527
|
+
# def one
|
528
|
+
# end
|
529
|
+
# end
|
530
|
+
#
|
531
|
+
# Is invoked as:
|
532
|
+
#
|
533
|
+
# invoke "c"
|
534
|
+
#
|
535
|
+
# Or even as:
|
536
|
+
#
|
537
|
+
# invoke C
|
538
|
+
#
|
539
|
+
def invoke(name, method_args=[], options={})
|
540
|
+
@_invocations ||= Hash.new { |h,k| h[k] = [] }
|
541
|
+
instance, task = _setup_for_invoke(name, method_args, options)
|
542
|
+
|
543
|
+
current = @_invocations[instance.class]
|
544
|
+
return if current.include?("all")
|
545
|
+
|
546
|
+
if task
|
547
|
+
task = self.class.all_tasks[task.to_s] || Task.dynamic(task) unless task.is_a?(Thor::Task)
|
548
|
+
return if current.include?(task.name)
|
549
|
+
|
550
|
+
current << task.name
|
551
|
+
task.run(instance, method_args)
|
552
|
+
else
|
553
|
+
current << "all"
|
554
|
+
instance.class.all_tasks.collect { |_, task| task.run(instance) }
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
protected
|
559
|
+
|
560
|
+
# This is the method responsable for retrieving and setting up an
|
561
|
+
# instance to be used in invoke.
|
562
|
+
#
|
563
|
+
def _setup_for_invoke(name, method_args, options) #:nodoc:
|
564
|
+
case name
|
565
|
+
when NilClass, Thor::Task
|
566
|
+
# Do nothing, we already have what we want
|
567
|
+
when Class
|
568
|
+
klass = name
|
569
|
+
else
|
570
|
+
name = name.to_s
|
571
|
+
unless self.class.all_tasks[name]
|
572
|
+
klass, task = Thor::Util.namespace_to_thor_class(name) rescue Thor::Error
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
if klass.nil?
|
577
|
+
return self, name
|
578
|
+
elsif klass <= Thor::Base
|
579
|
+
size = klass.arguments.size
|
580
|
+
class_args = method_args.slice!(0, size)
|
581
|
+
instance = klass.new(class_args, self.options.merge(options), _dump_config)
|
582
|
+
|
583
|
+
task ||= klass.default_task if klass <= Thor
|
584
|
+
instance.instance_variable_set("@_invocations", @_invocations)
|
585
|
+
|
586
|
+
return instance, task
|
587
|
+
else
|
588
|
+
raise ScriptError, "Expected Thor class, got #{klass}"
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
# Dump the configuration values for this current class.
|
593
|
+
#
|
594
|
+
def _dump_config #:nodoc:
|
595
|
+
{ :shell => self.shell }
|
596
|
+
end
|
597
|
+
|
598
|
+
end
|
498
599
|
end
|
499
600
|
end
|
@@ -11,15 +11,10 @@ class Thor
|
|
11
11
|
#
|
12
12
|
class HashWithIndifferentAccess < ::Hash
|
13
13
|
|
14
|
-
def initialize(hash)
|
14
|
+
def initialize(hash={})
|
15
15
|
super()
|
16
|
-
|
17
16
|
hash.each do |key, value|
|
18
|
-
|
19
|
-
self[key.to_s] = value
|
20
|
-
else
|
21
|
-
self[key] = value
|
22
|
-
end
|
17
|
+
self[convert_key(key)] = value
|
23
18
|
end
|
24
19
|
end
|
25
20
|
|
@@ -27,6 +22,10 @@ class Thor
|
|
27
22
|
super(convert_key(key))
|
28
23
|
end
|
29
24
|
|
25
|
+
def []=(key, value)
|
26
|
+
super(convert_key(key), value)
|
27
|
+
end
|
28
|
+
|
30
29
|
def delete(key)
|
31
30
|
super(convert_key(key))
|
32
31
|
end
|
@@ -35,6 +34,17 @@ class Thor
|
|
35
34
|
indices.collect { |key| self[convert_key(key)] }
|
36
35
|
end
|
37
36
|
|
37
|
+
def merge(other)
|
38
|
+
dup.merge!(other)
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge!(other)
|
42
|
+
other.each do |key, value|
|
43
|
+
self[convert_key(key)] = value
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
38
48
|
protected
|
39
49
|
|
40
50
|
def convert_key(key)
|
data/lib/thor/group.rb
CHANGED
@@ -22,6 +22,71 @@ class Thor::Group
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
# Sets the condition for some task to be executed in the class level. Why
|
26
|
+
# is this important? Setting the conditions in the class level allows to
|
27
|
+
# an inherited class change the conditions and customize the Thor::Group as
|
28
|
+
# it wishes.
|
29
|
+
#
|
30
|
+
# The conditions given are retrieved from the options hash. Let's suppose
|
31
|
+
# that a task is only executed if --test-framework is rspec. You could do
|
32
|
+
# this:
|
33
|
+
#
|
34
|
+
# class_option :test_framework, :type => :string
|
35
|
+
#
|
36
|
+
# conditions :test_framework => :rspec
|
37
|
+
# def create_rspec_files
|
38
|
+
# # magic
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Later someone creates a framework on top of rspec and need rspec files to
|
42
|
+
# generated as well. He could then change the conditions:
|
43
|
+
#
|
44
|
+
# conditions :test_framework => [ :rspec, :remarkable ], :for => :create_rspec_files
|
45
|
+
#
|
46
|
+
# He could also use remove_conditions and remove previous set conditions:
|
47
|
+
#
|
48
|
+
# remove_conditions :test_framework, :for => :create_rspec_files
|
49
|
+
#
|
50
|
+
# Conditions only work with the class option to be comparead to is a boolean,
|
51
|
+
# string or numeric (no array or hash comparisions).
|
52
|
+
#
|
53
|
+
# ==== Parameters
|
54
|
+
# conditions<Hash>:: the conditions for the task. The key is the option name
|
55
|
+
# and the value is the condition to be checked. If the
|
56
|
+
# condition is an array, it checkes if the current value
|
57
|
+
# is included in the array. If a regexp, checks if the
|
58
|
+
# value matches, all other values are simply compared (==).
|
59
|
+
#
|
60
|
+
def conditions(conditions=nil)
|
61
|
+
subject = if conditions && conditions[:for]
|
62
|
+
find_and_refresh_task(conditions.delete(:for)).conditions
|
63
|
+
else
|
64
|
+
@conditions ||= {}
|
65
|
+
end
|
66
|
+
|
67
|
+
subject.merge!(conditions) if conditions
|
68
|
+
subject
|
69
|
+
end
|
70
|
+
|
71
|
+
# Remove a previous specified condition. Check <tt>conditions</tt> above for
|
72
|
+
# a complete example.
|
73
|
+
#
|
74
|
+
# ==== Parameters
|
75
|
+
# conditions<Array>:: An array of conditions to be removed.
|
76
|
+
# for<Hash>:: A hash with :for as key indicating the task to remove the conditions from.
|
77
|
+
#
|
78
|
+
# ==== Examples
|
79
|
+
#
|
80
|
+
# remove_conditions :test_framework, :orm, :for => :create_app_skeleton
|
81
|
+
#
|
82
|
+
def remove_conditions(*conditions)
|
83
|
+
subject = find_and_refresh_task(conditions.pop[:for]).conditions
|
84
|
+
conditions.each do |condition|
|
85
|
+
subject.delete(condition)
|
86
|
+
end
|
87
|
+
subject
|
88
|
+
end
|
89
|
+
|
25
90
|
# Start in Thor::Group works differently. It invokes all tasks inside the
|
26
91
|
# class and does not have to parse task options.
|
27
92
|
#
|
@@ -34,7 +99,7 @@ class Thor::Group
|
|
34
99
|
opts = Thor::Options.new(class_options)
|
35
100
|
opts.parse(args)
|
36
101
|
|
37
|
-
new(opts.arguments, opts.options, config).
|
102
|
+
new(opts.arguments, opts.options, config).invoke(:all)
|
38
103
|
end
|
39
104
|
rescue Thor::Error => e
|
40
105
|
config[:shell].error e.message
|
@@ -75,15 +140,21 @@ class Thor::Group
|
|
75
140
|
end
|
76
141
|
|
77
142
|
def create_task(meth) #:nodoc:
|
78
|
-
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
|
143
|
+
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, @conditions)
|
144
|
+
@conditions = nil
|
79
145
|
end
|
80
146
|
end
|
81
147
|
|
82
|
-
# Invokes all tasks in the instance.
|
83
|
-
#
|
84
|
-
def invoke_all
|
85
|
-
self.class.all_tasks.map { |_, task| task.run(self) }
|
86
|
-
end
|
87
|
-
|
88
148
|
include Thor::Base
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
# Overwrite _setup_for_invoke to force invocation of all tasks when :all is
|
153
|
+
# supplied.
|
154
|
+
#
|
155
|
+
def _setup_for_invoke(name, method_args, options)
|
156
|
+
name = nil if name.to_s == "all"
|
157
|
+
super(name, method_args, options)
|
158
|
+
end
|
159
|
+
|
89
160
|
end
|
data/lib/thor/task.rb
CHANGED
@@ -1,30 +1,31 @@
|
|
1
1
|
class Thor
|
2
|
-
class Task < Struct.new(:name, :description, :usage, :options)
|
2
|
+
class Task < Struct.new(:name, :description, :usage, :options, :conditions)
|
3
3
|
|
4
4
|
# Creates a dynamic task. Dynamic tasks are created on demand to allow method
|
5
5
|
# missing calls (since a method missing does not have a task object for it).
|
6
6
|
#
|
7
7
|
def self.dynamic(name)
|
8
|
-
new(name, "A dynamically-generated task", name.to_s
|
8
|
+
new(name, "A dynamically-generated task", name.to_s)
|
9
9
|
end
|
10
10
|
|
11
|
-
def initialize(name, description, usage, options)
|
12
|
-
super(name, description, usage, options || {})
|
11
|
+
def initialize(name, description, usage, options=nil, conditions=nil)
|
12
|
+
super(name.to_s, description, usage, options || {}, conditions || {})
|
13
13
|
end
|
14
14
|
|
15
15
|
# Dup the options hash on clone.
|
16
16
|
#
|
17
17
|
def initialize_copy(other)
|
18
18
|
super(other)
|
19
|
-
self.options
|
19
|
+
self.options = other.options.dup if other.options
|
20
|
+
self.conditions = other.conditions.dup if other.conditions
|
20
21
|
end
|
21
22
|
|
22
23
|
# By default, a task invokes a method in the thor class. You can change this
|
23
24
|
# implementation to create custom tasks.
|
24
25
|
#
|
25
|
-
def run(instance,
|
26
|
+
def run(instance, args=[])
|
26
27
|
raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
|
27
|
-
instance.send(name, *args)
|
28
|
+
instance.send(name, *args) if valid_conditions?(instance)
|
28
29
|
rescue ArgumentError => e
|
29
30
|
backtrace = sans_backtrace(e.backtrace, caller)
|
30
31
|
|
@@ -86,7 +87,30 @@ class Thor
|
|
86
87
|
# Given a target, checks if this class name is not a private/protected method.
|
87
88
|
#
|
88
89
|
def public_method?(instance)
|
89
|
-
|
90
|
+
collection = instance.private_methods + instance.protected_methods
|
91
|
+
!(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9
|
92
|
+
end
|
93
|
+
|
94
|
+
# Check if the task conditions are met before invoking.
|
95
|
+
#
|
96
|
+
def valid_conditions?(instance)
|
97
|
+
return true if conditions.empty?
|
98
|
+
|
99
|
+
conditions.each do |key, expected|
|
100
|
+
actual = stringify!(instance.options[key])
|
101
|
+
expected = stringify!(expected)
|
102
|
+
|
103
|
+
return false if case expected
|
104
|
+
when Regexp
|
105
|
+
actual !~ expected
|
106
|
+
when Array
|
107
|
+
!expected.include?(actual)
|
108
|
+
else
|
109
|
+
actual != expected
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
true
|
90
114
|
end
|
91
115
|
|
92
116
|
# Clean everything that comes from the Thor gempath and remove the caller.
|
@@ -97,5 +121,18 @@ class Thor
|
|
97
121
|
saned -= caller
|
98
122
|
end
|
99
123
|
|
124
|
+
# Receives an object and convert any symbol to string.
|
125
|
+
#
|
126
|
+
def stringify!(duck)
|
127
|
+
case duck
|
128
|
+
when Array
|
129
|
+
duck.map!{ |i| i.is_a?(Symbol) ? i.to_s : i }
|
130
|
+
when Symbol
|
131
|
+
duck.to_s
|
132
|
+
else
|
133
|
+
duck
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
100
137
|
end
|
101
138
|
end
|
data/lib/thor.rb
CHANGED
@@ -127,14 +127,14 @@ class Thor
|
|
127
127
|
config[:shell] ||= Thor::Base.shell.new
|
128
128
|
|
129
129
|
meth = normalize_task_name(args.shift)
|
130
|
-
task =
|
130
|
+
task = all_tasks[meth] || Task.dynamic(meth)
|
131
131
|
|
132
132
|
options = class_options.merge(task.options)
|
133
133
|
opts = Thor::Options.new(options)
|
134
134
|
opts.parse(args)
|
135
135
|
|
136
136
|
instance = new(opts.arguments, opts.options, config)
|
137
|
-
instance.invoke(task
|
137
|
+
instance.invoke(task, opts.trailing)
|
138
138
|
rescue Thor::Error => e
|
139
139
|
config[:shell].error e.message
|
140
140
|
end
|
@@ -153,7 +153,7 @@ class Thor
|
|
153
153
|
meth, options = nil, meth if meth.is_a?(Hash)
|
154
154
|
|
155
155
|
if meth
|
156
|
-
task =
|
156
|
+
task = all_tasks[meth]
|
157
157
|
raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
|
158
158
|
|
159
159
|
shell.say "Usage:"
|
@@ -198,7 +198,7 @@ class Thor
|
|
198
198
|
end
|
199
199
|
|
200
200
|
def create_task(meth) #:nodoc:
|
201
|
-
tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
|
201
|
+
tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options, nil)
|
202
202
|
@usage, @desc, @method_options = nil
|
203
203
|
end
|
204
204
|
|
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.8
|
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-20 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|