thor 0.9.2 → 0.9.5
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 +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
|