thor 0.13.8 → 0.14.0
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/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
|