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 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 to the invocation system.
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:
@@ -1,7 +1,21 @@
1
- = thor
1
+ # Thor
2
2
 
3
- Map options to a class. Simply create a class with the appropriate annotations
4
- and have options automatically map to functions and parameters.
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. In this case, convert -L to :list
38
- 3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description
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
- == Types for <tt>method_options</tt>
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, examples:
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. The
61
- type is assumed to be string. If you want a required hash with default values
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. For example:
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. You can supply aliases in both short and declarative
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
- == Namespaces
99
+ ## Namespaces
86
100
 
87
- By default, your Thor tasks are invoked using Ruby namespace. In the example
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 hould be invoked as:
127
+ And then your tasks should be invoked as:
114
128
 
115
129
  thor myapp:install name --force
116
130
 
117
- == Invocations
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[http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor.html].
161
+ [documentation](http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor.html).
149
162
 
150
- == Thor::Group
163
+ ## Thor::Group
151
164
 
152
- Thor has a special class called Thor::Group. The main difference to Thor class
153
- is that it invokes all tasks at once. The example above could be rewritten in
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. Notice you should describe (using the method <tt>desc</tt>)
177
- only the class and not each task anymore. Thor::Group is a great tool to create
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
- == Actions
226
+ ## Actions
214
227
 
215
- Thor comes with several actions which helps with script and generator tasks. You
216
- might be familiar with them since some came from Rails Templates. They are:
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. This is the directory where your templates should be
230
- placed. Be sure to check the documentation on actions[http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor/Actions.html].
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
- == Generators
245
+ ## Generators
233
246
 
234
- A great use for Thor is creating custom generators. Combining Thor::Group,
235
- Thor::Actions and ERB templates makes this very easy. Here is an example:
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. It should read:
268
- <tt>thor newgem NAME</tt>. This shows that we have to supply a NAME
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. Be sure to read the documentation[http://rdoc.info/rdoc/wycats/thor/blob/f939a3e8a854616784cac1dcff04ef4f3ee5f7ff/Thor/Actions.html] for
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
- "devise/test/devise_test.rb". The user will then be prompt (with the
283
- use of the method <tt>yes?</tt>) if he wants to copy the MITLICENSE. If you
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
- == Further Reading
298
+ ## Further Reading
290
299
 
291
- Thor has many scripting possibilities beyond these examples. Be sure to read
292
- 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 all the
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
- == License
303
+ ## License
296
304
 
297
- See MIT LICENSE.
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.rdoc", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"]
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
- @@subcommands ||= {}
182
+ @subcommands ||= from_superclass(:subcommands, [])
219
183
  end
220
184
 
221
185
  def subcommand(subcommand, subcommand_class)
222
- subcommand = subcommand.to_s
223
- subcommands[subcommand] = subcommand_class
186
+ self.subcommands << subcommand.to_s
224
187
  subcommand_class.subcommand_help subcommand
225
- define_method(subcommand) { |*_| subcommand_class.start(subcommand_args) }
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.to_s] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
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.to_s] || meth.to_sym == :method_missing
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? #:nodoc:
118
- @check_unknown_options || false
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
- when nil
368
- @namespace ||= Thor::Util.namespace_from_thor_class(self)
369
- else
370
- @namespace = name.to_s
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
- # Default way to start generators from the command line.
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.include?("--debug")
387
+ self.debugging = given_args.delete("--debug")
378
388
  config[:shell] ||= Thor::Base.shell.new
379
- yield(given_args.dup)
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
@@ -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
- when Symbol, String
14
- Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
15
- else
16
- name
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
- object, task = _prepare_for_invocation(name, task)
101
- klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
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
- method_args = []
104
- current = @_invocations[klass]
112
+ # Invoke the given task if the given args.
113
+ def invoke_task(task, *args) #:nodoc:
114
+ current = @_invocations[self.class]
105
115
 
106
- iterator = proc do |_, task|
107
- unless current.include?(task.name)
108
- current << task.name
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
- if task
114
- args ||= []
115
- method_args = args[Range.new(klass.arguments.size, -1)] || []
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 can receive several different types of arguments and it's then
135
- # responsible to normalize them by returning the object where the task should
136
- # be invoked and a Thor::Task object.
137
- def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
138
- if name.is_a?(Thor::Task)
139
- task = name
140
- elsif task = self.class.all_tasks[name.to_s]
141
- object = self
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
- object, task = self.class.prepare_for_invocation(nil, name)
144
- task ||= sent_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 _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
165
- if object.is_a?(Class)
166
- klass = object
156
+ def _parse_initialization_options(args, opts, config) #:nodoc:
157
+ stored_args, stored_opts, stored_config = @_initializer
167
158
 
168
- stored_args, stored_opts, stored_config = @_initializer
169
- args ||= stored_args.dup
170
- opts ||= stored_opts.dup
159
+ args ||= stored_args.dup
160
+ opts ||= stored_opts.dup
171
161
 
172
- config ||= {}
173
- config = stored_config.merge(_shared_configuration).merge!(config)
174
- [ klass, klass.new(args, opts, config) ]
175
- else
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
@@ -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
@@ -27,7 +27,7 @@ class Thor
27
27
 
28
28
  autoload :Basic, 'thor/shell/basic'
29
29
  autoload :Color, 'thor/shell/color'
30
- autoload :HTML, 'thor/shell/HTML'
30
+ autoload :HTML, 'thor/shell/html'
31
31
 
32
32
  # Add shell to initialize config values.
33
33
  #
@@ -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 = message.to_s
39
- message = set_color(message, color) if color
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
- say "#{status}#{spaces}#{message}", nil, true
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
@@ -1,3 +1,3 @@
1
1
  class Thor
2
- VERSION = "0.13.8".freeze
2
+ VERSION = "0.14.0".freeze
3
3
  end
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
@@ -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
@@ -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.invoke }
87
+ silence(:stdout){ base.invoke_all }
95
88
 
96
89
  lambda do
97
90
  base.invoke("foo:bar")
data/spec/spec_helper.rb CHANGED
@@ -13,6 +13,7 @@ require 'fakeweb' # You need fakeweb installed to run specs (but not to run Tho
13
13
  # Set shell to basic
14
14
  $0 = "thor"
15
15
  $thor_runner = true
16
+ ARGV.clear
16
17
  Thor::Base.shell = Thor::Shell::Basic
17
18
 
18
19
  # Load fixtures
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
- it "sets method_option with given parameters" do
12
- arg, options = MyScript.start(["with_optional"])
13
- options.must == {}
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
- arg, options = MyScript.start(["with_optional", "--lazy"])
16
- options.must == { "lazy" => "yes" }
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
- arg, options = MyScript.start(["with_optional", "--lazy", "yesyes!"])
19
- options.must == { "lazy" => "yesyes!" }
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: 59
4
+ hash: 39
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 13
9
- - 8
10
- version: 0.13.8
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-16 00:00:00 +02:00
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.rdoc
33
+ - README.md
34
34
  - Thorfile
35
35
  files:
36
36
  - CHANGELOG.rdoc
37
37
  - LICENSE
38
- - README.rdoc
38
+ - README.md
39
39
  - Thorfile
40
40
  - bin/rake2thor
41
41
  - bin/thor