thor 0.13.8 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +2 -1
- data/{README.rdoc → README.md} +62 -54
- data/Thorfile +2 -2
- data/lib/thor.rb +72 -48
- data/lib/thor/base.rb +26 -10
- data/lib/thor/group.rb +17 -15
- data/lib/thor/invocation.rb +45 -57
- data/lib/thor/parser/options.rb +2 -0
- data/lib/thor/shell.rb +1 -1
- data/lib/thor/shell/basic.rb +9 -5
- data/lib/thor/version.rb +1 -1
- data/spec/base_spec.rb +6 -0
- data/spec/fixtures/script.thor +13 -1
- data/spec/invocation_spec.rb +1 -8
- data/spec/spec_helper.rb +1 -0
- data/spec/thor_spec.rb +39 -7
- metadata +7 -7
data/CHANGELOG.rdoc
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
* Added :lazy_default which is only triggered if a switch is given
|
4
4
|
* Added Thor::Shell::HTML
|
5
|
+
* Added subcommands
|
5
6
|
* Decoupled Thor::Group and Thor, so it's easier to vendor
|
6
7
|
* Added check_unknown_options! in case you want error messages to be raised in valid switches
|
7
8
|
* run(command) should return the results of command
|
@@ -21,7 +22,7 @@
|
|
21
22
|
Thor classes.
|
22
23
|
|
23
24
|
* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
|
24
|
-
since it wrong behavior
|
25
|
+
since it may cause wrong behavior in the invocation system.
|
25
26
|
|
26
27
|
* thor help now show information about any class/task. All those calls are
|
27
28
|
possible:
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,7 +1,21 @@
|
|
1
|
-
|
1
|
+
# Thor
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
## Description
|
4
|
+
|
5
|
+
Thor is a simple and efficient tool for building self-documenting command line utilities. It removes the pain of parsing command line options, writing "USAGE:" banners, and can also be used as an alternative to the [Rake](http://github.com/jimweirich/rake) build tool. The syntax is Rake-like, so it should be familiar to most Rake users.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
$ gem install thor
|
10
|
+
|
11
|
+
or
|
12
|
+
|
13
|
+
$ gem install wycats-thor -s http://gems.github.com
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Map options to a class. Simply create a class with the appropriate annotations
|
18
|
+
and have options automatically map to functions and parameters.
|
5
19
|
|
6
20
|
Example:
|
7
21
|
|
@@ -24,7 +38,7 @@ Example:
|
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
27
|
-
Thor automatically maps commands as such:
|
41
|
+
Thor automatically maps commands as such:
|
28
42
|
|
29
43
|
thor app:install myname --force
|
30
44
|
|
@@ -33,12 +47,12 @@ That gets converted to:
|
|
33
47
|
App.new.install("myname")
|
34
48
|
# with {'force' => true} as options hash
|
35
49
|
|
36
|
-
1. Inherit from Thor to turn a class into an option mapper
|
37
|
-
2. Map additional non-valid identifiers to specific methods.
|
38
|
-
3. Describe the method immediately below.
|
39
|
-
4. Provide any additional options that will be available the instance method options.
|
50
|
+
1. Inherit from Thor to turn a class into an option mapper.
|
51
|
+
2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
|
52
|
+
3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description.
|
53
|
+
4. Provide any additional options that will be available the instance method options.
|
40
54
|
|
41
|
-
|
55
|
+
## Types for <tt>method_options</tt>
|
42
56
|
|
43
57
|
* :boolean - is parsed as <tt>--option</tt> or <tt>--option=true</tt>
|
44
58
|
* :string - is parsed as <tt>--option=VALUE</tt>
|
@@ -46,7 +60,7 @@ That gets converted to:
|
|
46
60
|
* :array - is parsed as <tt>--option=one two three</tt>
|
47
61
|
* :hash - is parsed as <tt>--option=name:string age:integer</tt>
|
48
62
|
|
49
|
-
Besides, method_option allows a default value to be given
|
63
|
+
Besides, method_option allows a default value to be given. Examples:
|
50
64
|
|
51
65
|
method_options :force => false
|
52
66
|
#=> Creates a boolean option with default value false
|
@@ -57,19 +71,19 @@ Besides, method_option allows a default value to be given, examples:
|
|
57
71
|
method_options :threshold => 3.0
|
58
72
|
#=> Creates a numeric option with default value 3.0
|
59
73
|
|
60
|
-
You can also supply <tt>:option => :required</tt> to mark an option as required.
|
61
|
-
type is assumed to be string.
|
74
|
+
You can also supply <tt>:option => :required</tt> to mark an option as required. The
|
75
|
+
type is assumed to be string. If you want a required hash with default values
|
62
76
|
as option, you can use <tt>method_option</tt> which uses a more declarative style:
|
63
77
|
|
64
78
|
method_option :attributes, :type => :hash, :default => {}, :required => true
|
65
79
|
|
66
80
|
All arguments can be set to nil (except required arguments), by suppling a no or
|
67
|
-
skip variant.
|
81
|
+
skip variant. For example:
|
68
82
|
|
69
83
|
thor app name --no-attributes
|
70
84
|
|
71
85
|
In previous versions, aliases for options were created automatically, but now
|
72
|
-
they should be explicit.
|
86
|
+
they should be explicit. You can supply aliases in both short and declarative
|
73
87
|
styles:
|
74
88
|
|
75
89
|
method_options %w( force -f ) => :boolean
|
@@ -82,9 +96,9 @@ You can supply as many aliases as you want.
|
|
82
96
|
|
83
97
|
NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead.
|
84
98
|
|
85
|
-
|
99
|
+
## Namespaces
|
86
100
|
|
87
|
-
By default, your Thor tasks are invoked using Ruby namespace.
|
101
|
+
By default, your Thor tasks are invoked using Ruby namespace. In the example
|
88
102
|
above, tasks are invoked as:
|
89
103
|
|
90
104
|
thor app:install name --force
|
@@ -110,14 +124,13 @@ If desired, you can change the namespace:
|
|
110
124
|
end
|
111
125
|
end
|
112
126
|
|
113
|
-
And then your tasks
|
127
|
+
And then your tasks should be invoked as:
|
114
128
|
|
115
129
|
thor myapp:install name --force
|
116
130
|
|
117
|
-
|
131
|
+
## Invocations
|
118
132
|
|
119
|
-
Thor comes with a invocation-dependency system as well which allows a task to be
|
120
|
-
invoked only once. For example:
|
133
|
+
Thor comes with a invocation-dependency system as well, which allows a task to be invoked only once. For example:
|
121
134
|
|
122
135
|
class Counter < Thor
|
123
136
|
desc "one", "Prints 1, 2, 3"
|
@@ -143,14 +156,14 @@ When invoking the task one:
|
|
143
156
|
|
144
157
|
thor counter:one
|
145
158
|
|
146
|
-
The output is "1 2 3", which means that the three task was invoked only once.
|
159
|
+
The output is "1 2 3", which means that the three task was invoked only once.
|
147
160
|
You can even invoke tasks from another class, so be sure to check the
|
148
|
-
documentation
|
161
|
+
[documentation](http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor.html).
|
149
162
|
|
150
|
-
|
163
|
+
## Thor::Group
|
151
164
|
|
152
|
-
Thor has a special class called Thor::Group.
|
153
|
-
is that it invokes all tasks at once.
|
165
|
+
Thor has a special class called Thor::Group. The main difference to Thor class
|
166
|
+
is that it invokes all tasks at once. The example above could be rewritten in
|
154
167
|
Thor::Group as this:
|
155
168
|
|
156
169
|
class Counter < Thor::Group
|
@@ -173,12 +186,12 @@ When invoked:
|
|
173
186
|
|
174
187
|
thor counter
|
175
188
|
|
176
|
-
It prints "1 2 3" as well.
|
177
|
-
only the class and not each task anymore.
|
189
|
+
It prints "1 2 3" as well. Notice you should describe (using the method <tt>desc</tt>)
|
190
|
+
only the class and not each task anymore. Thor::Group is a great tool to create
|
178
191
|
generators, since you can define several steps which are invoked in the order they
|
179
|
-
are defined (Thor::Group is the tool use in generators in Rails 3.0).
|
192
|
+
are defined (Thor::Group is the tool use in generators in Rails 3.0).
|
180
193
|
|
181
|
-
Besides, Thor::Group can parse arguments and options as Thor tasks:
|
194
|
+
Besides, Thor::Group can parse arguments and options as Thor tasks:
|
182
195
|
|
183
196
|
class Counter < Thor::Group
|
184
197
|
# number will be available as attr_accessor
|
@@ -210,13 +223,13 @@ You can also give options to Thor::Group, but instead of using <tt>method_option
|
|
210
223
|
and <tt>method_options</tt>, you should use <tt>class_option</tt> and <tt>class_options</tt>.
|
211
224
|
Both argument and class_options methods are available to Thor class as well.
|
212
225
|
|
213
|
-
|
226
|
+
## Actions
|
214
227
|
|
215
|
-
Thor comes with several actions which helps with script and generator tasks.
|
216
|
-
might be familiar with them since some came from Rails Templates.
|
228
|
+
Thor comes with several actions which helps with script and generator tasks. You
|
229
|
+
might be familiar with them since some came from Rails Templates. They are:
|
217
230
|
<tt>say</tt>, <tt>ask</tt>, <tt>yes?</tt>, <tt>no?</tt>, <tt>add_file</tt>,
|
218
231
|
<tt>remove_file</tt>, <tt>copy_file</tt>, <tt>template</tt>, <tt>directory</tt>,
|
219
|
-
<tt>inside</tt>, <tt>run</tt>, <tt>inject_into_file</tt> and a couple more.
|
232
|
+
<tt>inside</tt>, <tt>run</tt>, <tt>inject_into_file</tt> and a couple more.
|
220
233
|
|
221
234
|
To use them, you just need to include Thor::Actions in your Thor classes:
|
222
235
|
|
@@ -226,13 +239,13 @@ To use them, you just need to include Thor::Actions in your Thor classes:
|
|
226
239
|
end
|
227
240
|
|
228
241
|
Some actions like copy file requires that a class method called source_root is
|
229
|
-
defined in your class.
|
230
|
-
placed.
|
242
|
+
defined in your class. This is the directory where your templates should be
|
243
|
+
placed. Be sure to check the documentation on [actions](http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor/Actions.html).
|
231
244
|
|
232
|
-
|
245
|
+
## Generators
|
233
246
|
|
234
|
-
A great use for Thor is creating custom generators.
|
235
|
-
Thor::Actions and ERB templates makes this very easy.
|
247
|
+
A great use for Thor is creating custom generators. Combining Thor::Group,
|
248
|
+
Thor::Actions and ERB templates makes this very easy. Here is an example:
|
236
249
|
|
237
250
|
class Newgem < Thor::Group
|
238
251
|
include Thor::Actions
|
@@ -264,8 +277,8 @@ Thor::Actions and ERB templates makes this very easy. Here is an example:
|
|
264
277
|
end
|
265
278
|
end
|
266
279
|
|
267
|
-
Doing a <tt>thor -T</tt> will show how to run our generator.
|
268
|
-
<tt>thor newgem NAME</tt>.
|
280
|
+
Doing a <tt>thor -T</tt> will show how to run our generator. It should read:
|
281
|
+
<tt>thor newgem NAME</tt>. This shows that we have to supply a NAME
|
269
282
|
argument for our generator to run.
|
270
283
|
|
271
284
|
The <tt>create_lib_file</tt> uses an ERB template. This is what it looks like:
|
@@ -274,24 +287,19 @@ The <tt>create_lib_file</tt> uses an ERB template. This is what it looks like:
|
|
274
287
|
end
|
275
288
|
|
276
289
|
The arguments that you set in your generator will automatically be passed in
|
277
|
-
when <tt>template</tt> gets called.
|
290
|
+
when <tt>template</tt> gets called. Be sure to read the [documentation](http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor/Actions.html) for
|
278
291
|
more options.
|
279
292
|
|
280
293
|
Running the generator with <tt>thor newgem devise</tt> will
|
281
|
-
create two files: "devise/lib/devise.rb",
|
282
|
-
|
283
|
-
|
284
|
-
want to change the test framework, you can add the option:
|
285
|
-
<tt>thor newgem devise --test-framework=rspec</tt>
|
286
|
-
This will generate: "devise/lib/devise.rb" and
|
287
|
-
"devise/spec/devise_spec.rb".
|
294
|
+
create two files: "devise/lib/devise.rb", and "devise/test/devise_test.rb". The user will then be asked (via a prompt by the <tt>yes?</tt> method) whether or not they would like to copy the MIT License. If you want to change the test framework, you can add the option: <tt>thor newgem devise --test-framework=rspec</tt>
|
295
|
+
|
296
|
+
This will generate two files - "devise/lib/devise.rb" and "devise/spec/devise_spec.rb".
|
288
297
|
|
289
|
-
|
298
|
+
## Further Reading
|
290
299
|
|
291
|
-
Thor
|
292
|
-
through the documentation
|
293
|
-
options Thor offers.
|
300
|
+
Thor offers many scripting possibilities beyond these examples. Be sure to read
|
301
|
+
through the [documentation](http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor.html) and [specs](http://github.com/wycats/thor/tree/master/spec/) to get a better understanding of the options available.
|
294
302
|
|
295
|
-
|
303
|
+
## License
|
296
304
|
|
297
|
-
|
305
|
+
Released under the MIT License. See the LICENSE file for further details.
|
data/Thorfile
CHANGED
@@ -10,7 +10,7 @@ rescue LoadError
|
|
10
10
|
end
|
11
11
|
|
12
12
|
GEM_NAME = 'thor'
|
13
|
-
EXTRA_RDOC_FILES = ["README.
|
13
|
+
EXTRA_RDOC_FILES = ["README.md", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"]
|
14
14
|
|
15
15
|
class Default < Thor
|
16
16
|
include Thor::RakeCompat
|
@@ -44,7 +44,7 @@ class Default < Thor
|
|
44
44
|
require 'jeweler'
|
45
45
|
Jeweler::Tasks.new do |s|
|
46
46
|
s.name = GEM_NAME
|
47
|
-
s.version = Thor::VERSION
|
47
|
+
s.version = Thor::VERSION.dup
|
48
48
|
s.rubyforge_project = "textmate"
|
49
49
|
s.platform = Gem::Platform::RUBY
|
50
50
|
s.summary = "A scripting framework that replaces rake, sake and rubigen"
|
data/lib/thor.rb
CHANGED
@@ -126,42 +126,6 @@ class Thor
|
|
126
126
|
build_option(name, options, scope)
|
127
127
|
end
|
128
128
|
|
129
|
-
# Parses the task and options from the given args, instantiate the class
|
130
|
-
# and invoke the task. This method is used when the arguments must be parsed
|
131
|
-
# from an array. If you are inside Ruby and want to use a Thor class, you
|
132
|
-
# can simply initialize it:
|
133
|
-
#
|
134
|
-
# script = MyScript.new(args, options, config)
|
135
|
-
# script.invoke(:task, first_arg, second_arg, third_arg)
|
136
|
-
#
|
137
|
-
def start(original_args=ARGV, config={})
|
138
|
-
@@original_args = original_args
|
139
|
-
|
140
|
-
super do |given_args|
|
141
|
-
meth = given_args.first.to_s
|
142
|
-
|
143
|
-
if !meth.empty? && (map[meth] || meth !~ /^\-/)
|
144
|
-
given_args.shift
|
145
|
-
else
|
146
|
-
meth = nil
|
147
|
-
end
|
148
|
-
|
149
|
-
meth = normalize_task_name(meth)
|
150
|
-
task = all_tasks[meth]
|
151
|
-
|
152
|
-
if task
|
153
|
-
args, opts = Thor::Options.split(given_args)
|
154
|
-
config.merge!(:task_options => task.options)
|
155
|
-
else
|
156
|
-
args, opts = given_args, {}
|
157
|
-
end
|
158
|
-
|
159
|
-
task ||= Thor::DynamicTask.new(meth)
|
160
|
-
trailing = args[Range.new(arguments.size, -1)]
|
161
|
-
new(args, opts, config).invoke(task, trailing || [])
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
129
|
# Prints help information for the given task.
|
166
130
|
#
|
167
131
|
# ==== Parameters
|
@@ -215,18 +179,73 @@ class Thor
|
|
215
179
|
end
|
216
180
|
|
217
181
|
def subcommands
|
218
|
-
|
182
|
+
@subcommands ||= from_superclass(:subcommands, [])
|
219
183
|
end
|
220
184
|
|
221
185
|
def subcommand(subcommand, subcommand_class)
|
222
|
-
|
223
|
-
subcommands[subcommand] = subcommand_class
|
186
|
+
self.subcommands << subcommand.to_s
|
224
187
|
subcommand_class.subcommand_help subcommand
|
225
|
-
define_method(subcommand) { |*
|
188
|
+
define_method(subcommand) { |*args| invoke subcommand_class, args }
|
189
|
+
end
|
190
|
+
|
191
|
+
# Extend check unknown options to accept a hash of conditions.
|
192
|
+
#
|
193
|
+
# === Parameters
|
194
|
+
# options<Hash>: A hash containing :only and/or :except keys
|
195
|
+
def check_unknown_options!(options={})
|
196
|
+
@check_unknown_options ||= Hash.new
|
197
|
+
options.each do |key, value|
|
198
|
+
if value
|
199
|
+
@check_unknown_options[key] = Array(value)
|
200
|
+
else
|
201
|
+
@check_unknown_options.delete(key)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
@check_unknown_options
|
205
|
+
end
|
206
|
+
|
207
|
+
# Overwrite check_unknown_options? to take subcommands and options into account.
|
208
|
+
def check_unknown_options?(config) #:nodoc:
|
209
|
+
options = check_unknown_options
|
210
|
+
return false unless options
|
211
|
+
|
212
|
+
task = config[:current_task]
|
213
|
+
return true unless task
|
214
|
+
|
215
|
+
name = task.name
|
216
|
+
|
217
|
+
if subcommands.include?(name)
|
218
|
+
false
|
219
|
+
elsif options[:except]
|
220
|
+
!options[:except].include?(name.to_sym)
|
221
|
+
elsif options[:only]
|
222
|
+
options[:only].include?(name.to_sym)
|
223
|
+
else
|
224
|
+
true
|
225
|
+
end
|
226
226
|
end
|
227
227
|
|
228
228
|
protected
|
229
229
|
|
230
|
+
# The method responsible for dispatching given the args.
|
231
|
+
def dispatch(meth, given_args, given_opts, config) #:nodoc:
|
232
|
+
meth ||= retrieve_task_name(given_args)
|
233
|
+
task = all_tasks[normalize_task_name(meth)]
|
234
|
+
|
235
|
+
if task
|
236
|
+
args, opts = Thor::Options.split(given_args)
|
237
|
+
else
|
238
|
+
args, opts = given_args, nil
|
239
|
+
task = Thor::DynamicTask.new(meth)
|
240
|
+
end
|
241
|
+
|
242
|
+
opts = given_opts || opts || []
|
243
|
+
config.merge!(:current_task => task, :task_options => task.options)
|
244
|
+
|
245
|
+
trailing = args[Range.new(arguments.size, -1)]
|
246
|
+
new(args, opts, config).invoke_task(task, trailing || [])
|
247
|
+
end
|
248
|
+
|
230
249
|
# The banner for this class. You can customize it if you are invoking the
|
231
250
|
# thor class by another ways which is not the Thor::Runner. It receives
|
232
251
|
# the task that is going to be invoked and a boolean which indicates if
|
@@ -243,10 +262,10 @@ class Thor
|
|
243
262
|
def create_task(meth) #:nodoc:
|
244
263
|
if @usage && @desc
|
245
264
|
base_class = @hide ? Thor::HiddenTask : Thor::Task
|
246
|
-
tasks[meth
|
265
|
+
tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
|
247
266
|
@usage, @desc, @long_desc, @method_options, @hide = nil
|
248
267
|
true
|
249
|
-
elsif self.all_tasks[meth
|
268
|
+
elsif self.all_tasks[meth] || meth == "method_missing"
|
250
269
|
true
|
251
270
|
else
|
252
271
|
puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
|
@@ -261,9 +280,19 @@ class Thor
|
|
261
280
|
@method_options = nil
|
262
281
|
end
|
263
282
|
|
283
|
+
# Retrieve the task name from given args.
|
284
|
+
def retrieve_task_name(args) #:nodoc:
|
285
|
+
meth = args.first.to_s unless args.empty?
|
286
|
+
|
287
|
+
if meth && (map[meth] || meth !~ /^\-/)
|
288
|
+
args.shift
|
289
|
+
else
|
290
|
+
nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
264
294
|
# Receives a task name (can be nil), and try to get a map from it.
|
265
295
|
# If a map can't be found use the sent name or the default task.
|
266
|
-
#
|
267
296
|
def normalize_task_name(meth) #:nodoc:
|
268
297
|
meth = map[meth.to_s] || meth || default_task
|
269
298
|
meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
|
@@ -275,11 +304,6 @@ class Thor
|
|
275
304
|
def help(task = nil, subcommand = true); super; end
|
276
305
|
RUBY
|
277
306
|
end
|
278
|
-
|
279
|
-
end
|
280
|
-
|
281
|
-
def subcommand_args
|
282
|
-
@@original_args[1..-1]
|
283
307
|
end
|
284
308
|
|
285
309
|
include Thor::Base
|
data/lib/thor/base.rb
CHANGED
@@ -53,7 +53,7 @@ class Thor
|
|
53
53
|
|
54
54
|
opts = Thor::Options.new(parse_options, hash_options)
|
55
55
|
self.options = opts.parse(array_options)
|
56
|
-
opts.check_unknown! if self.class.check_unknown_options?
|
56
|
+
opts.check_unknown! if self.class.check_unknown_options?(config)
|
57
57
|
end
|
58
58
|
|
59
59
|
class << self
|
@@ -114,8 +114,12 @@ class Thor
|
|
114
114
|
@check_unknown_options = true
|
115
115
|
end
|
116
116
|
|
117
|
-
def check_unknown_options
|
118
|
-
@check_unknown_options
|
117
|
+
def check_unknown_options #:nodoc:
|
118
|
+
@check_unknown_options ||= from_superclass(:check_unknown_options, false)
|
119
|
+
end
|
120
|
+
|
121
|
+
def check_unknown_options?(config) #:nodoc:
|
122
|
+
!!check_unknown_options
|
119
123
|
end
|
120
124
|
|
121
125
|
# Adds an argument to the class and creates an attr_accessor for it.
|
@@ -364,19 +368,25 @@ class Thor
|
|
364
368
|
#
|
365
369
|
def namespace(name=nil)
|
366
370
|
case name
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
+
when nil
|
372
|
+
@namespace ||= Thor::Util.namespace_from_thor_class(self)
|
373
|
+
else
|
374
|
+
@namespace = name.to_s
|
371
375
|
end
|
372
376
|
end
|
373
377
|
|
374
|
-
#
|
378
|
+
# Parses the task and options from the given args, instantiate the class
|
379
|
+
# and invoke the task. This method is used when the arguments must be parsed
|
380
|
+
# from an array. If you are inside Ruby and want to use a Thor class, you
|
381
|
+
# can simply initialize it:
|
382
|
+
#
|
383
|
+
# script = MyScript.new(args, options, config)
|
384
|
+
# script.invoke(:task, first_arg, second_arg, third_arg)
|
375
385
|
#
|
376
386
|
def start(given_args=ARGV, config={})
|
377
|
-
self.debugging = given_args.
|
387
|
+
self.debugging = given_args.delete("--debug")
|
378
388
|
config[:shell] ||= Thor::Base.shell.new
|
379
|
-
|
389
|
+
dispatch(nil, given_args.dup, nil, config)
|
380
390
|
rescue Thor::Error => e
|
381
391
|
debugging ? (raise e) : config[:shell].error(e.message)
|
382
392
|
exit(1) if exit_on_failure?
|
@@ -535,6 +545,12 @@ class Thor
|
|
535
545
|
# class.
|
536
546
|
def initialize_added #:nodoc:
|
537
547
|
end
|
548
|
+
|
549
|
+
# SIGNATURE: The hook invoked by start.
|
550
|
+
def dispatch(task, given_args, given_opts, config) #:nodoc:
|
551
|
+
raise NotImplementedError
|
552
|
+
end
|
553
|
+
|
538
554
|
end
|
539
555
|
end
|
540
556
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -22,21 +22,6 @@ class Thor::Group
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
# Start works differently in Thor::Group, it simply invokes all tasks
|
26
|
-
# inside the class.
|
27
|
-
#
|
28
|
-
def start(original_args=ARGV, config={})
|
29
|
-
super do |given_args|
|
30
|
-
if Thor::HELP_MAPPINGS.include?(given_args.first)
|
31
|
-
help(config[:shell])
|
32
|
-
return
|
33
|
-
end
|
34
|
-
|
35
|
-
args, opts = Thor::Options.split(given_args)
|
36
|
-
new(args, opts, config).invoke
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
25
|
# Prints help information.
|
41
26
|
#
|
42
27
|
# ==== Options
|
@@ -225,6 +210,23 @@ class Thor::Group
|
|
225
210
|
|
226
211
|
protected
|
227
212
|
|
213
|
+
# The method responsible for dispatching given the args.
|
214
|
+
def dispatch(task, given_args, given_opts, config) #:nodoc:
|
215
|
+
if Thor::HELP_MAPPINGS.include?(given_args.first)
|
216
|
+
help(config[:shell])
|
217
|
+
return
|
218
|
+
end
|
219
|
+
|
220
|
+
args, opts = Thor::Options.split(given_args)
|
221
|
+
opts = given_opts || opts
|
222
|
+
|
223
|
+
if task
|
224
|
+
new(args, opts, config).invoke_task(all_tasks[task])
|
225
|
+
else
|
226
|
+
new(args, opts, config).invoke_all
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
228
230
|
# The banner for this class. You can customize it if you are invoking the
|
229
231
|
# thor class by another ways which is not the Thor::Runner.
|
230
232
|
def banner
|
data/lib/thor/invocation.rb
CHANGED
@@ -10,10 +10,10 @@ class Thor
|
|
10
10
|
# available only in class methods invocations (i.e. in Thor::Group).
|
11
11
|
def prepare_for_invocation(key, name) #:nodoc:
|
12
12
|
case name
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
when Symbol, String
|
14
|
+
Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
|
15
|
+
else
|
16
|
+
name
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -94,29 +94,34 @@ class Thor
|
|
94
94
|
# invoke Rspec::RR, [], :style => :foo
|
95
95
|
#
|
96
96
|
def invoke(name=nil, *args)
|
97
|
+
if name.nil?
|
98
|
+
warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
|
99
|
+
return invoke_all
|
100
|
+
end
|
101
|
+
|
97
102
|
args.unshift(nil) if Array === args.first || NilClass === args.first
|
98
103
|
task, args, opts, config = args
|
99
104
|
|
100
|
-
|
101
|
-
|
105
|
+
klass, task = _retrieve_class_and_task(name, task)
|
106
|
+
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
107
|
+
|
108
|
+
args, opts, config = _parse_initialization_options(args, opts, config)
|
109
|
+
klass.send(:dispatch, task, args, opts, config)
|
110
|
+
end
|
102
111
|
|
103
|
-
|
104
|
-
|
112
|
+
# Invoke the given task if the given args.
|
113
|
+
def invoke_task(task, *args) #:nodoc:
|
114
|
+
current = @_invocations[self.class]
|
105
115
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
task.run(instance, method_args)
|
110
|
-
end
|
116
|
+
unless current.include?(task.name)
|
117
|
+
current << task.name
|
118
|
+
task.run(self, *args)
|
111
119
|
end
|
120
|
+
end
|
112
121
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
iterator.call(nil, task)
|
117
|
-
else
|
118
|
-
klass.all_tasks.map(&iterator)
|
119
|
-
end
|
122
|
+
# Invoke all tasks for the current instance.
|
123
|
+
def invoke_all #:nodoc:
|
124
|
+
self.class.all_tasks.map { |_, task| invoke_task(task) }
|
120
125
|
end
|
121
126
|
|
122
127
|
# Invokes using shell padding.
|
@@ -131,50 +136,33 @@ class Thor
|
|
131
136
|
{ :invocations => @_invocations }
|
132
137
|
end
|
133
138
|
|
134
|
-
# This method
|
135
|
-
#
|
136
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
139
|
+
# This method simply retrieves the class and task to be invoked.
|
140
|
+
# If the name is nil or the given name is a task in the current class,
|
141
|
+
# use the given name and return self as class. Otherwise, call
|
142
|
+
# prepare_for_invocation in the current class.
|
143
|
+
def _retrieve_class_and_task(name, sent_task=nil) #:nodoc:
|
144
|
+
case
|
145
|
+
when name.nil?
|
146
|
+
[self.class, nil]
|
147
|
+
when self.class.all_tasks[name.to_s]
|
148
|
+
[self.class, name.to_s]
|
142
149
|
else
|
143
|
-
|
144
|
-
task
|
150
|
+
klass, task = self.class.prepare_for_invocation(nil, name)
|
151
|
+
[klass, task || sent_task]
|
145
152
|
end
|
146
|
-
|
147
|
-
# If the object was not set, use self and use the name as task.
|
148
|
-
object, task = self, name unless object
|
149
|
-
return object, _validate_task(object, task)
|
150
|
-
end
|
151
|
-
|
152
|
-
# Check if the object given is a Thor class object and get a task object
|
153
|
-
# for it.
|
154
|
-
def _validate_task(object, task) #:nodoc:
|
155
|
-
klass = object.is_a?(Class) ? object : object.class
|
156
|
-
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
157
|
-
|
158
|
-
task ||= klass.default_task if klass.respond_to?(:default_task)
|
159
|
-
task = klass.all_tasks[task.to_s] || Thor::DynamicTask.new(task) if task && !task.is_a?(Thor::Task)
|
160
|
-
task
|
161
153
|
end
|
162
154
|
|
163
155
|
# Initialize klass using values stored in the @_initializer.
|
164
|
-
def
|
165
|
-
|
166
|
-
klass = object
|
156
|
+
def _parse_initialization_options(args, opts, config) #:nodoc:
|
157
|
+
stored_args, stored_opts, stored_config = @_initializer
|
167
158
|
|
168
|
-
|
169
|
-
|
170
|
-
opts ||= stored_opts.dup
|
159
|
+
args ||= stored_args.dup
|
160
|
+
opts ||= stored_opts.dup
|
171
161
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
[ object.class, object ]
|
177
|
-
end
|
162
|
+
config ||= {}
|
163
|
+
config = stored_config.merge(_shared_configuration).merge!(config)
|
164
|
+
|
165
|
+
[ args, opts, config ]
|
178
166
|
end
|
179
167
|
end
|
180
168
|
end
|
data/lib/thor/parser/options.rb
CHANGED
@@ -151,6 +151,8 @@ class Thor
|
|
151
151
|
elsif option.string? && !option.required?
|
152
152
|
# Return the default if there is one, else the human name
|
153
153
|
return option.lazy_default || option.default || option.human_name
|
154
|
+
elsif option.lazy_default
|
155
|
+
return option.lazy_default
|
154
156
|
else
|
155
157
|
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
|
156
158
|
end
|
data/lib/thor/shell.rb
CHANGED
data/lib/thor/shell/basic.rb
CHANGED
@@ -35,13 +35,15 @@ class Thor
|
|
35
35
|
# say("I know you knew that.")
|
36
36
|
#
|
37
37
|
def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
|
38
|
-
message
|
39
|
-
message
|
38
|
+
message = message.to_s
|
39
|
+
message = set_color(message, color) if color
|
40
|
+
|
41
|
+
spaces = " " * padding
|
40
42
|
|
41
43
|
if force_new_line
|
42
|
-
$stdout.puts(message)
|
44
|
+
$stdout.puts(spaces + message)
|
43
45
|
else
|
44
|
-
$stdout.print(message)
|
46
|
+
$stdout.print(spaces + message)
|
45
47
|
end
|
46
48
|
$stdout.flush
|
47
49
|
end
|
@@ -58,7 +60,9 @@ class Thor
|
|
58
60
|
|
59
61
|
status = status.to_s.rjust(12)
|
60
62
|
status = set_color status, color, true if color
|
61
|
-
|
63
|
+
|
64
|
+
$stdout.puts "#{status}#{spaces}#{message}"
|
65
|
+
$stdout.flush
|
62
66
|
end
|
63
67
|
|
64
68
|
# Make a question the to user and returns true if the user replies "y" or
|
data/lib/thor/version.rb
CHANGED
data/spec/base_spec.rb
CHANGED
@@ -244,6 +244,12 @@ describe Thor::Base do
|
|
244
244
|
MyScript.start(["foo", "bar", "--force", "true", "--unknown", "baz"])
|
245
245
|
}.strip.must == "Unknown switches '--unknown'"
|
246
246
|
end
|
247
|
+
|
248
|
+
it "checks unknown options except specified" do
|
249
|
+
capture(:stderr) {
|
250
|
+
MyScript.start(["with_optional", "NAME", "--omg", "--invalid"]).must == ["NAME", {}]
|
251
|
+
}.strip.must be_empty
|
252
|
+
end
|
247
253
|
end
|
248
254
|
|
249
255
|
describe "attr_*" do
|
data/spec/fixtures/script.thor
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class MyScript < Thor
|
2
|
-
check_unknown_options!
|
2
|
+
check_unknown_options! :except => :with_optional
|
3
3
|
|
4
4
|
attr_accessor :some_attribute
|
5
5
|
attr_writer :another_attribute
|
@@ -73,6 +73,9 @@ END
|
|
73
73
|
|
74
74
|
method_options :all => :boolean
|
75
75
|
method_option :lazy, :lazy_default => "yes"
|
76
|
+
method_option :lazy_numeric, :type => :numeric, :lazy_default => 42
|
77
|
+
method_option :lazy_array, :type => :array, :lazy_default => %w[eat at joes]
|
78
|
+
method_option :lazy_hash, :type => :hash, :lazy_default => {'swedish' => 'meatballs'}
|
76
79
|
desc "with_optional NAME", "invoke with optional name"
|
77
80
|
def with_optional(name=nil)
|
78
81
|
[ name, options ]
|
@@ -136,6 +139,13 @@ class Barn < Thor
|
|
136
139
|
puts "Open sesame!"
|
137
140
|
end
|
138
141
|
end
|
142
|
+
|
143
|
+
desc "paint [COLOR]", "paint the barn"
|
144
|
+
method_option :coats, :type => :numeric, :default => 2, :desc => 'how many coats of paint'
|
145
|
+
def paint(color='red')
|
146
|
+
puts "#{options[:coats]} coats of #{color} paint"
|
147
|
+
end
|
148
|
+
|
139
149
|
end
|
140
150
|
|
141
151
|
module Scripts
|
@@ -150,6 +160,8 @@ module Scripts
|
|
150
160
|
end
|
151
161
|
|
152
162
|
class MyDefaults < Thor
|
163
|
+
check_unknown_options!
|
164
|
+
|
153
165
|
namespace :default
|
154
166
|
desc "cow", "prints 'moo'"
|
155
167
|
def cow
|
data/spec/invocation_spec.rb
CHANGED
@@ -37,13 +37,6 @@ describe Thor::Invocation do
|
|
37
37
|
base.invoke(B, :one, ["Jose"]).must == "Valim, Jose"
|
38
38
|
end
|
39
39
|
|
40
|
-
it "accepts a Thor instance as argument" do
|
41
|
-
invoked = B.new([], :last_name => "Valim")
|
42
|
-
base = A.new
|
43
|
-
base.invoke(invoked, :one, ["Jose"]).must == "Valim, Jose"
|
44
|
-
base.invoke(invoked, :one, ["Jose"]).must be_nil
|
45
|
-
end
|
46
|
-
|
47
40
|
it "allows customized options to be given" do
|
48
41
|
base = A.new([], :last_name => "Wrong")
|
49
42
|
base.invoke(B, :one, ["Jose"], :last_name => "Valim").must == "Valim, Jose"
|
@@ -91,7 +84,7 @@ describe Thor::Invocation do
|
|
91
84
|
|
92
85
|
it "raises Thor::UndefinedTaskError if the task can't be found even if all tasks where already executed" do
|
93
86
|
base = C.new
|
94
|
-
silence(:stdout){ base.
|
87
|
+
silence(:stdout){ base.invoke_all }
|
95
88
|
|
96
89
|
lambda do
|
97
90
|
base.invoke("foo:bar")
|
data/spec/spec_helper.rb
CHANGED
data/spec/thor_spec.rb
CHANGED
@@ -8,15 +8,43 @@ describe Thor do
|
|
8
8
|
options.must == { "force" => true }
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
describe ":lazy_default" do
|
12
|
+
it "is absent when option is not specified" do
|
13
|
+
arg, options = MyScript.start(["with_optional"])
|
14
|
+
options.must == {}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets a default that can be overridden for strings" do
|
18
|
+
arg, options = MyScript.start(["with_optional", "--lazy"])
|
19
|
+
options.must == { "lazy" => "yes" }
|
20
|
+
|
21
|
+
arg, options = MyScript.start(["with_optional", "--lazy", "yesyes!"])
|
22
|
+
options.must == { "lazy" => "yesyes!" }
|
23
|
+
end
|
24
|
+
|
25
|
+
it "sets a default that can be overridden for numerics" do
|
26
|
+
arg, options = MyScript.start(["with_optional", "--lazy-numeric"])
|
27
|
+
options.must == { "lazy_numeric" => 42 }
|
28
|
+
|
29
|
+
arg, options = MyScript.start(["with_optional", "--lazy-numeric", 20000])
|
30
|
+
options.must == { "lazy_numeric" => 20000 }
|
31
|
+
end
|
14
32
|
|
15
|
-
|
16
|
-
|
33
|
+
it "sets a default that can be overridden for arrays" do
|
34
|
+
arg, options = MyScript.start(["with_optional", "--lazy-array"])
|
35
|
+
options.must == { "lazy_array" => %w[eat at joes] }
|
17
36
|
|
18
|
-
|
19
|
-
|
37
|
+
arg, options = MyScript.start(["with_optional", "--lazy-array", "hello", "there"])
|
38
|
+
options.must == { "lazy_array" => %w[hello there] }
|
39
|
+
end
|
40
|
+
|
41
|
+
it "sets a default that can be overridden for hashes" do
|
42
|
+
arg, options = MyScript.start(["with_optional", "--lazy-hash"])
|
43
|
+
options.must == { "lazy_hash" => {'swedish' => 'meatballs'} }
|
44
|
+
|
45
|
+
arg, options = MyScript.start(["with_optional", "--lazy-hash", "polish:sausage"])
|
46
|
+
options.must == { "lazy_hash" => {'polish' => 'sausage'} }
|
47
|
+
end
|
20
48
|
end
|
21
49
|
|
22
50
|
describe "when :for is supplied" do
|
@@ -182,6 +210,10 @@ describe Thor do
|
|
182
210
|
it "passes arguments to subcommand classes" do
|
183
211
|
capture(:stdout){ Scripts::MyDefaults.start(["barn", "open", "shotgun"]) }.strip.must == "That's going to leave a mark."
|
184
212
|
end
|
213
|
+
|
214
|
+
it "ignores unknown options (the subcommand class will handle them)" do
|
215
|
+
capture(:stdout){ Scripts::MyDefaults.start(["barn", "paint", "blue", "--coats", "4"])}.strip.must == "4 coats of blue paint"
|
216
|
+
end
|
185
217
|
end
|
186
218
|
|
187
219
|
describe "#help" do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 39
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 14
|
9
|
+
- 0
|
10
|
+
version: 0.14.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Yehuda Katz
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-07-
|
19
|
+
date: 2010-07-26 00:00:00 +02:00
|
20
20
|
default_executable:
|
21
21
|
dependencies: []
|
22
22
|
|
@@ -30,12 +30,12 @@ extensions: []
|
|
30
30
|
extra_rdoc_files:
|
31
31
|
- CHANGELOG.rdoc
|
32
32
|
- LICENSE
|
33
|
-
- README.
|
33
|
+
- README.md
|
34
34
|
- Thorfile
|
35
35
|
files:
|
36
36
|
- CHANGELOG.rdoc
|
37
37
|
- LICENSE
|
38
|
-
- README.
|
38
|
+
- README.md
|
39
39
|
- Thorfile
|
40
40
|
- bin/rake2thor
|
41
41
|
- bin/thor
|