josevalim-thor 0.10.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 ADDED
@@ -0,0 +1,73 @@
1
+ == TODO
2
+
3
+ * Improve spec coverage for Thor::Runner
4
+ * Improve help output to list shorthand switches, too
5
+ * Investigate and fix deep namespacing ("foo:bar:baz") issues
6
+
7
+ == Current
8
+
9
+ * thor help now show information about any class/task. All those calls are
10
+ possible:
11
+
12
+ thor help describe
13
+ thor help describe:amazing
14
+
15
+ Or even with default namespaces:
16
+
17
+ thor help :spec
18
+
19
+ * Thor::Runner now invokes the default task if none is supplied:
20
+
21
+ thor describe # invokes the default task, usually help
22
+
23
+ * Thor::Runner now works with mappings:
24
+
25
+ thor describe -h
26
+
27
+ * Added some documentation and code refactoring.
28
+
29
+ == 0.9.8, released 2008-10-20
30
+
31
+ * Fixed some tiny issues that were introduced lately.
32
+
33
+ == 0.9.7, released 2008-10-13
34
+
35
+ * Setting global method options on the initialize method works as expected:
36
+ All other tasks will accept these global options in addition to their own.
37
+ * Added 'group' notion to Thor task sets (class Thor); by default all tasks
38
+ are in the 'standard' group. Running 'thor -T' will only show the standard
39
+ tasks - adding --all will show all tasks. You can also filter on a specific
40
+ group using the --group option: thor -T --group advanced
41
+
42
+ == 0.9.6, released 2008-09-13
43
+
44
+ * Generic improvements
45
+
46
+ == 0.9.5, released 2008-08-27
47
+
48
+ * Improve Windows compatibility
49
+ * Update (incorrect) README and task.thor sample file
50
+ * Options hash is now frozen (once returned)
51
+ * Allow magic predicates on options object. For instance: `options.force?`
52
+ * Add support for :numeric type
53
+ * BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
54
+ * Allow specifying optional args with default values: method_options(:user => "mislav")
55
+ * Don't write options for nil or false values. This allows, for example, turning color off when running specs.
56
+ * Exit with the status of the spec command to help CI stuff out some.
57
+
58
+ == 0.9.4, released 2008-08-13
59
+
60
+ * Try to add Windows compatibility.
61
+ * BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
62
+ * Allow options at the beginning of the argument list as well as the end.
63
+ * Make options available with symbol keys in addition to string keys.
64
+ * Allow true to be passed to Thor#method_options to denote a boolean option.
65
+ * If loading a thor file fails, don't give up, just print a warning and keep going.
66
+ * Make sure that we re-raise errors if they happened further down the pipe than we care about.
67
+ * Only delete the old file on updating when the installation of the new one is a success
68
+ * Make it Ruby 1.8.5 compatible.
69
+ * Don't raise an error if a boolean switch is defined multiple times.
70
+ * Thor::Options now doesn't parse through things that look like options but aren't.
71
+ * Add URI detection to install task, and make sure we don't append ".thor" to URIs
72
+ * Add rake2thor to the gem binfiles.
73
+ * Make sure local Thorfiles override system-wide ones.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Yehuda Katz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,76 @@
1
+ thor
2
+ ====
3
+
4
+ Map options to a class. Simply create a class with the appropriate annotations, and have options automatically map
5
+ to functions and parameters.
6
+
7
+ Example:
8
+
9
+ class MyApp < Thor # [1]
10
+ map "-L" => :list # [2]
11
+
12
+ desc "install APP_NAME", "install one of the available apps" # [3]
13
+ method_options :force => :boolean, :alias => :optional # [4]
14
+ def install(name)
15
+ user_alias = options[:alias]
16
+ if options.force?
17
+ # do something
18
+ end
19
+ # ... other code ...
20
+ end
21
+
22
+ desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
23
+ def list(search = "")
24
+ # list everything
25
+ end
26
+ end
27
+
28
+ Thor automatically maps commands as such:
29
+
30
+ app install myname --force
31
+
32
+ That gets converted to:
33
+
34
+ MyApp.new.install("myname")
35
+ # with {'force' => true} as options hash
36
+
37
+ 1. Inherit from Thor to turn a class into an option mapper
38
+ 2. Map additional non-valid identifiers to specific methods. In this case,
39
+ convert -L to :list
40
+ 3. Describe the method immediately below. The first parameter is the usage information,
41
+ and the second parameter is the description.
42
+ 4. Provide any additional options. These will be marshaled from `--` and `-` params.
43
+ In this case, a `--force` and a `-f` option is added.
44
+
45
+ Types for `method_options`
46
+ --------------------------
47
+
48
+ <dl>
49
+ <dt><code>:boolean</code></dt>
50
+ <dd>true if the option is passed</dd>
51
+ <dt><code>true or false</code></dt>
52
+ <dd>same as <code>:boolean</code>, but fall back to given boolean as default value</dd>
53
+ <dt><code>:required</code></dt>
54
+ <dd>the value for this option MUST be provided</dd>
55
+ <dt><code>:optional</code></dt>
56
+ <dd>the value for this option MAY be provided</dd>
57
+ <dt><code>:numeric</code></dt>
58
+ <dd>the value MAY be provided, but MUST be in numeric form</dd>
59
+ <dt>a String or Numeric</dt>
60
+ <dd>same as <code>:optional</code>, but fall back to the given object as default value</dd>
61
+ </dl>
62
+
63
+ In case of unsatisfied requirements, `Thor::Options::Error` is raised.
64
+
65
+ Examples of option parsing:
66
+
67
+ # let's say this is how we defined options for a method:
68
+ method_options(:force => :boolean, :retries => :numeric)
69
+
70
+ # here is how the following command-line invocations would be parsed:
71
+
72
+ command -f --retries 5 # => {'force' => true, 'retries' => 5}
73
+ command --force -r=5 # => {'force' => true, 'retries' => 5}
74
+ command -fr 5 # => {'force' => true, 'retries' => 5}
75
+ command --retries=5 # => {'retries' => 5}
76
+ command -r5 # => {'retries' => 5}
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ task :default => :install
2
+
3
+ desc "install the gem locally"
4
+ task :install do
5
+ sh %{ruby "#{File.dirname(__FILE__)}/bin/thor" :install}
6
+ end
data/bin/rake2thor ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'ruby2ruby'
5
+ require 'parse_tree'
6
+ if Ruby2Ruby::VERSION >= "1.2.0"
7
+ require 'parse_tree_extensions'
8
+ end
9
+ require 'rake'
10
+
11
+ input = ARGV[0] || 'Rakefile'
12
+ output = ARGV[1] || 'Thorfile'
13
+
14
+ $requires = []
15
+
16
+ module Kernel
17
+ def require_with_record(file)
18
+ $requires << file if caller[1] =~ /rake2thor:/
19
+ require_without_record file
20
+ end
21
+ alias_method :require_without_record, :require
22
+ alias_method :require, :require_with_record
23
+ end
24
+
25
+ load input
26
+
27
+ @private_methods = []
28
+
29
+ def file_task_name(name)
30
+ "compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_')
31
+ end
32
+
33
+ def method_for_task(task)
34
+ file_task = task.is_a?(Rake::FileTask)
35
+ comment = task.instance_variable_get('@comment')
36
+ prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?))
37
+ actions = task.instance_variable_get('@actions')
38
+ name = task.name.gsub(/^([^:]+:)+/, '')
39
+ name = file_task_name(name) if file_task
40
+ meth = ''
41
+
42
+ meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment
43
+ meth << "def #{name}\n"
44
+
45
+ meth << prereqs.map do |pre|
46
+ pre = pre.to_s
47
+ pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask)
48
+ ' ' + pre
49
+ end.join("\n")
50
+
51
+ meth << "\n\n" unless prereqs.empty? || actions.empty?
52
+
53
+ meth << actions.map do |act|
54
+ act = act.to_ruby
55
+ unless act.gsub!(/^proc \{ \|(\w+)\|\n/,
56
+ " \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n")
57
+ act.gsub!(/^proc \{\n/, '')
58
+ end
59
+ act.gsub(/\n\}$/, '')
60
+ end.join("\n")
61
+
62
+ meth << "\nend"
63
+
64
+ if file_task
65
+ @private_methods << meth
66
+ return
67
+ end
68
+
69
+ meth
70
+ end
71
+
72
+ body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n")
73
+
74
+ unless @private_methods.empty?
75
+ body << "\n\n private\n\n"
76
+ body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n")
77
+ end
78
+
79
+ requires = $requires.map { |r| "require #{r.inspect}" }.join("\n")
80
+
81
+ File.open(output, 'w') { |f| f.write(<<END.lstrip) }
82
+ #{requires}
83
+
84
+ class Default < Thor
85
+ #{body}
86
+ end
87
+ END
data/bin/thor ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'thor')
5
+ require 'thor/runner'
6
+
7
+ Thor::Runner.start
data/lib/thor.rb ADDED
@@ -0,0 +1,229 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+ require 'thor/base'
3
+ require 'thor/group'
4
+ require 'thor/actions'
5
+
6
+ class Thor
7
+
8
+ class << self
9
+
10
+ # Sets the default task when thor is executed without an explicit task to be called.
11
+ #
12
+ # ==== Parameters
13
+ # meth<Symbol>:: name of the defaut task
14
+ #
15
+ def default_task(meth=nil)
16
+ case meth
17
+ when :none
18
+ @default_task = 'help'
19
+ when nil
20
+ @default_task ||= from_superclass(:default_task, 'help')
21
+ else
22
+ @default_task = meth.to_s
23
+ end
24
+ end
25
+
26
+ # Defines the usage and the description of the next task.
27
+ #
28
+ # ==== Parameters
29
+ # usage<String>
30
+ # description<String>
31
+ #
32
+ def desc(usage, description, options={})
33
+ if options[:for]
34
+ task = find_and_refresh_task(options[:for])
35
+ task.usage = usage if usage
36
+ task.description = description if description
37
+ else
38
+ @usage, @desc = usage, description
39
+ end
40
+ end
41
+
42
+ # Maps an input to a task. If you define:
43
+ #
44
+ # map "-T" => "list"
45
+ #
46
+ # Running:
47
+ #
48
+ # thor -T
49
+ #
50
+ # Will invoke the list task.
51
+ #
52
+ # ==== Parameters
53
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
54
+ #
55
+ def map(mappings=nil)
56
+ @map ||= from_superclass(:map, {})
57
+
58
+ if mappings
59
+ mappings.each do |key, value|
60
+ if key.respond_to?(:each)
61
+ key.each {|subkey| @map[subkey] = value}
62
+ else
63
+ @map[key] = value
64
+ end
65
+ end
66
+ end
67
+
68
+ @map
69
+ end
70
+
71
+ # Declares the options for the next task to be declared.
72
+ #
73
+ # ==== Parameters
74
+ # Hash[Symbol => Object]:: The hash key is the name of the option and the value
75
+ # is the type of the option. Can be :optional, :required, :boolean or :numeric.
76
+ #
77
+ def method_options(options=nil)
78
+ @method_options ||= Thor::CoreExt::OrderedHash.new
79
+ build_options(options, @method_options) if options
80
+ @method_options
81
+ end
82
+
83
+ # Adds an option to the set of class options. If :for is given as option,
84
+ # it allows you to change the options from a previous defined task.
85
+ #
86
+ # def previous_task
87
+ # # magic
88
+ # end
89
+ #
90
+ # method_options :foo => :bar, :for => :previous_task
91
+ #
92
+ # def next_task
93
+ # # magic
94
+ # end
95
+ #
96
+ # ==== Parameters
97
+ # name<Symbol>:: The name of the argument.
98
+ # options<Hash>:: The description, type, default value, aliases and if this option is required or not.
99
+ # The type can be :string, :boolean, :numeric, :hash or :array. If none is given
100
+ # a default type which accepts both (--name and --name=NAME) entries is assumed.
101
+ #
102
+ def method_option(name, options)
103
+ scope = if options[:for]
104
+ find_and_refresh_task(options[:for]).options
105
+ else
106
+ method_options
107
+ end
108
+
109
+ build_option(name, options, scope)
110
+ end
111
+
112
+ # Parses the task and options from the given args, instantiate the class
113
+ # and invoke the task. This method is used when the arguments must be parsed
114
+ # from an array. If you are inside Ruby and want to use a Thor class, you
115
+ # can simply initialize it:
116
+ #
117
+ # script = MyScript.new(args, options, config)
118
+ # script.invoke(:task, first_arg, second_arg, third_arg)
119
+ #
120
+ def start(args=ARGV, config={})
121
+ config[:shell] ||= Thor::Base.shell.new
122
+
123
+ meth = normalize_task_name(args.shift)
124
+ task = self[meth]
125
+
126
+ options = class_options.merge(task.options)
127
+ opts = Thor::Options.new(options)
128
+ opts.parse(args)
129
+
130
+ instance = new(opts.arguments, opts.options, config)
131
+ instance.invoke(task.name, *opts.trailing)
132
+ rescue Thor::Error => e
133
+ config[:shell].error e.message
134
+ end
135
+
136
+ # Prints help information. If a task name is given, it shows information
137
+ # only about the specific task.
138
+ #
139
+ # ==== Parameters
140
+ # meth<String>:: An optional task name to print usage information about.
141
+ #
142
+ # ==== Options
143
+ # namespace:: When true, shows the namespace in the output before the usage.
144
+ # skip_inherited:: When true, does not show tasks from superclass.
145
+ #
146
+ def help(shell, meth=nil, options={})
147
+ meth, options = nil, meth if meth.is_a?(Hash)
148
+ namespace = options[:namespace] ? self : nil
149
+
150
+ if meth
151
+ task = self.all_tasks[meth]
152
+ raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
153
+
154
+ shell.say "Usage:"
155
+ shell.say " #{task.formatted_usage(namespace)}"
156
+ shell.say
157
+ options_help(shell)
158
+ shell.say task.description
159
+ else
160
+ if options[:short]
161
+ list = self.tasks.map do |_, task|
162
+ [ task.formatted_usage(namespace), task.short_description || '' ]
163
+ end
164
+
165
+ shell.print_table(list, :emphasize_last => true)
166
+ else
167
+ options_help(shell)
168
+
169
+ list = self.all_tasks.map do |_, task|
170
+ [ task.formatted_usage(namespace), task.short_description || '' ]
171
+ end
172
+
173
+ shell.say "Tasks:"
174
+ shell.print_table(list, :ident => 2, :emphasize_last => true)
175
+ end
176
+ end
177
+ end
178
+
179
+ protected
180
+
181
+ def baseclass #:nodoc:
182
+ Thor
183
+ end
184
+
185
+ def valid_task?(meth) #:nodoc:
186
+ public_instance_methods.include?(meth) && @usage && @desc
187
+ end
188
+
189
+ def create_task(meth) #:nodoc:
190
+ tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
191
+ @usage, @desc, @method_options = nil
192
+ end
193
+
194
+ def initialize_added #:nodoc:
195
+ class_options.merge!(method_options)
196
+ @method_options = nil
197
+ end
198
+
199
+ # Receives a task name (can be nil), and try to get a map from it.
200
+ # If a map can't be found use the sent name or the default task.
201
+ #
202
+ def normalize_task_name(meth) #:nodoc:
203
+ mapping = map[meth.to_s]
204
+ meth = mapping || meth || default_task
205
+ meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
206
+ end
207
+
208
+ def options_help(shell) #:nodoc:
209
+ unless self.class_options.empty?
210
+ list = self.class_options.map do |_, option|
211
+ [ option.usage, option.description || '' ]
212
+ end
213
+
214
+ shell.say "Global arguments:"
215
+ shell.print_table(list, :emphasize_last => true, :ident => 2)
216
+ shell.say ""
217
+ end
218
+ end
219
+ end
220
+
221
+ include Thor::Base
222
+
223
+ map HELP_MAPPINGS => :help
224
+
225
+ desc "help [TASK]", "Describe available tasks or one specific task"
226
+ def help(task=nil)
227
+ self.class.help(shell, task, :namespace => task && task.include?(?:))
228
+ end
229
+ end