Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 380 lines (335 sloc) 11.487 kb
1b58d9d @josevalim Start to deal with generators on Runner.
josevalim authored
1 require 'thor/base'
82ff27a @wycats Change name to thor. Last commit to hermes repo.
wycats authored
2
3 class Thor
bdb1eef @josevalim Add argument and option as methods to setup options.
josevalim authored
4 class << self
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
5 # Sets the default task when thor is executed without an explicit task to be called.
6 #
7 # ==== Parameters
b341095 @wycats Additional improvements:
wycats authored
8 # meth<Symbol>:: name of the default task
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
9 #
10 def default_task(meth=nil)
11 case meth
12 when :none
13 @default_task = 'help'
14 when nil
15 @default_task ||= from_superclass(:default_task, 'help')
16 else
17 @default_task = meth.to_s
18 end
19 end
20
fe45e3d @smerritt Add Thor.register to let you register additional subcommands.
smerritt authored
21 # Registers another Thor subclass as a command.
22 #
23 # ==== Parameters
24 # klass<Class>:: Thor subclass to register
25 # command<String>:: Subcommand name to use
26 # usage<String>:: Short usage for the subcommand
27 # description<String>:: Description for the subcommand
28 def register(klass, subcommand_name, usage, description, options={})
40fc01a @smerritt Make Thor.register work with a Thor::Group argument.
smerritt authored
29 if klass <= Thor::Group
30 desc usage, description, options
70d4972 @argent-smith Thor#register 1.9 compatibility fix
argent-smith authored
31 define_method(subcommand_name) { |*args| invoke(klass, args) }
40fc01a @smerritt Make Thor.register work with a Thor::Group argument.
smerritt authored
32 else
33 desc usage, description, options
34 subcommand subcommand_name, klass
35 end
fe45e3d @smerritt Add Thor.register to let you register additional subcommands.
smerritt authored
36 end
37
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
38 # Defines the usage and the description of the next task.
39 #
40 # ==== Parameters
41 # usage<String>
42 # description<String>
71dc13e @josevalim Allow hidden tasks.
josevalim authored
43 # options<String>
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
44 #
5ebe094 @josevalim Added :for support to desc as well.
josevalim authored
45 def desc(usage, description, options={})
46 if options[:for]
47 task = find_and_refresh_task(options[:for])
48 task.usage = usage if usage
49 task.description = description if description
50 else
71dc13e @josevalim Allow hidden tasks.
josevalim authored
51 @usage, @desc, @hide = usage, description, options[:hide] || false
5ebe094 @josevalim Added :for support to desc as well.
josevalim authored
52 end
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
53 end
54
473ea67 @joshbuddy Added long descriptions to tasks for more detailed help messages
joshbuddy authored
55 # Defines the long description of the next task.
56 #
57 # ==== Parameters
58 # long description<String>
59 #
60 def long_desc(long_description, options={})
61 if options[:for]
62 task = find_and_refresh_task(options[:for])
63 task.long_description = long_description if long_description
64 else
65 @long_desc = long_description
66 end
67 end
68
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
69 # Maps an input to a task. If you define:
70 #
71 # map "-T" => "list"
bdb1eef @josevalim Add argument and option as methods to setup options.
josevalim authored
72 #
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
73 # Running:
74 #
75 # thor -T
76 #
77 # Will invoke the list task.
78 #
79 # ==== Parameters
2212ae3 @josevalim Add description to Thor::Group.
josevalim authored
80 # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
81 #
82 def map(mappings=nil)
83 @map ||= from_superclass(:map, {})
84
85 if mappings
86 mappings.each do |key, value|
87 if key.respond_to?(:each)
88 key.each {|subkey| @map[subkey] = value}
89 else
90 @map[key] = value
91 end
92 end
93 end
94
95 @map
bdb1eef @josevalim Add argument and option as methods to setup options.
josevalim authored
96 end
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
97
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
98 # Declares the options for the next task to be declared.
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
99 #
100 # ==== Parameters
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
101 # Hash[Symbol => Object]:: The hash key is the name of the option and the value
c5015fb @josevalim Remove type :default.
josevalim authored
102 # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
103 # or :required (string). If you give a value, the type of the value is used.
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
104 #
105 def method_options(options=nil)
9308cec @josevalim Make options a hash.
josevalim authored
106 @method_options ||= {}
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
107 build_options(options, @method_options) if options
108 @method_options
109 end
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
110
b341095 @wycats Additional improvements:
wycats authored
111 alias options method_options
112
094cd62 @josevalim Fix specs.
josevalim authored
113 # Adds an option to the set of method options. If :for is given as option,
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
114 # it allows you to change the options from a previous defined task.
115 #
116 # def previous_task
117 # # magic
118 # end
119 #
094cd62 @josevalim Fix specs.
josevalim authored
120 # method_option :foo => :bar, :for => :previous_task
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
121 #
122 # def next_task
a52d778 @josevalim Put the initialization process that is common to scripts and generato…
josevalim authored
123 # # magic
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
124 # end
125 #
126 # ==== Parameters
127 # name<Symbol>:: The name of the argument.
f3cc428 @josevalim Options can be grouped. This allows a better output when several opti…
josevalim authored
128 # options<Hash>:: Described below.
129 #
130 # ==== Options
131 # :desc - Description for the argument.
132 # :required - If the argument is required or not.
133 # :default - Default value for this argument. It cannot be required and have default values.
134 # :aliases - Aliases for this option.
3664784 @josevalim Remove unecessary space on help.
josevalim authored
135 # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
ffdd72e @josevalim Add banner to options.
josevalim authored
136 # :banner - String to show on usage notes.
2c12f68 @helios add the hidded flag to option as well, to prevent display of this opt…
helios authored
137 # :hide - If you want to hide this option from the help.
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
138 #
364d6e3 @josevalim Added more specs to class method invocations.
josevalim authored
139 def method_option(name, options={})
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
140 scope = if options[:for]
141 find_and_refresh_task(options[:for]).options
142 else
143 method_options
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
144 end
145
206b464 @josevalim Renaming methods as discussed with Yehuda.
josevalim authored
146 build_option(name, options, scope)
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
147 end
148
b341095 @wycats Additional improvements:
wycats authored
149 alias option method_option
150
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
151 # Prints help information for the given task.
1754f44 @josevalim Improving #help coverage.
josevalim authored
152 #
153 # ==== Parameters
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
154 # shell<Thor::Shell>
155 # task_name<String>
156 #
157 def task_help(shell, task_name)
98b1bad Normalize task names when providing task help.
Brian Donovan authored
158 meth = normalize_task_name(task_name)
159 task = all_tasks[meth]
ac419fa @josevalim Unify error messages.
josevalim authored
160 handle_no_task_error(meth) unless task
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
161
162 shell.say "Usage:"
163 shell.say " #{banner(task)}"
164 shell.say
165 class_options_help(shell, nil => task.options.map { |_, o| o })
a83cb80 @indirect Word wrap and indent long_desc before printing, preserving paragraphs.
indirect authored
166 if task.long_description
167 shell.say "Description:"
4aa4b68 @wijet Fixed typo in word indent. Closes #154
wijet authored
168 shell.print_wrapped(task.long_description, :indent => 2)
a83cb80 @indirect Word wrap and indent long_desc before printing, preserving paragraphs.
indirect authored
169 else
170 shell.say task.description
171 end
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
172 end
9434c8f @josevalim Runner uses #help methods from classes.
josevalim authored
173
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
174 # Prints help information for this class.
175 #
176 # ==== Parameters
177 # shell<Thor::Shell>
178 #
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
179 def help(shell, subcommand = false)
180 list = printable_tasks(true, subcommand)
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
181 Thor::Util.thor_classes_in(self).each do |klass|
182 list += klass.printable_tasks(false)
4289017 @josevalim Refactoring on how tasks are shown.
josevalim authored
183 end
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
184 list.sort!{ |a,b| a[0] <=> b[0] }
185
186 shell.say "Tasks:"
4aa4b68 @wijet Fixed typo in word indent. Closes #154
wijet authored
187 shell.print_table(list, :indent => 2, :truncate => true)
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
188 shell.say
189 class_options_help(shell)
4289017 @josevalim Refactoring on how tasks are shown.
josevalim authored
190 end
dfb5a14 @josevalim Stub at rake compat layer.
josevalim authored
191
f32bdbf @josevalim More work on changing from rr to spec mock framework
josevalim authored
192 # Returns tasks ready to be printed.
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
193 def printable_tasks(all = true, subcommand = false)
4289017 @josevalim Refactoring on how tasks are shown.
josevalim authored
194 (all ? all_tasks : tasks).map do |_, task|
71dc13e @josevalim Allow hidden tasks.
josevalim authored
195 next if task.hidden?
4289017 @josevalim Refactoring on how tasks are shown.
josevalim authored
196 item = []
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
197 item << banner(task, false, subcommand)
7f892ff @josevalim Migration to rspec mock framework.
josevalim authored
198 item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
4289017 @josevalim Refactoring on how tasks are shown.
josevalim authored
199 item
71dc13e @josevalim Allow hidden tasks.
josevalim authored
200 end.compact
3acd153 @josevalim Moving task list responsabilities to one place.
josevalim authored
201 end
202
e98b523 @indirect Subcommands!
indirect authored
203 def subcommands
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
204 @subcommands ||= from_superclass(:subcommands, [])
e969b14 @smerritt Make check_unknown_options! and subcommands work together.
smerritt authored
205 end
206
e98b523 @indirect Subcommands!
indirect authored
207 def subcommand(subcommand, subcommand_class)
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
208 self.subcommands << subcommand.to_s
e98b523 @indirect Subcommands!
indirect authored
209 subcommand_class.subcommand_help subcommand
b341095 @wycats Additional improvements:
wycats authored
210
211 define_method(subcommand) do |*args|
212 args, opts = Thor::Arguments.split(args)
213 invoke subcommand_class, args, opts
214 end
e98b523 @indirect Subcommands!
indirect authored
215 end
216
9c00e55 @josevalim Add :only and :except to check_unknown_options.
josevalim authored
217 # Extend check unknown options to accept a hash of conditions.
218 #
219 # === Parameters
220 # options<Hash>: A hash containing :only and/or :except keys
221 def check_unknown_options!(options={})
222 @check_unknown_options ||= Hash.new
223 options.each do |key, value|
224 if value
225 @check_unknown_options[key] = Array(value)
226 else
227 @check_unknown_options.delete(key)
228 end
c3c5368 Fix whitespace
Carlhuda authored
229 end
9c00e55 @josevalim Add :only and :except to check_unknown_options.
josevalim authored
230 @check_unknown_options
231 end
232
233 # Overwrite check_unknown_options? to take subcommands and options into account.
234 def check_unknown_options?(config) #:nodoc:
235 options = check_unknown_options
236 return false unless options
237
238 task = config[:current_task]
239 return true unless task
240
241 name = task.name
242
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
243 if subcommands.include?(name)
9c00e55 @josevalim Add :only and :except to check_unknown_options.
josevalim authored
244 false
245 elsif options[:except]
246 !options[:except].include?(name.to_sym)
247 elsif options[:only]
248 options[:only].include?(name.to_sym)
249 else
250 true
251 end
252 end
253
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
254 protected
255
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
256 # The method responsible for dispatching given the args.
257 def dispatch(meth, given_args, given_opts, config) #:nodoc:
258 meth ||= retrieve_task_name(given_args)
259 task = all_tasks[normalize_task_name(meth)]
260
261 if task
262 args, opts = Thor::Options.split(given_args)
263 else
264 args, opts = given_args, nil
265 task = Thor::DynamicTask.new(meth)
266 end
267
268 opts = given_opts || opts || []
269 config.merge!(:current_task => task, :task_options => task.options)
270
0cfe4af @wycats Changes in semantics:
wycats authored
271 instance = new(args, opts, config)
b341095 @wycats Additional improvements:
wycats authored
272 yield instance if block_given?
0cfe4af @wycats Changes in semantics:
wycats authored
273 args = instance.args
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
274 trailing = args[Range.new(arguments.size, -1)]
0cfe4af @wycats Changes in semantics:
wycats authored
275 instance.invoke_task(task, trailing || [])
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
276 end
277
38e8f36 @josevalim Created a hook called banner to allow description customization for t…
josevalim authored
278 # The banner for this class. You can customize it if you are invoking the
a659385 @josevalim Added rdoc task and checked if a good documentation will be generated.
josevalim authored
279 # thor class by another ways which is not the Thor::Runner. It receives
280 # the task that is going to be invoked and a boolean which indicates if
281 # the namespace should be displayed as arguments.
38e8f36 @josevalim Created a hook called banner to allow description customization for t…
josevalim authored
282 #
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
283 def banner(task, namespace = nil, subcommand = false)
d50594b @postmodern Added Thor::Base.basename which is used by both Thor.banner and Thor:…
postmodern authored
284 "#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
38e8f36 @josevalim Created a hook called banner to allow description customization for t…
josevalim authored
285 end
286
46fb577 @josevalim Tidying up Thor and Thor::Generator classes.
josevalim authored
287 def baseclass #:nodoc:
288 Thor
289 end
290
291 def create_task(meth) #:nodoc:
51969f3 @josevalim Print a warning if a task is created without description or usage.
josevalim authored
292 if @usage && @desc
71dc13e @josevalim Allow hidden tasks.
josevalim authored
293 base_class = @hide ? Thor::HiddenTask : Thor::Task
9c00e55 @josevalim Add :only and :except to check_unknown_options.
josevalim authored
294 tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
71dc13e @josevalim Allow hidden tasks.
josevalim authored
295 @usage, @desc, @long_desc, @method_options, @hide = nil
51969f3 @josevalim Print a warning if a task is created without description or usage.
josevalim authored
296 true
9c00e55 @josevalim Add :only and :except to check_unknown_options.
josevalim authored
297 elsif self.all_tasks[meth] || meth == "method_missing"
51969f3 @josevalim Print a warning if a task is created without description or usage.
josevalim authored
298 true
299 else
300 puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
301 "Call desc if you want this method to be available as task or declare it inside a " <<
302 "no_tasks{} block. Invoked from #{caller[1].inspect}."
303 false
304 end
46fb577 @josevalim Tidying up Thor and Thor::Generator classes.
josevalim authored
305 end
306
307 def initialize_added #:nodoc:
308 class_options.merge!(method_options)
309 @method_options = nil
310 end
311
4d2e21b @josevalim Refactor invocations and make it simple.
josevalim authored
312 # Retrieve the task name from given args.
313 def retrieve_task_name(args) #:nodoc:
314 meth = args.first.to_s unless args.empty?
315 if meth && (map[meth] || meth !~ /^\-/)
316 args.shift
317 else
318 nil
319 end
320 end
321
a0e3e20 @sferik Automatically create aliases for unambiguous substrings of commands
sferik authored
322 # receives a (possibly nil) task name and returns a name that is in
323 # the tasks hash. In addition to normalizing aliases, this logic
9bb68dc @sferik Minor change of diction in a comment [ci skip]
sferik authored
324 # will determine if a shortened command is an unambiguous substring of
a0e3e20 @sferik Automatically create aliases for unambiguous substrings of commands
sferik authored
325 # a task or alias.
326 #
327 # +normalize_task_name+ also converts names like +animal-prison+
328 # into +animal_prison+.
46fb577 @josevalim Tidying up Thor and Thor::Generator classes.
josevalim authored
329 def normalize_task_name(meth) #:nodoc:
a0e3e20 @sferik Automatically create aliases for unambiguous substrings of commands
sferik authored
330 return default_task.to_s.gsub('-', '_') unless meth
331
332 possibilities = find_task_possibilities(meth)
333 if possibilities.size > 1
334 raise ArgumentError, "Ambiguous task #{meth} matches [#{possibilities.join(', ')}]"
335 elsif possibilities.size < 1
336 meth = meth || default_task
337 elsif map[meth]
338 meth = map[meth]
339 else
340 meth = possibilities.first
341 end
342
343 meth.to_s.gsub('-','_') # treat foo-bar as foo_bar
344 end
345
346 # this is the logic that takes the task name passed in by the user
9bb68dc @sferik Minor change of diction in a comment [ci skip]
sferik authored
347 # and determines whether it is an unambiguous substrings of a task or
a0e3e20 @sferik Automatically create aliases for unambiguous substrings of commands
sferik authored
348 # alias name.
349 def find_task_possibilities(meth)
3fa32a2 @sferik Make specs pass on Ruby 1.8
sferik authored
350 len = meth.to_s.length
a0e3e20 @sferik Automatically create aliases for unambiguous substrings of commands
sferik authored
351 possibilities = all_tasks.merge(map).keys.select { |n| meth == n[0, len] }.sort
352 unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
353
354 if possibilities.include?(meth)
355 [meth]
356 elsif unique_possibilities.size == 1
357 unique_possibilities
358 else
359 possibilities
360 end
6d8c59d @josevalim Started to move specific Script implementations to Thor and specific …
josevalim authored
361 end
e98b523 @indirect Subcommands!
indirect authored
362
363 def subcommand_help(cmd)
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
364 desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
e98b523 @indirect Subcommands!
indirect authored
365 class_eval <<-RUBY
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
366 def help(task = nil, subcommand = true); super; end
e98b523 @indirect Subcommands!
indirect authored
367 RUBY
368 end
46fb577 @josevalim Tidying up Thor and Thor::Generator classes.
josevalim authored
369 end
ad71b5b @josevalim More formatting on thor list.
josevalim authored
370
46fb577 @josevalim Tidying up Thor and Thor::Generator classes.
josevalim authored
371 include Thor::Base
372
bb35d61 @josevalim Arguments can be optional too (the main difference from options then …
josevalim authored
373 map HELP_MAPPINGS => :help
614326a @josevalim Moved Thor DSL to lib/thor/dsl
josevalim authored
374
3acd153 @josevalim Moving task list responsabilities to one place.
josevalim authored
375 desc "help [TASK]", "Describe available tasks or one specific task"
5bb798a @indirect Fix subcommand help to display subcommands instead of tasks
indirect authored
376 def help(task = nil, subcommand = false)
377 task ? self.class.task_help(shell, task) : self.class.help(shell, subcommand)
b522f05 @wycats First pass of Thor::Runner
wycats authored
378 end
5dbab4e @nex3 Preserve metadata for tasks defined in superclasses.
nex3 authored
379 end
Something went wrong with that request. Please try again.