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
@@ -0,0 +1,64 @@
|
|
1
|
+
class Thor
|
2
|
+
# This class is based on the Ruby 1.9 ordered hashes.
|
3
|
+
# It keeps the semantics and most of the efficiency of normal hashes
|
4
|
+
# while also keeping track of the order in which elements were set.
|
5
|
+
class OrderedHash
|
6
|
+
Node = Struct.new(:key, :value, :next, :prev)
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@hash = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_copy(other)
|
14
|
+
@hash = other.instance_variable_get('@hash').clone
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
@hash[key] && @hash[key].value
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, value)
|
22
|
+
node = Node.new(key, value)
|
23
|
+
|
24
|
+
if old = @hash[key]
|
25
|
+
if old.prev
|
26
|
+
old.prev.next = old.next
|
27
|
+
else # old is @first and @last
|
28
|
+
@first = @last = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if @first.nil?
|
33
|
+
@first = @last = node
|
34
|
+
else
|
35
|
+
node.prev = @last
|
36
|
+
@last.next = node
|
37
|
+
@last = node
|
38
|
+
end
|
39
|
+
|
40
|
+
@hash[key] = node
|
41
|
+
value
|
42
|
+
end
|
43
|
+
|
44
|
+
def each
|
45
|
+
return unless @first
|
46
|
+
yield [@first.key, @first.value]
|
47
|
+
node = @first
|
48
|
+
yield [node.key, node.value] while node = node.next
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def values
|
53
|
+
self.map { |k, v| v }
|
54
|
+
end
|
55
|
+
|
56
|
+
def +(other)
|
57
|
+
new = clone
|
58
|
+
other.each do |key, value|
|
59
|
+
new[key] = value unless self[key]
|
60
|
+
end
|
61
|
+
new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/thor/runner.rb
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require "thor/util"
|
3
|
+
require "open-uri"
|
4
|
+
require "fileutils"
|
5
|
+
require "yaml"
|
6
|
+
require "digest/md5"
|
7
|
+
require "readline"
|
8
|
+
|
9
|
+
class Thor::Runner < Thor
|
10
|
+
|
11
|
+
def self.globs_for(path)
|
12
|
+
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
|
13
|
+
end
|
14
|
+
|
15
|
+
map "-T" => :list, "-i" => :install, "-u" => :update
|
16
|
+
|
17
|
+
desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates"
|
18
|
+
method_options :as => :optional, :relative => :boolean
|
19
|
+
def install(name)
|
20
|
+
initialize_thorfiles
|
21
|
+
begin
|
22
|
+
contents = open(name).read
|
23
|
+
rescue OpenURI::HTTPError
|
24
|
+
raise Error, "Error opening URI `#{name}'"
|
25
|
+
rescue Errno::ENOENT
|
26
|
+
raise Error, "Error opening file `#{name}'"
|
27
|
+
end
|
28
|
+
|
29
|
+
is_uri = File.exist?(name) ? false : true
|
30
|
+
|
31
|
+
puts "Your Thorfile contains: "
|
32
|
+
puts contents
|
33
|
+
print "Do you wish to continue [y/N]? "
|
34
|
+
response = Readline.readline
|
35
|
+
|
36
|
+
return false unless response =~ /^\s*y/i
|
37
|
+
|
38
|
+
constants = Thor::Util.constants_in_contents(contents)
|
39
|
+
|
40
|
+
# name = name =~ /\.thor$/ || is_uri ? name : "#{name}.thor"
|
41
|
+
|
42
|
+
as = options["as"] || begin
|
43
|
+
first_line = contents.split("\n")[0]
|
44
|
+
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
|
45
|
+
end
|
46
|
+
|
47
|
+
if !as
|
48
|
+
print "Please specify a name for #{name} in the system repository [#{name}]: "
|
49
|
+
as = Readline.readline
|
50
|
+
as = name if as.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
FileUtils.mkdir_p thor_root
|
54
|
+
|
55
|
+
yaml_file = File.join(thor_root, "thor.yml")
|
56
|
+
FileUtils.touch(yaml_file)
|
57
|
+
yaml = thor_yaml
|
58
|
+
|
59
|
+
location = (options[:relative] || is_uri) ? name : File.expand_path(name)
|
60
|
+
yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => location, :constants => constants}
|
61
|
+
|
62
|
+
save_yaml(yaml)
|
63
|
+
|
64
|
+
puts "Storing thor file in your system repository"
|
65
|
+
|
66
|
+
File.open(File.join(thor_root, yaml[as][:filename]), "w") do |file|
|
67
|
+
file.puts contents
|
68
|
+
end
|
69
|
+
|
70
|
+
yaml[as][:filename] # Indicate sucess
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "uninstall NAME", "uninstall a named Thor module"
|
74
|
+
def uninstall(name)
|
75
|
+
yaml = thor_yaml
|
76
|
+
raise Error, "Can't find module `#{name}'" unless yaml[name]
|
77
|
+
|
78
|
+
puts "Uninstalling #{name}."
|
79
|
+
|
80
|
+
file = File.join(thor_root, "#{yaml[name][:filename]}")
|
81
|
+
File.delete(file)
|
82
|
+
yaml.delete(name)
|
83
|
+
save_yaml(yaml)
|
84
|
+
|
85
|
+
puts "Done."
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "update NAME", "update a Thor file from its original location"
|
89
|
+
def update(name)
|
90
|
+
yaml = thor_yaml
|
91
|
+
raise Error, "Can't find module `#{name}'" if !yaml[name] || !yaml[name][:location]
|
92
|
+
|
93
|
+
puts "Updating `#{name}' from #{yaml[name][:location]}"
|
94
|
+
old_filename = yaml[name][:filename]
|
95
|
+
options["as"] = name
|
96
|
+
filename = install(yaml[name][:location])
|
97
|
+
unless filename == old_filename
|
98
|
+
File.delete(File.join(thor_root, old_filename))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc "installed", "list the installed Thor modules and tasks (--internal means list the built-in tasks as well)"
|
103
|
+
method_options :internal => :boolean
|
104
|
+
def installed
|
105
|
+
thor_root_glob.each do |f|
|
106
|
+
next if f =~ /thor\.yml$/
|
107
|
+
load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))
|
108
|
+
end
|
109
|
+
|
110
|
+
klasses = Thor.subclasses
|
111
|
+
klasses -= [Thor, Thor::Runner] unless options['internal']
|
112
|
+
display_klasses(true, klasses)
|
113
|
+
end
|
114
|
+
|
115
|
+
desc "list [SEARCH]", "list the available thor tasks (--substring means SEARCH can be anywhere in the module)"
|
116
|
+
method_options :substring => :boolean
|
117
|
+
def list(search = "")
|
118
|
+
initialize_thorfiles
|
119
|
+
search = ".*#{search}" if options["substring"]
|
120
|
+
search = /^#{search}.*/i
|
121
|
+
|
122
|
+
display_klasses(false, Thor.subclasses.select {|k|
|
123
|
+
Thor::Util.constant_to_thor_path(k.name) =~ search})
|
124
|
+
end
|
125
|
+
|
126
|
+
# Override Thor#help so we can give info about not-yet-loaded tasks
|
127
|
+
def help(task = nil)
|
128
|
+
initialize_thorfiles(task) if task && task.include?(?:)
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
def method_missing(meth, *args)
|
133
|
+
meth = meth.to_s
|
134
|
+
super(meth.to_sym, *args) unless meth.include? ?:
|
135
|
+
|
136
|
+
initialize_thorfiles(meth)
|
137
|
+
task = Thor[meth]
|
138
|
+
task.parse task.klass.new, ARGV[1..-1]
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.thor_root
|
142
|
+
File.join(ENV["HOME"] || ENV["APPDATA"], ".thor")
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.thor_root_glob
|
146
|
+
# On Windows thor_root will be something like this:
|
147
|
+
#
|
148
|
+
# C:\Documents and Settings\james\.thor
|
149
|
+
#
|
150
|
+
# If we don't #gsub the \ character, Dir.glob will fail.
|
151
|
+
Dir["#{thor_root.gsub(/\\/, '/')}/**/*"]
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def thor_root
|
156
|
+
self.class.thor_root
|
157
|
+
end
|
158
|
+
|
159
|
+
def thor_root_glob
|
160
|
+
self.class.thor_root_glob
|
161
|
+
end
|
162
|
+
|
163
|
+
def thor_yaml
|
164
|
+
yaml_file = File.join(thor_root, "thor.yml")
|
165
|
+
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
|
166
|
+
yaml || {}
|
167
|
+
end
|
168
|
+
|
169
|
+
def save_yaml(yaml)
|
170
|
+
yaml_file = File.join(thor_root, "thor.yml")
|
171
|
+
File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml }
|
172
|
+
end
|
173
|
+
|
174
|
+
def display_klasses(with_modules = false, klasses = Thor.subclasses)
|
175
|
+
klasses -= [Thor, Thor::Runner] unless with_modules
|
176
|
+
raise Error, "No Thor tasks available" if klasses.empty?
|
177
|
+
|
178
|
+
if with_modules && !(yaml = thor_yaml).empty?
|
179
|
+
max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size
|
180
|
+
modules_label = "Modules"
|
181
|
+
namespaces_label = "Namespaces"
|
182
|
+
column_width = [max_name + 4, modules_label.size + 1].max
|
183
|
+
|
184
|
+
print "%-#{column_width}s" % modules_label
|
185
|
+
puts namespaces_label
|
186
|
+
print "%-#{column_width}s" % ("-" * modules_label.size)
|
187
|
+
puts "-" * namespaces_label.size
|
188
|
+
|
189
|
+
yaml.each do |name, info|
|
190
|
+
print "%-#{column_width}s" % name
|
191
|
+
puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ")
|
192
|
+
end
|
193
|
+
|
194
|
+
puts
|
195
|
+
end
|
196
|
+
|
197
|
+
puts "Tasks"
|
198
|
+
puts "-----"
|
199
|
+
|
200
|
+
# Calculate the largest base class name
|
201
|
+
max_base = klasses.max do |x,y|
|
202
|
+
Thor::Util.constant_to_thor_path(x.name).size <=> Thor::Util.constant_to_thor_path(y.name).size
|
203
|
+
end.name.size
|
204
|
+
|
205
|
+
# Calculate the size of the largest option description
|
206
|
+
max_left_item = klasses.max do |x,y|
|
207
|
+
(x.maxima.usage + x.maxima.opt).to_i <=> (y.maxima.usage + y.maxima.opt).to_i
|
208
|
+
end
|
209
|
+
|
210
|
+
max_left = max_left_item.maxima.usage + max_left_item.maxima.opt
|
211
|
+
|
212
|
+
klasses.each {|k| display_tasks(k, max_base, max_left)}
|
213
|
+
end
|
214
|
+
|
215
|
+
def display_tasks(klass, max_base, max_left)
|
216
|
+
base = Thor::Util.constant_to_thor_path(klass.name)
|
217
|
+
klass.tasks.each true do |name, task|
|
218
|
+
format_string = "%-#{max_left + max_base + 5}s"
|
219
|
+
print format_string % task.formatted_usage(true)
|
220
|
+
puts task.description
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def initialize_thorfiles(relevant_to = nil)
|
225
|
+
thorfiles(relevant_to).each {|f| load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
|
226
|
+
end
|
227
|
+
|
228
|
+
def load_thorfile(path)
|
229
|
+
begin
|
230
|
+
load path
|
231
|
+
rescue Object => e
|
232
|
+
$stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def thorfiles(relevant_to = nil)
|
237
|
+
path = Dir.pwd
|
238
|
+
thorfiles = []
|
239
|
+
|
240
|
+
# Look for Thorfile or *.thor in the current directory or a parent directory, until the root
|
241
|
+
while thorfiles.empty?
|
242
|
+
thorfiles = Thor::Runner.globs_for(path).map {|g| Dir[g]}.flatten
|
243
|
+
path = File.dirname(path)
|
244
|
+
break if path == "/"
|
245
|
+
end
|
246
|
+
|
247
|
+
# We want to load system-wide Thorfiles first
|
248
|
+
# so the local Thorfiles will override them.
|
249
|
+
(relevant_to ? thorfiles_relevant_to(relevant_to) :
|
250
|
+
thor_root_glob) + thorfiles - ["#{thor_root}/thor.yml"]
|
251
|
+
end
|
252
|
+
|
253
|
+
def thorfiles_relevant_to(meth)
|
254
|
+
klass_str = Thor::Util.to_constant(meth.split(":")[0...-1].join(":"))
|
255
|
+
thor_yaml.select do |k, v|
|
256
|
+
v[:constants] && v[:constants].include?(klass_str)
|
257
|
+
end.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
data/lib/thor/task.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'thor/error'
|
2
|
+
require 'thor/util'
|
3
|
+
|
4
|
+
class Thor
|
5
|
+
class Task < Struct.new(:meth, :description, :usage, :opts, :klass)
|
6
|
+
def self.dynamic(meth, klass)
|
7
|
+
new(meth, "A dynamically-generated task", meth.to_s, nil, klass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(obj, args)
|
11
|
+
list, hash = parse_args(args)
|
12
|
+
obj.options = hash
|
13
|
+
run(obj, *list)
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(obj, *params)
|
17
|
+
raise NoMethodError, "the `#{meth}' task of #{obj.class} is private" if
|
18
|
+
(obj.private_methods + obj.protected_methods).include?(meth)
|
19
|
+
|
20
|
+
obj.send(meth, *params)
|
21
|
+
rescue ArgumentError => e
|
22
|
+
# backtrace sans anything in this file
|
23
|
+
backtrace = e.backtrace.reject {|frame| frame =~ /^#{Regexp.escape(__FILE__)}/}
|
24
|
+
# and sans anything that got us here
|
25
|
+
backtrace -= caller
|
26
|
+
raise e unless backtrace.empty?
|
27
|
+
|
28
|
+
# okay, they really did call it wrong
|
29
|
+
raise Error, "`#{meth}' was called incorrectly. Call as `#{formatted_usage}'"
|
30
|
+
rescue NoMethodError => e
|
31
|
+
begin
|
32
|
+
raise e unless e.message =~ /^undefined method `#{meth}' for #{Regexp.escape(obj.inspect)}$/
|
33
|
+
rescue
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
raise Error, "The #{namespace false} namespace doesn't have a `#{meth}' task"
|
37
|
+
end
|
38
|
+
|
39
|
+
def namespace(remove_default = true)
|
40
|
+
Thor::Util.constant_to_thor_path(klass, remove_default)
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_klass(klass)
|
44
|
+
new = self.dup
|
45
|
+
new.klass = klass
|
46
|
+
new
|
47
|
+
end
|
48
|
+
|
49
|
+
def opts
|
50
|
+
return super unless super.kind_of? Hash
|
51
|
+
self.opts = Options.new(super)
|
52
|
+
end
|
53
|
+
|
54
|
+
def formatted_usage(namespace = false)
|
55
|
+
(namespace ? self.namespace + ':' : '') + usage +
|
56
|
+
(opts ? " " + opts.formatted_usage : "")
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def parse_args(args)
|
62
|
+
return [args, {}] unless opts
|
63
|
+
hash = opts.parse(args)
|
64
|
+
list = opts.non_opts
|
65
|
+
[list, hash]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'thor/ordered_hash'
|
2
|
+
require 'thor/task'
|
3
|
+
|
4
|
+
class Thor::TaskHash < Thor::OrderedHash
|
5
|
+
def initialize(klass)
|
6
|
+
super()
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(local = false, &block)
|
11
|
+
super() { |k, t| yield k, t.with_klass(@klass) }
|
12
|
+
@klass.superclass.tasks.each { |k, t| yield k, t.with_klass(@klass) } unless local || @klass == Thor
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
if task = super(name) || (@klass == Thor && @klass.superclass.tasks[name])
|
17
|
+
return task.with_klass(@klass)
|
18
|
+
end
|
19
|
+
|
20
|
+
Thor::Task.dynamic(name, @klass)
|
21
|
+
end
|
22
|
+
end
|
data/lib/thor/tasks.rb
CHANGED
@@ -2,29 +2,28 @@ require "thor"
|
|
2
2
|
require "fileutils"
|
3
3
|
|
4
4
|
class Thor
|
5
|
-
def self.package_task
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
RUBY
|
5
|
+
def self.package_task(spec)
|
6
|
+
desc "package", "package up the gem"
|
7
|
+
define_method :package do
|
8
|
+
FileUtils.mkdir_p(File.join(Dir.pwd, "pkg"))
|
9
|
+
Gem::Builder.new(spec).build
|
10
|
+
FileUtils.mv(spec.file_name, File.join(Dir.pwd, "pkg", spec.file_name))
|
11
|
+
end
|
14
12
|
end
|
15
13
|
|
16
|
-
def self.install_task
|
17
|
-
package_task
|
14
|
+
def self.install_task(spec)
|
15
|
+
package_task spec
|
16
|
+
|
17
|
+
null, sudo, gem = RUBY_PLATFORM =~ /w(in)?32$/ ? ['NUL', '', 'gem.bat'] :
|
18
|
+
['/dev/null', 'sudo', 'gem']
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
RUBY
|
20
|
+
desc "install", "install the gem"
|
21
|
+
define_method :install do
|
22
|
+
old_stderr, $stderr = $stderr.dup, File.open(null, "w")
|
23
|
+
package
|
24
|
+
$stderr = old_stderr
|
25
|
+
system %{#{sudo} #{gem} install pkg/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources}
|
26
|
+
end
|
28
27
|
end
|
29
28
|
|
30
29
|
def self.spec_task(file_list, opts = {})
|
@@ -42,32 +41,34 @@ class Thor
|
|
42
41
|
FileUtils.rm_rf(File.join(Dir.pwd, rcov_dir))
|
43
42
|
end
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
cmd << "-S rcov -o #{rcov_dir} #{rcov_opts.inspect[1...-1]} "
|
51
|
-
end
|
52
|
-
cmd << `which spec`.chomp
|
53
|
-
cmd << " -- " if #{rcov.inspect}
|
54
|
-
cmd << " "
|
55
|
-
cmd << #{file_list.inspect}
|
56
|
-
cmd << " "
|
57
|
-
cmd << #{options.inspect}
|
58
|
-
puts cmd if #{verbose.inspect}
|
59
|
-
system(cmd)
|
44
|
+
desc(name, "spec task")
|
45
|
+
define_method(name) do
|
46
|
+
cmd = "ruby "
|
47
|
+
if rcov
|
48
|
+
cmd << "-S rcov -o #{rcov_dir} #{rcov_opts} "
|
60
49
|
end
|
61
|
-
|
50
|
+
cmd << `which spec`.chomp
|
51
|
+
cmd << " -- " if rcov
|
52
|
+
cmd << " "
|
53
|
+
cmd << file_list
|
54
|
+
cmd << " "
|
55
|
+
cmd << options
|
56
|
+
puts cmd if verbose
|
57
|
+
system(cmd)
|
58
|
+
exit($?.exitstatus)
|
59
|
+
end
|
62
60
|
end
|
63
61
|
|
64
62
|
private
|
65
63
|
def self.convert_task_options(opts)
|
66
64
|
opts.map do |key, value|
|
67
|
-
|
65
|
+
case value
|
66
|
+
when true
|
68
67
|
"--#{key}"
|
69
|
-
|
68
|
+
when Array
|
70
69
|
value.map {|v| "--#{key} #{v.inspect}"}.join(" ")
|
70
|
+
when nil, false
|
71
|
+
""
|
71
72
|
else
|
72
73
|
"--#{key} #{value.inspect}"
|
73
74
|
end
|