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