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 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