thor 0.9.9 → 0.11.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG.rdoc +29 -4
  2. data/README.rdoc +234 -0
  3. data/Thorfile +57 -0
  4. data/VERSION +1 -0
  5. data/bin/rake2thor +4 -0
  6. data/bin/thor +1 -1
  7. data/lib/thor.rb +216 -119
  8. data/lib/thor/actions.rb +272 -0
  9. data/lib/thor/actions/create_file.rb +102 -0
  10. data/lib/thor/actions/directory.rb +87 -0
  11. data/lib/thor/actions/empty_directory.rb +133 -0
  12. data/lib/thor/actions/file_manipulation.rb +195 -0
  13. data/lib/thor/actions/inject_into_file.rb +78 -0
  14. data/lib/thor/base.rb +510 -0
  15. data/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  16. data/lib/thor/core_ext/ordered_hash.rb +100 -0
  17. data/lib/thor/error.rb +25 -1
  18. data/lib/thor/group.rb +263 -0
  19. data/lib/thor/invocation.rb +178 -0
  20. data/lib/thor/parser.rb +4 -0
  21. data/lib/thor/parser/argument.rb +67 -0
  22. data/lib/thor/parser/arguments.rb +145 -0
  23. data/lib/thor/parser/option.rb +132 -0
  24. data/lib/thor/parser/options.rb +142 -0
  25. data/lib/thor/rake_compat.rb +67 -0
  26. data/lib/thor/runner.rb +232 -242
  27. data/lib/thor/shell.rb +72 -0
  28. data/lib/thor/shell/basic.rb +220 -0
  29. data/lib/thor/shell/color.rb +108 -0
  30. data/lib/thor/task.rb +97 -60
  31. data/lib/thor/util.rb +230 -55
  32. data/spec/actions/create_file_spec.rb +170 -0
  33. data/spec/actions/directory_spec.rb +118 -0
  34. data/spec/actions/empty_directory_spec.rb +91 -0
  35. data/spec/actions/file_manipulation_spec.rb +242 -0
  36. data/spec/actions/inject_into_file_spec.rb +80 -0
  37. data/spec/actions_spec.rb +291 -0
  38. data/spec/base_spec.rb +236 -0
  39. data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
  40. data/spec/core_ext/ordered_hash_spec.rb +115 -0
  41. data/spec/fixtures/bundle/execute.rb +6 -0
  42. data/spec/fixtures/doc/config.rb +1 -0
  43. data/spec/group_spec.rb +177 -0
  44. data/spec/invocation_spec.rb +107 -0
  45. data/spec/parser/argument_spec.rb +47 -0
  46. data/spec/parser/arguments_spec.rb +64 -0
  47. data/spec/parser/option_spec.rb +212 -0
  48. data/spec/parser/options_spec.rb +255 -0
  49. data/spec/rake_compat_spec.rb +64 -0
  50. data/spec/runner_spec.rb +204 -0
  51. data/spec/shell/basic_spec.rb +206 -0
  52. data/spec/shell/color_spec.rb +41 -0
  53. data/spec/shell_spec.rb +25 -0
  54. data/spec/spec_helper.rb +52 -0
  55. data/spec/task_spec.rb +82 -0
  56. data/spec/thor_spec.rb +234 -0
  57. data/spec/util_spec.rb +196 -0
  58. metadata +69 -25
  59. data/README.markdown +0 -76
  60. data/Rakefile +0 -6
  61. data/lib/thor/options.rb +0 -242
  62. data/lib/thor/ordered_hash.rb +0 -64
  63. data/lib/thor/task_hash.rb +0 -22
  64. data/lib/thor/tasks.rb +0 -77
  65. data/lib/thor/tasks/package.rb +0 -18
data/CHANGELOG.rdoc CHANGED
@@ -1,9 +1,34 @@
1
1
  == TODO
2
2
 
3
- * Change Thor.start to parse ARGV in a single pass
4
3
  * Improve spec coverage for Thor::Runner
5
- * Improve help output to list shorthand switches, too
6
- * Investigate and fix deep namespacing ("foo:bar:baz") issues
4
+
5
+ == 0.11.x, released 2009-07-01
6
+
7
+ * Added a rake compatibility layer. It allows you to use spec and rdoc tasks on
8
+ Thor classes.
9
+
10
+ * BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
11
+ since it wrong behavior to the invocation system.
12
+
13
+ * thor help now show information about any class/task. All those calls are
14
+ possible:
15
+
16
+ thor help describe
17
+ thor help describe:amazing
18
+
19
+ Or even with default namespaces:
20
+
21
+ thor help :spec
22
+
23
+ * Thor::Runner now invokes the default task if none is supplied:
24
+
25
+ thor describe # invokes the default task, usually help
26
+
27
+ * Thor::Runner now works with mappings:
28
+
29
+ thor describe -h
30
+
31
+ * Added some documentation and code refactoring.
7
32
 
8
33
  == 0.9.8, released 2008-10-20
9
34
 
@@ -49,4 +74,4 @@
49
74
  * Thor::Options now doesn't parse through things that look like options but aren't.
50
75
  * Add URI detection to install task, and make sure we don't append ".thor" to URIs
51
76
  * Add rake2thor to the gem binfiles.
52
- * Make sure local Thorfiles override system-wide ones.
77
+ * Make sure local Thorfiles override system-wide ones.
data/README.rdoc ADDED
@@ -0,0 +1,234 @@
1
+ = thor
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.
5
+
6
+ Example:
7
+
8
+ class App < Thor # [1]
9
+ map "-L" => :list # [2]
10
+
11
+ desc "install APP_NAME", "install one of the available apps" # [3]
12
+ method_options :force => :boolean, :alias => :string # [4]
13
+ def install(name)
14
+ user_alias = options[:alias]
15
+ if options.force?
16
+ # do something
17
+ end
18
+ # other code
19
+ end
20
+
21
+ desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
22
+ def list(search="")
23
+ # list everything
24
+ end
25
+ end
26
+
27
+ Thor automatically maps commands as such:
28
+
29
+ thor app:install myname --force
30
+
31
+ That gets converted to:
32
+
33
+ App.new.install("myname")
34
+ # with {'force' => true} as options hash
35
+
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.
40
+
41
+ == Types for <tt>method_options</tt>
42
+
43
+ * :boolean - is parsed as <tt>--option</tt> or <tt>--option=true</tt>
44
+ * :string - is parsed as <tt>--option=VALUE</tt>
45
+ * :numeric - is parsed as <tt>--option=N</tt>
46
+ * :array - is parsed as <tt>--option=one two three</tt>
47
+ * :hash - is parsed as <tt>--option=name:string age:integer</tt>
48
+
49
+ Besides, method_option allows a default value to be given, examples:
50
+
51
+ method_options :force => false
52
+ #=> Creates a boolean option with default value false
53
+
54
+ method_options :alias => "bar"
55
+ #=> Creates a string option with default value "bar"
56
+
57
+ method_options :threshold => 3.0
58
+ #=> Creates a numeric option with default value 3.0
59
+
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
62
+ as option, you can use <tt>method_option</tt> which uses a more declarative style:
63
+
64
+ method_option :attributes, :type => :hash, :default => {}, :required => true
65
+
66
+ All arguments can be set to nil (except required arguments), by suppling a no or
67
+ skip variant. For example:
68
+
69
+ thor app name --no-attributes
70
+
71
+ 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
73
+ styles:
74
+
75
+ method_options %w( force -f ) => :boolean
76
+
77
+ Or:
78
+
79
+ method_option :force, :type => :boolean, :aliases => "-f"
80
+
81
+ You can supply as many aliases as you want.
82
+
83
+ NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead.
84
+
85
+ == Namespaces
86
+
87
+ By default, your Thor tasks are invoked using Ruby namespace. In the example
88
+ above, tasks are invoked as:
89
+
90
+ thor app:install name --force
91
+
92
+ However, you could namespace your class as:
93
+
94
+ module Sinatra
95
+ class App < Thor
96
+ # tasks
97
+ end
98
+ end
99
+
100
+ And then you should invoke your tasks as:
101
+
102
+ thor sinatra:app:install name --force
103
+
104
+ If desired, you can change the namespace:
105
+
106
+ module Sinatra
107
+ class App < Thor
108
+ namespace :myapp
109
+ # tasks
110
+ end
111
+ end
112
+
113
+ And then your tasks hould be invoked as:
114
+
115
+ thor myapp:install name --force
116
+
117
+ == Invocations
118
+
119
+ Thor comes with a invocation-dependency system as well which allows a task to be
120
+ invoked only once. For example:
121
+
122
+ class Counter < Thor
123
+ desc "one", "Prints 1, 2, 3"
124
+ def one
125
+ puts 1
126
+ invoke :two
127
+ invoke :three
128
+ end
129
+
130
+ desc "two", "Prints 2, 3"
131
+ def two
132
+ puts 2
133
+ invoke :three
134
+ end
135
+
136
+ desc "three", "Prints 3"
137
+ def three
138
+ puts 3
139
+ end
140
+ end
141
+
142
+ When invoking the task one:
143
+
144
+ thor counter:one
145
+
146
+ The output is "1 2 3", which means that the three task was invoked only once.
147
+ You can even invoke tasks from another class, so be sure to check the
148
+ documentation.
149
+
150
+ == Thor::Group
151
+
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
154
+ Thor::Group as this:
155
+
156
+ class Counter < Thor::Group
157
+ desc "Prints 1, 2, 3"
158
+
159
+ def one
160
+ puts 1
161
+ end
162
+
163
+ def two
164
+ puts 2
165
+ end
166
+
167
+ def three
168
+ puts 3
169
+ end
170
+ end
171
+
172
+ When invoked:
173
+
174
+ thor counter
175
+
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
178
+ 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).
180
+
181
+ Besides, Thor::Group can parse arguments and options as Thor tasks:
182
+
183
+ class Counter < Thor::Group
184
+ # number will be available as attr_accessor
185
+ argument :number, :type => :numeric, :desc => "The number to start counting"
186
+ desc "Prints the 'number' given upto 'number+2'"
187
+
188
+ def one
189
+ puts number + 0
190
+ end
191
+
192
+ def two
193
+ puts number + 1
194
+ end
195
+
196
+ def three
197
+ puts number + 2
198
+ end
199
+ end
200
+
201
+ The counter above expects one parameter and has the folling outputs:
202
+
203
+ thor counter 5
204
+ # Prints "5 6 7"
205
+
206
+ thor counter 11
207
+ # Prints "11 12 13"
208
+
209
+ You can also give options to Thor::Group, but instead of using <tt>method_option</tt>
210
+ and <tt>method_options</tt>, you should use <tt>class_option</tt> and <tt>class_options</tt>.
211
+ Both argument and class_options methods are available to Thor class as well.
212
+
213
+ == Actions
214
+
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:
217
+ <tt>say</tt>, <tt>ask</tt>, <tt>yes?</tt>, <tt>no?</tt>, <tt>add_file</tt>,
218
+ <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.
220
+
221
+ To use them, you just need to include Thor::Actions in your Thor classes:
222
+
223
+ class App < Thor
224
+ include Thor::Actions
225
+ # tasks
226
+ end
227
+
228
+ 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.
231
+
232
+ == License
233
+
234
+ See MIT LICENSE.
data/Thorfile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'thor/rake_compat'
3
+ require 'spec/rake/spectask'
4
+ require 'rdoc/task'
5
+
6
+ GEM_NAME = 'thor'
7
+ EXTRA_RDOC_FILES = ["README.rdoc", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"]
8
+
9
+ class Default < Thor
10
+ include Thor::RakeCompat
11
+
12
+ Spec::Rake::SpecTask.new(:spec) do |t|
13
+ t.spec_opts = ['--options', "spec/spec.opts"]
14
+ t.spec_files = FileList['spec/**/*_spec.rb']
15
+ end
16
+
17
+ Spec::Rake::SpecTask.new(:rcov) do |t|
18
+ t.spec_opts = ['--options', "spec/spec.opts"]
19
+ t.spec_files = FileList['spec/**/*_spec.rb']
20
+ t.rcov = true
21
+ t.rcov_dir = "rcov"
22
+ end
23
+
24
+ RDoc::Task.new do |rdoc|
25
+ rdoc.main = "README.rdoc"
26
+ rdoc.rdoc_dir = "rdoc"
27
+ rdoc.title = GEM_NAME
28
+ rdoc.rdoc_files.include(*EXTRA_RDOC_FILES)
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ rdoc.options << '--line-numbers' << '--inline-source'
31
+ end
32
+
33
+ begin
34
+ require 'jeweler'
35
+ Jeweler::Tasks.new do |s|
36
+ s.name = GEM_NAME
37
+ s.version = "0.11.4"
38
+ s.rubyforge_project = "textmate"
39
+ s.platform = Gem::Platform::RUBY
40
+ s.summary = "A scripting framework that replaces rake, sake and rubigen"
41
+ s.email = "ruby-thor@googlegroups.com"
42
+ s.homepage = "http://yehudakatz.com"
43
+ s.description = "A scripting framework that replaces rake, sake and rubigen"
44
+ s.authors = ['Yehuda Katz', 'José Valim']
45
+ s.has_rdoc = true
46
+ s.extra_rdoc_files = EXTRA_RDOC_FILES
47
+ s.require_path = 'lib'
48
+ s.bindir = "bin"
49
+ s.executables = %w( thor rake2thor )
50
+ s.files = s.extra_rdoc_files + Dir.glob("{bin,lib}/**/*")
51
+ s.files.exclude 'spec/sandbox/**/*'
52
+ s.test_files.exclude 'spec/sandbox/**/*'
53
+ end
54
+ rescue LoadError
55
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
56
+ end
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.11.5
data/bin/rake2thor CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'ruby2ruby'
5
+ require 'parse_tree'
6
+ if Ruby2Ruby::VERSION >= "1.2.0"
7
+ require 'parse_tree_extensions'
8
+ end
5
9
  require 'rake'
6
10
 
7
11
  input = ARGV[0] || 'Rakefile'
data/bin/thor CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- mode: ruby -*-
3
3
 
4
- require File.dirname(__FILE__) + "/../lib/thor"
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thor')
5
5
  require 'thor/runner'
6
6
 
7
7
  Thor::Runner.start
data/lib/thor.rb CHANGED
@@ -1,146 +1,243 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__))
2
- require "thor/options"
3
- require "thor/util"
4
- require "thor/task"
5
- require "thor/task_hash"
2
+ require 'thor/base'
3
+ require 'thor/group'
4
+ require 'thor/actions'
6
5
 
7
6
  class Thor
8
- attr_accessor :options
9
-
10
- def self.map(map)
11
- @map ||= superclass.instance_variable_get("@map") || {}
12
- map.each do |key, value|
13
- if key.respond_to?(:each)
14
- key.each {|subkey| @map[subkey] = value}
7
+ class << self
8
+ # Sets the default task when thor is executed without an explicit task to be called.
9
+ #
10
+ # ==== Parameters
11
+ # meth<Symbol>:: name of the defaut task
12
+ #
13
+ def default_task(meth=nil)
14
+ case meth
15
+ when :none
16
+ @default_task = 'help'
17
+ when nil
18
+ @default_task ||= from_superclass(:default_task, 'help')
19
+ else
20
+ @default_task = meth.to_s
21
+ end
22
+ end
23
+
24
+ # Defines the usage and the description of the next task.
25
+ #
26
+ # ==== Parameters
27
+ # usage<String>
28
+ # description<String>
29
+ #
30
+ def desc(usage, description, options={})
31
+ if options[:for]
32
+ task = find_and_refresh_task(options[:for])
33
+ task.usage = usage if usage
34
+ task.description = description if description
15
35
  else
16
- @map[key] = value
36
+ @usage, @desc = usage, description
17
37
  end
18
38
  end
19
- end
20
39
 
21
- def self.desc(usage, description)
22
- @usage, @desc = usage, description
23
- end
24
-
25
- def self.group(name)
26
- @group_name = name.to_s
27
- end
28
-
29
- def self.group_name
30
- @group_name || 'standard'
31
- end
32
-
33
- def self.method_options(opts)
34
- @method_options = opts
35
- end
40
+ # Maps an input to a task. If you define:
41
+ #
42
+ # map "-T" => "list"
43
+ #
44
+ # Running:
45
+ #
46
+ # thor -T
47
+ #
48
+ # Will invoke the list task.
49
+ #
50
+ # ==== Parameters
51
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
52
+ #
53
+ def map(mappings=nil)
54
+ @map ||= from_superclass(:map, {})
55
+
56
+ if mappings
57
+ mappings.each do |key, value|
58
+ if key.respond_to?(:each)
59
+ key.each {|subkey| @map[subkey] = value}
60
+ else
61
+ @map[key] = value
62
+ end
63
+ end
64
+ end
36
65
 
37
- def self.subclass_files
38
- @subclass_files ||= Hash.new {|h,k| h[k] = []}
39
- end
40
-
41
- def self.subclasses
42
- @subclasses ||= []
43
- end
44
-
45
- def self.tasks
46
- @tasks ||= TaskHash.new(self)
47
- end
66
+ @map
67
+ end
48
68
 
49
- def self.opts
50
- (@opts || {}).merge(self == Thor ? {} : superclass.opts)
51
- end
69
+ # Declares the options for the next task to be declared.
70
+ #
71
+ # ==== Parameters
72
+ # Hash[Symbol => Object]:: The hash key is the name of the option and the value
73
+ # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
74
+ # or :required (string). If you give a value, the type of the value is used.
75
+ #
76
+ def method_options(options=nil)
77
+ @method_options ||= {}
78
+ build_options(options, @method_options) if options
79
+ @method_options
80
+ end
52
81
 
53
- def self.[](task)
54
- namespaces = task.split(":")
55
- klass = Thor::Util.constant_from_thor_path(namespaces[0...-1].join(":"))
56
- raise Error, "`#{klass}' is not a Thor class" unless klass <= Thor
57
- klass.tasks[namespaces.last]
58
- end
82
+ # Adds an option to the set of class options. If :for is given as option,
83
+ # it allows you to change the options from a previous defined task.
84
+ #
85
+ # def previous_task
86
+ # # magic
87
+ # end
88
+ #
89
+ # method_options :foo => :bar, :for => :previous_task
90
+ #
91
+ # def next_task
92
+ # # magic
93
+ # end
94
+ #
95
+ # ==== Parameters
96
+ # name<Symbol>:: The name of the argument.
97
+ # options<Hash>:: Described below.
98
+ #
99
+ # ==== Options
100
+ # :desc - Description for the argument.
101
+ # :required - If the argument is required or not.
102
+ # :default - Default value for this argument. It cannot be required and have default values.
103
+ # :aliases - Aliases for this option.
104
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
105
+ # :group - The group for this options. Use by class options to output options in different levels.
106
+ # :banner - String to show on usage notes.
107
+ #
108
+ def method_option(name, options={})
109
+ scope = if options[:for]
110
+ find_and_refresh_task(options[:for]).options
111
+ else
112
+ method_options
113
+ end
59
114
 
60
- def self.maxima
61
- @maxima ||= begin
62
- max_usage = tasks.map {|_, t| t.usage}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
63
- max_desc = tasks.map {|_, t| t.description}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
64
- max_opts = tasks.map {|_, t| t.opts ? t.opts.formatted_usage : ""}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
65
- Struct.new(:description, :usage, :opt).new(max_desc, max_usage, max_opts)
115
+ build_option(name, options, scope)
66
116
  end
67
- end
68
-
69
- def self.start(args = ARGV)
70
- options = Thor::Options.new(self.opts)
71
- opts = options.parse(args, false)
72
- args = options.trailing_non_opts
73
-
74
- meth = args.first
75
- meth = @map[meth].to_s if @map && @map[meth]
76
- meth ||= "help"
77
-
78
- tasks[meth].parse new(opts, *args), args[1..-1]
79
- rescue Thor::Error => e
80
- $stderr.puts e.message
81
- end
82
117
 
83
- class << self
84
- protected
85
- def inherited(klass)
86
- register_klass_file klass
118
+ # Parses the task and options from the given args, instantiate the class
119
+ # and invoke the task. This method is used when the arguments must be parsed
120
+ # from an array. If you are inside Ruby and want to use a Thor class, you
121
+ # can simply initialize it:
122
+ #
123
+ # script = MyScript.new(args, options, config)
124
+ # script.invoke(:task, first_arg, second_arg, third_arg)
125
+ #
126
+ def start(given_args=ARGV, config={})
127
+ super do
128
+ meth = normalize_task_name(given_args.shift)
129
+ task = all_tasks[meth]
130
+
131
+ if task
132
+ args, opts = Thor::Options.split(given_args)
133
+ config.merge!(:task_options => task.options)
134
+ else
135
+ args, opts = given_args, {}
136
+ end
137
+
138
+ task ||= Task.dynamic(meth)
139
+ trailing = args[Range.new(arguments.size, -1)]
140
+ new(args, opts, config).invoke(task, trailing || [])
141
+ end
87
142
  end
88
143
 
89
- def method_added(meth)
90
- meth = meth.to_s
91
-
92
- if meth == "initialize"
93
- @opts = @method_options
94
- @method_options = nil
95
- return
144
+ # Prints help information. If a task name is given, it shows information
145
+ # only about the specific task.
146
+ #
147
+ # ==== Parameters
148
+ # meth<String>:: An optional task name to print usage information about.
149
+ #
150
+ # ==== Options
151
+ # namespace:: When true, shows the namespace in the output before the usage.
152
+ # skip_inherited:: When true, does not show tasks from superclass.
153
+ #
154
+ def help(shell, meth=nil, options={})
155
+ meth, options = nil, meth if meth.is_a?(Hash)
156
+
157
+ if meth
158
+ task = all_tasks[meth]
159
+ raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
160
+
161
+ shell.say "Usage:"
162
+ shell.say " #{banner(task, options[:namespace], false)}"
163
+ shell.say
164
+ class_options_help(shell, "Class", :Method => task.options.map { |_, o| o })
165
+ shell.say task.description
166
+ else
167
+ list = (options[:short] ? tasks : all_tasks).map do |_, task|
168
+ item = [ banner(task, options[:namespace]) ]
169
+ item << "# #{task.short_description}" if task.short_description
170
+ item << " "
171
+ end
172
+
173
+ options[:ident] ||= 2
174
+ if options[:short]
175
+ shell.print_list(list, :ident => options[:ident])
176
+ else
177
+ shell.say "Tasks:"
178
+ shell.print_list(list, :ident => options[:ident])
179
+ end
180
+
181
+ Thor::Util.thor_classes_in(self).each do |subclass|
182
+ namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '')
183
+ subclass.help(shell, options.merge(:short => true, :namespace => namespace))
184
+ end
185
+
186
+ class_options_help(shell, "Class") unless options[:short]
96
187
  end
188
+ end
97
189
 
98
- return if !public_instance_methods.include?(meth) || !@usage
99
- register_klass_file self
100
-
101
- tasks[meth] = Task.new(meth, @desc, @usage, @method_options)
190
+ protected
102
191
 
103
- @usage, @desc, @method_options = nil
104
- end
192
+ # The banner for this class. You can customize it if you are invoking the
193
+ # thor class by another ways which is not the Thor::Runner. It receives
194
+ # the task that is going to be invoked and a boolean which indicates if
195
+ # the namespace should be displayed as arguments.
196
+ #
197
+ def banner(task, namespace=true, show_options=true)
198
+ task.formatted_usage(self, namespace, show_options)
199
+ end
105
200
 
106
- def register_klass_file(klass, file = caller[1].split(":")[0])
107
- unless self == Thor
108
- superclass.register_klass_file(klass, file)
109
- return
201
+ def baseclass #:nodoc:
202
+ Thor
110
203
  end
111
204
 
112
- file_subclasses = subclass_files[File.expand_path(file)]
113
- file_subclasses << klass unless file_subclasses.include?(klass)
114
- subclasses << klass unless subclasses.include?(klass)
115
- end
116
- end
205
+ def create_task(meth) #:nodoc:
206
+ if @usage && @desc
207
+ tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
208
+ @usage, @desc, @method_options = nil
209
+ true
210
+ elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
211
+ true
212
+ else
213
+ puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
214
+ "Call desc if you want this method to be available as task or declare it inside a " <<
215
+ "no_tasks{} block. Invoked from #{caller[1].inspect}."
216
+ false
217
+ end
218
+ end
117
219
 
118
- def initialize(opts = {}, *args)
119
- end
120
-
121
- map ["-h", "-?", "--help", "-D"] => :help
122
-
123
- desc "help [TASK]", "describe available tasks or one specific task"
124
- def help(task = nil)
125
- if task
126
- if task.include? ?:
127
- task = self.class[task]
128
- namespace = true
129
- else
130
- task = self.class.tasks[task]
220
+ def initialize_added #:nodoc:
221
+ class_options.merge!(method_options)
222
+ @method_options = nil
131
223
  end
132
224
 
133
- puts task.formatted_usage(namespace)
134
- puts task.description
135
- else
136
- puts "Options"
137
- puts "-------"
138
- self.class.tasks.each do |_, task|
139
- format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s"
140
- print format % ("#{task.formatted_usage}")
141
- puts task.description.split("\n").first
225
+ # Receives a task name (can be nil), and try to get a map from it.
226
+ # If a map can't be found use the sent name or the default task.
227
+ #
228
+ def normalize_task_name(meth) #:nodoc:
229
+ mapping = map[meth.to_s]
230
+ meth = mapping || meth || default_task
231
+ meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
142
232
  end
143
- end
144
233
  end
145
-
234
+
235
+ include Thor::Base
236
+
237
+ map HELP_MAPPINGS => :help
238
+
239
+ desc "help [TASK]", "Describe available tasks or one specific task"
240
+ def help(task=nil)
241
+ self.class.help(shell, task, :namespace => task && task.include?(?:))
242
+ end
146
243
  end