thor 0.9.2 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +35 -0
- data/README.markdown +47 -32
- data/Rakefile +4 -39
- data/bin/rake2thor +83 -0
- data/bin/thor +4 -324
- data/lib/thor.rb +104 -97
- data/lib/thor/error.rb +3 -0
- data/lib/thor/options.rb +238 -0
- data/lib/thor/ordered_hash.rb +64 -0
- data/lib/thor/runner.rb +260 -0
- data/lib/thor/task.rb +68 -0
- data/lib/thor/task_hash.rb +22 -0
- data/lib/thor/tasks.rb +39 -38
- data/lib/thor/tasks/package.rb +18 -0
- data/lib/thor/util.rb +43 -0
- metadata +17 -10
- data/lib/getopt.rb +0 -238
- data/lib/vendor/ruby2ruby.rb +0 -1090
- data/lib/vendor/sexp.rb +0 -278
- data/lib/vendor/sexp_processor.rb +0 -336
- data/lib/vendor/unified_ruby.rb +0 -196
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
== TODO
|
2
|
+
|
3
|
+
* Change Thor.start to parse ARGV in a single pass
|
4
|
+
* 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
|
7
|
+
|
8
|
+
== 0.9.5, released 2008-08-27
|
9
|
+
|
10
|
+
* Improve Windows compatibility
|
11
|
+
* Update (incorrect) README and task.thor sample file
|
12
|
+
* Options hash is now frozen (once returned)
|
13
|
+
* Allow magic predicates on options object. For instance: `options.force?`
|
14
|
+
* Add support for :numeric type
|
15
|
+
* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
|
16
|
+
* Allow specifying optional args with default values: method_options(:user => "mislav")
|
17
|
+
* Don't write options for nil or false values. This allows, for example, turning color off when running specs.
|
18
|
+
* Exit with the status of the spec command to help CI stuff out some.
|
19
|
+
|
20
|
+
== 0.9.4, released 2008-08-13
|
21
|
+
|
22
|
+
* Try to add Windows compatibility.
|
23
|
+
* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
|
24
|
+
* Allow options at the beginning of the argument list as well as the end.
|
25
|
+
* Make options available with symbol keys in addition to string keys.
|
26
|
+
* Allow true to be passed to Thor#method_options to denote a boolean option.
|
27
|
+
* If loading a thor file fails, don't give up, just print a warning and keep going.
|
28
|
+
* Make sure that we re-raise errors if they happened further down the pipe than we care about.
|
29
|
+
* Only delete the old file on updating when the installation of the new one is a success
|
30
|
+
* Make it Ruby 1.8.5 compatible.
|
31
|
+
* Don't raise an error if a boolean switch is defined multiple times.
|
32
|
+
* Thor::Options now doesn't parse through things that look like options but aren't.
|
33
|
+
* Add URI detection to install task, and make sure we don't append ".thor" to URIs
|
34
|
+
* Add rake2thor to the gem binfiles.
|
35
|
+
* Make sure local Thorfiles override system-wide ones.
|
data/README.markdown
CHANGED
@@ -4,58 +4,73 @@ thor
|
|
4
4
|
Map options to a class. Simply create a class with the appropriate annotations, and have options automatically map
|
5
5
|
to functions and parameters.
|
6
6
|
|
7
|
-
|
7
|
+
Example:
|
8
8
|
|
9
|
-
class MyApp
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
... code ...
|
18
|
-
if opts[:force]
|
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?
|
19
17
|
# do something
|
20
18
|
end
|
19
|
+
# ... other code ...
|
21
20
|
end
|
22
21
|
|
23
22
|
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
|
24
23
|
def list(search = "")
|
25
24
|
# list everything
|
26
25
|
end
|
27
|
-
|
28
26
|
end
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
Hermes automatically maps commands as follows:
|
28
|
+
Thor automatically maps commands as such:
|
33
29
|
|
34
|
-
app install
|
30
|
+
app install myname --force
|
35
31
|
|
36
32
|
That gets converted to:
|
37
33
|
|
38
|
-
MyApp.new.install("
|
39
|
-
|
40
|
-
[1] Use `extend Hermes` to turn a class into an option mapper
|
34
|
+
MyApp.new.install("myname")
|
35
|
+
# with {'force' => true} as options hash
|
41
36
|
|
42
|
-
|
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,
|
43
39
|
convert -L to :list
|
44
|
-
|
45
|
-
[3] Describe the method immediately below. The first parameter is the usage information,
|
40
|
+
3. Describe the method immediately below. The first parameter is the usage information,
|
46
41
|
and the second parameter is the description.
|
47
|
-
|
48
|
-
|
49
|
-
In this case, a --force and a -f option is added.
|
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.
|
50
44
|
|
51
45
|
Types for `method_options`
|
52
46
|
--------------------------
|
53
47
|
|
54
48
|
<dl>
|
55
|
-
<dt>:boolean</dt>
|
56
|
-
|
57
|
-
<dt
|
58
|
-
|
59
|
-
<dt>:
|
60
|
-
|
61
|
-
</
|
49
|
+
<dt><code>:boolean</code></dt>
|
50
|
+
<dd>true if the option is passed</dd>
|
51
|
+
<dt><code>true</code></dt>
|
52
|
+
<dd>same as <code>:boolean</code></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
CHANGED
@@ -1,41 +1,6 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake/gempackagetask'
|
3
|
-
require 'rubygems/specification'
|
4
|
-
require 'spec/rake/spectask'
|
5
|
-
require 'date'
|
6
|
-
|
7
|
-
GEM = "thor"
|
8
|
-
GEM_VERSION = "0.9.2"
|
9
|
-
AUTHOR = "Yehuda Katz"
|
10
|
-
EMAIL = "wycats@gmail.com"
|
11
|
-
HOMEPAGE = "http://yehudakatz.com"
|
12
|
-
SUMMARY = "A gem that maps options to a class"
|
13
|
-
|
14
|
-
spec = Gem::Specification.new do |s|
|
15
|
-
s.name = GEM
|
16
|
-
s.version = GEM_VERSION
|
17
|
-
s.platform = Gem::Platform::RUBY
|
18
|
-
s.has_rdoc = true
|
19
|
-
s.extra_rdoc_files = ["README.markdown", "LICENSE"]
|
20
|
-
s.summary = SUMMARY
|
21
|
-
s.description = s.summary
|
22
|
-
s.author = AUTHOR
|
23
|
-
s.email = EMAIL
|
24
|
-
s.homepage = HOMEPAGE
|
25
|
-
|
26
|
-
s.require_path = 'lib'
|
27
|
-
s.autorequire = GEM
|
28
|
-
s.bindir = "bin"
|
29
|
-
s.executables = %w( thor )
|
30
|
-
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{bin,lib,specs}/**/*")
|
31
|
-
end
|
32
|
-
|
33
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
34
|
-
pkg.gem_spec = spec
|
35
|
-
end
|
36
|
-
|
37
1
|
task :default => :install
|
2
|
+
|
38
3
|
desc "install the gem locally"
|
39
|
-
task :install
|
40
|
-
sh %{
|
41
|
-
end
|
4
|
+
task :install do
|
5
|
+
sh %{ruby "#{File.dirname(__FILE__)}/bin/thor" :install}
|
6
|
+
end
|
data/bin/rake2thor
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ruby2ruby'
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
input = ARGV[0] || 'Rakefile'
|
8
|
+
output = ARGV[1] || 'Thorfile'
|
9
|
+
|
10
|
+
$requires = []
|
11
|
+
|
12
|
+
module Kernel
|
13
|
+
def require_with_record(file)
|
14
|
+
$requires << file if caller[1] =~ /rake2thor:/
|
15
|
+
require_without_record file
|
16
|
+
end
|
17
|
+
alias_method :require_without_record, :require
|
18
|
+
alias_method :require, :require_with_record
|
19
|
+
end
|
20
|
+
|
21
|
+
load input
|
22
|
+
|
23
|
+
@private_methods = []
|
24
|
+
|
25
|
+
def file_task_name(name)
|
26
|
+
"compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_')
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_for_task(task)
|
30
|
+
file_task = task.is_a?(Rake::FileTask)
|
31
|
+
comment = task.instance_variable_get('@comment')
|
32
|
+
prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?))
|
33
|
+
actions = task.instance_variable_get('@actions')
|
34
|
+
name = task.name.gsub(/^([^:]+:)+/, '')
|
35
|
+
name = file_task_name(name) if file_task
|
36
|
+
meth = ''
|
37
|
+
|
38
|
+
meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment
|
39
|
+
meth << "def #{name}\n"
|
40
|
+
|
41
|
+
meth << prereqs.map do |pre|
|
42
|
+
pre = pre.to_s
|
43
|
+
pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask)
|
44
|
+
' ' + pre
|
45
|
+
end.join("\n")
|
46
|
+
|
47
|
+
meth << "\n\n" unless prereqs.empty? || actions.empty?
|
48
|
+
|
49
|
+
meth << actions.map do |act|
|
50
|
+
act = act.to_ruby
|
51
|
+
unless act.gsub!(/^proc \{ \|(\w+)\|\n/,
|
52
|
+
" \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n")
|
53
|
+
act.gsub!(/^proc \{\n/, '')
|
54
|
+
end
|
55
|
+
act.gsub(/\n\}$/, '')
|
56
|
+
end.join("\n")
|
57
|
+
|
58
|
+
meth << "\nend"
|
59
|
+
|
60
|
+
if file_task
|
61
|
+
@private_methods << meth
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
meth
|
66
|
+
end
|
67
|
+
|
68
|
+
body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n")
|
69
|
+
|
70
|
+
unless @private_methods.empty?
|
71
|
+
body << "\n\n private\n\n"
|
72
|
+
body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
requires = $requires.map { |r| "require #{r.inspect}" }.join("\n")
|
76
|
+
|
77
|
+
File.open(output, 'w') { |f| f.write(<<END.lstrip) }
|
78
|
+
#{requires}
|
79
|
+
|
80
|
+
class Default < Thor
|
81
|
+
#{body}
|
82
|
+
end
|
83
|
+
END
|
data/bin/thor
CHANGED
@@ -1,327 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# -*- mode: ruby -*-
|
2
3
|
|
3
|
-
require "thor"
|
4
|
-
require
|
5
|
-
require "fileutils"
|
6
|
-
require "yaml"
|
7
|
-
require "digest/md5"
|
8
|
-
require "readline"
|
4
|
+
require File.dirname(__FILE__) + "/../lib/thor"
|
5
|
+
require 'thor/runner'
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
class << self
|
13
|
-
|
14
|
-
# ==== Returns
|
15
|
-
# Array[Class]:: All the classes in the object space.
|
16
|
-
def classes
|
17
|
-
klasses = []
|
18
|
-
ObjectSpace.each_object(Class) {|o| klasses << o}
|
19
|
-
klasses
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
class Thor::Util
|
26
|
-
|
27
|
-
# @public
|
28
|
-
def self.constant_to_thor_path(str)
|
29
|
-
snake_case(str).squeeze(":")
|
30
|
-
end
|
31
|
-
|
32
|
-
# @public
|
33
|
-
def self.constant_from_thor_path(str)
|
34
|
-
make_constant(to_constant(str))
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.to_constant(str)
|
38
|
-
str.gsub(/:(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.constants_in_contents(str)
|
42
|
-
klasses = self.constants.dup
|
43
|
-
eval(str)
|
44
|
-
ret = self.constants - klasses
|
45
|
-
ret.each {|k| self.send(:remove_const, k)}
|
46
|
-
ret
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
# @private
|
51
|
-
def self.make_constant(str)
|
52
|
-
list = str.split("::")
|
53
|
-
obj = Object
|
54
|
-
list.each {|x| obj = obj.const_get(x) }
|
55
|
-
obj
|
56
|
-
end
|
57
|
-
|
58
|
-
# @private
|
59
|
-
def self.snake_case(str)
|
60
|
-
return str.downcase if str =~ /^[A-Z]+$/
|
61
|
-
str.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
|
62
|
-
return $+.downcase
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
class Thor::Runner < Thor
|
68
|
-
|
69
|
-
def self.globs_for(path)
|
70
|
-
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
|
71
|
-
end
|
72
|
-
|
73
|
-
def initialize_thorfiles(include_system = true)
|
74
|
-
thorfiles(include_system).each {|f| load f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
|
75
|
-
end
|
76
|
-
|
77
|
-
map "-T" => :list, "-i" => :install, "-u" => :update
|
78
|
-
|
79
|
-
desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates"
|
80
|
-
method_options :as => :optional
|
81
|
-
def install(name, opts)
|
82
|
-
initialize_thorfiles
|
83
|
-
begin
|
84
|
-
contents = open(name).read
|
85
|
-
rescue OpenURI::HTTPError
|
86
|
-
puts "The URI you provided: `#{name}' was invalid"
|
87
|
-
return
|
88
|
-
rescue Errno::ENOENT
|
89
|
-
puts "`#{name}' is not a valid file"
|
90
|
-
return
|
91
|
-
end
|
92
|
-
|
93
|
-
puts "Your Thorfile contains: "
|
94
|
-
puts contents
|
95
|
-
print "Do you wish to continue [y/N]? "
|
96
|
-
response = Readline.readline
|
97
|
-
|
98
|
-
return unless response =~ /^\s*y/i
|
99
|
-
|
100
|
-
constants = Thor::Util.constants_in_contents(contents)
|
101
|
-
|
102
|
-
name = name =~ /\.thor$/ ? name : "#{name}.thor"
|
103
|
-
|
104
|
-
as = opts["as"] || begin
|
105
|
-
first_line = contents.split("\n")[0]
|
106
|
-
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
|
107
|
-
end
|
108
|
-
|
109
|
-
if !as
|
110
|
-
print "Please specify a name for #{name} in the system repository [#{name}]: "
|
111
|
-
as = Readline.readline
|
112
|
-
as = name if as.empty?
|
113
|
-
end
|
114
|
-
|
115
|
-
FileUtils.mkdir_p thor_root
|
116
|
-
|
117
|
-
yaml_file = File.join(thor_root, "thor.yml")
|
118
|
-
FileUtils.touch(yaml_file)
|
119
|
-
yaml = thor_yaml
|
120
|
-
|
121
|
-
yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => name, :constants => constants}
|
122
|
-
|
123
|
-
save_yaml(yaml)
|
124
|
-
|
125
|
-
puts "Storing thor file in your system repository"
|
126
|
-
|
127
|
-
File.open(File.join(thor_root, yaml[as][:filename] + ".thor"), "w") do |file|
|
128
|
-
file.puts contents
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
desc "uninstall NAME", "uninstall a named Thor module"
|
133
|
-
def uninstall(name)
|
134
|
-
yaml = thor_yaml
|
135
|
-
unless yaml[name]
|
136
|
-
puts "There was no module by that name installed"
|
137
|
-
return
|
138
|
-
end
|
139
|
-
|
140
|
-
puts "Uninstalling #{name}."
|
141
|
-
|
142
|
-
file = File.join(thor_root, "#{yaml[name][:filename]}.thor")
|
143
|
-
File.delete(file)
|
144
|
-
yaml.delete(name)
|
145
|
-
save_yaml(yaml)
|
146
|
-
|
147
|
-
puts "Done."
|
148
|
-
end
|
149
|
-
|
150
|
-
desc "update NAME", "update a Thor file from its original location"
|
151
|
-
def update(name)
|
152
|
-
yaml = thor_yaml
|
153
|
-
if !yaml[name] || !yaml[name][:location]
|
154
|
-
puts "`#{name}' was not found in the system repository"
|
155
|
-
else
|
156
|
-
puts "Updating `#{name}' from #{yaml[name][:location]}"
|
157
|
-
install(yaml[name][:location], "as" => name)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def installed
|
162
|
-
Dir["#{ENV["HOME"]}/.thor/**/*.thor"].each do |f|
|
163
|
-
load f unless Thor.subclass_files.keys.include?(File.expand_path(f))
|
164
|
-
end
|
165
|
-
display_klasses(true)
|
166
|
-
end
|
167
|
-
|
168
|
-
desc "list [SEARCH]", "list the available thor tasks (--substring means SEARCH can be anywhere in the module)"
|
169
|
-
method_options :substring => :boolean
|
170
|
-
def list(search = "", options = {})
|
171
|
-
initialize_thorfiles
|
172
|
-
search = ".*#{search}" if options["substring"]
|
173
|
-
search = /^#{search}.*/i
|
174
|
-
|
175
|
-
display_klasses(false, Thor.subclasses.select {|k|
|
176
|
-
Thor::Util.constant_to_thor_path(k.name) =~ search})
|
177
|
-
end
|
178
|
-
|
179
|
-
def method_missing(meth, *args)
|
180
|
-
initialize_thorfiles(false)
|
181
|
-
meth = meth.to_s
|
182
|
-
unless meth =~ /:/
|
183
|
-
puts "Thor tasks must contain a :"
|
184
|
-
return
|
185
|
-
end
|
186
|
-
|
187
|
-
thor_klass = meth.split(":")[0...-1].join(":")
|
188
|
-
to_call = meth.split(":").last
|
189
|
-
|
190
|
-
yaml = thor_yaml
|
191
|
-
|
192
|
-
klass_str = Thor::Util.to_constant(thor_klass)
|
193
|
-
files = yaml.inject([]) { |a,(k,v)| a << v[:filename] if v[:constants] && v[:constants].include?(klass_str); a }
|
194
|
-
|
195
|
-
unless files.empty?
|
196
|
-
files.each do |f|
|
197
|
-
load File.join(thor_root, "#{f}.thor")
|
198
|
-
end
|
199
|
-
klass = Thor::Util.constant_from_thor_path(thor_klass)
|
200
|
-
else
|
201
|
-
begin
|
202
|
-
klass = Thor::Util.constant_from_thor_path(thor_klass)
|
203
|
-
rescue
|
204
|
-
puts "There was no available namespace `#{thor_klass}'."
|
205
|
-
return
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
unless klass.ancestors.include?(Thor)
|
210
|
-
puts "`#{thor_klass}' is not a Thor module"
|
211
|
-
return
|
212
|
-
end
|
213
|
-
|
214
|
-
ARGV.replace [to_call, *(args + ARGV)].compact
|
215
|
-
begin
|
216
|
-
klass.start
|
217
|
-
rescue ArgumentError
|
218
|
-
puts "You need to call #{to_call} as `#{klass.usage_for_method(to_call)}'"
|
219
|
-
rescue NoMethodError
|
220
|
-
puts "`#{to_call}' is not available in #{thor_klass}"
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
private
|
225
|
-
def thor_root
|
226
|
-
File.join(ENV["HOME"], ".thor")
|
227
|
-
end
|
228
|
-
|
229
|
-
def thor_yaml
|
230
|
-
yaml_file = File.join(thor_root, "thor.yml")
|
231
|
-
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
|
232
|
-
yaml || {}
|
233
|
-
end
|
234
|
-
|
235
|
-
def save_yaml(yaml)
|
236
|
-
yaml_file = File.join(thor_root, "thor.yml")
|
237
|
-
File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml }
|
238
|
-
end
|
239
|
-
|
240
|
-
def display_klasses(with_modules = false, klasses = Thor.subclasses)
|
241
|
-
klasses = klasses - [Thor::Runner]
|
242
|
-
|
243
|
-
if klasses.empty?
|
244
|
-
puts "No thorfiles available"
|
245
|
-
return
|
246
|
-
end
|
247
|
-
|
248
|
-
if with_modules
|
249
|
-
yaml = thor_yaml
|
250
|
-
max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size
|
251
|
-
|
252
|
-
print "%-#{max_name + 4}s" % "Name"
|
253
|
-
puts "Modules"
|
254
|
-
print "%-#{max_name + 4}s" % "----"
|
255
|
-
puts "-------"
|
256
|
-
|
257
|
-
yaml.each do |name, info|
|
258
|
-
print "%-#{max_name + 4}s" % name
|
259
|
-
puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ")
|
260
|
-
end
|
261
|
-
|
262
|
-
puts
|
263
|
-
end
|
264
|
-
|
265
|
-
puts "Tasks"
|
266
|
-
puts "-----"
|
267
|
-
|
268
|
-
# Calculate the largest base class name
|
269
|
-
max_base = klasses.max do |x,y|
|
270
|
-
Thor::Util.constant_to_thor_path(x.name).size <=> Thor::Util.constant_to_thor_path(y.name).size
|
271
|
-
end.name.size
|
272
|
-
|
273
|
-
# Calculate the size of the largest option description
|
274
|
-
max_left_item = klasses.max do |x,y|
|
275
|
-
(x.help_list && x.help_list.max.usage + x.help_list.max.opt).to_i <=>
|
276
|
-
(y.help_list && y.help_list.max.usage + y.help_list.max.opt).to_i
|
277
|
-
end
|
278
|
-
|
279
|
-
max_left = max_left_item.help_list.max.usage + max_left_item.help_list.max.opt
|
280
|
-
|
281
|
-
klasses.map {|k| k.help_list}.compact.each do |item|
|
282
|
-
display_tasks(item, max_base, max_left)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def display_tasks(item, max_base, max_left)
|
287
|
-
base = Thor::Util.constant_to_thor_path(item.klass.name)
|
288
|
-
item.usages.each do |name, usage|
|
289
|
-
format_string = "%-#{max_left + max_base + 5}s"
|
290
|
-
print format_string %
|
291
|
-
"#{base}:#{item.usages.assoc(name).last} #{display_opts(item.opts.assoc(name) && item.opts.assoc(name).last)}"
|
292
|
-
puts item.descriptions.assoc(name).last
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
def display_opts(opts)
|
297
|
-
return "" unless opts
|
298
|
-
opts.map do |opt, val|
|
299
|
-
if val == true || val == "BOOLEAN"
|
300
|
-
"[#{opt}]"
|
301
|
-
elsif val == "REQUIRED"
|
302
|
-
opt + "=" + opt.gsub(/\-/, "").upcase
|
303
|
-
elsif val == "OPTIONAL"
|
304
|
-
"[" + opt + "=" + opt.gsub(/\-/, "").upcase + "]"
|
305
|
-
end
|
306
|
-
end.join(" ")
|
307
|
-
end
|
308
|
-
|
309
|
-
def thorfiles(include_system = true)
|
310
|
-
path = Dir.pwd
|
311
|
-
system_thorfiles = Dir["#{ENV["HOME"]}/.thor/**/*.thor"]
|
312
|
-
thorfiles = []
|
313
|
-
|
314
|
-
# Look for Thorfile or *.thor in the current directory or a parent directory, until the root
|
315
|
-
while thorfiles.empty?
|
316
|
-
thorfiles = Dir[*Thor::Runner.globs_for(path)]
|
317
|
-
path = File.dirname(path)
|
318
|
-
break if path == "/"
|
319
|
-
end
|
320
|
-
thorfiles + (include_system ? system_thorfiles : [])
|
321
|
-
end
|
322
|
-
|
323
|
-
end
|
324
|
-
|
325
|
-
unless defined?(Spec)
|
326
|
-
Thor::Runner.start
|
327
|
-
end
|
7
|
+
Thor::Runner.start
|