public
Fork of wycats/thor
Description: A scripting framework that replaces rake and sake
Homepage: http://www.yehudakatz.com
Clone URL: git://github.com/halorgium/thor.git
Search Repo:
Made the remote installation protocol much more robust; Defaults no-op to 
"help"; Improve usage printing;
wycats (author)
Mon May 12 18:19:18 -0700 2008
commit  ead58597a95723a8261065aba7dc8f0c23a7125f
tree    c0f259a735965516982fb580634e4cfcdabea328
parent  2f9416611fdecdbf76dabe9c0efb88563b3cc987
...
22
23
24
25
26
27
28
 
29
30
31
...
22
23
24
 
 
 
 
25
26
27
28
0
@@ -22,10 +22,7 @@ spec = Gem::Specification.new do |s|
0
   s.author = AUTHOR
0
   s.email = EMAIL
0
   s.homepage = HOMEPAGE
0
-
0
- # Uncomment this to add a dependency
0
- # s.add_dependency "foo"
0
-
0
+
0
   s.require_path = 'lib'
0
   s.autorequire = GEM
0
   s.bindir = "bin"
...
3
4
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
7
 
8
9
10
...
15
16
17
18
19
20
21
22
23
 
 
 
 
 
 
 
 
 
 
24
25
26
...
44
45
46
47
48
 
 
49
50
51
52
53
54
 
 
 
55
56
57
58
59
 
60
61
 
62
63
 
 
 
 
 
 
 
 
 
64
65
66
 
 
 
 
 
 
 
 
 
 
 
67
68
69
70
71
 
72
73
 
74
75
76
77
 
78
79
80
...
83
84
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
87
88
89
90
 
91
92
93
94
95
 
96
97
98
99
100
 
101
102
103
 
104
105
106
107
108
109
110
...
113
114
115
116
 
117
118
119
...
122
123
124
125
 
 
 
 
 
 
 
 
 
 
 
 
126
127
 
128
129
 
130
131
132
133
134
135
136
137
 
 
138
139
140
...
145
146
147
148
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
151
152
...
188
189
190
191
 
192
193
194
...
199
200
201
202
 
203
204
205
...
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
32
33
34
 
 
35
36
37
 
38
39
40
41
42
43
44
45
46
47
48
49
50
...
68
69
70
 
 
71
72
73
74
75
76
 
 
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
 
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
 
117
118
 
119
120
 
 
 
121
122
123
124
...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
 
 
 
150
151
152
153
154
 
155
156
157
158
159
 
160
161
162
 
163
164
165
166
 
167
168
169
...
172
173
174
 
175
176
177
178
...
181
182
183
 
184
185
186
187
188
189
190
191
192
193
194
195
196
 
197
198
 
199
200
201
202
203
204
205
 
 
206
207
208
209
210
...
215
216
217
 
 
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
298
299
300
 
301
302
303
304
...
309
310
311
 
312
313
314
315
0
@@ -3,8 +3,25 @@ require "open-uri"
0
 require "fileutils"
0
 require "yaml"
0
 require "digest/md5"
0
+require "readline"
0
+
0
+module ObjectSpace
0
+
0
+ class << self
0
+
0
+ # ==== Returns
0
+ # Array[Class]:: All the classes in the object space.
0
+ def classes
0
+ klasses = []
0
+ ObjectSpace.each_object(Class) {|o| klasses << o}
0
+ klasses
0
+ end
0
+ end
0
+
0
+end
0
 
0
 class Thor::Util
0
+
0
   # @public
0
   def self.constant_to_thor_path(str)
0
     snake_case(str).squeeze(":")
0
@@ -15,12 +32,19 @@ class Thor::Util
0
     make_constant(to_constant(str))
0
   end
0
 
0
- private
0
- # @private
0
   def self.to_constant(str)
0
     str.gsub(/:(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
0
   end
0
-
0
+
0
+ def self.constants_in_contents(str)
0
+ klasses = self.constants.dup
0
+ eval(str)
0
+ ret = self.constants - klasses
0
+ ret.each {|k| self.send(:remove_const, k)}
0
+ ret
0
+ end
0
+
0
+ private
0
   # @private
0
   def self.make_constant(str)
0
     list = str.split("::")
0
@@ -44,37 +68,57 @@ class Thor::Runner < Thor
0
     ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
0
   end
0
 
0
- def initialize_thorfiles
0
- thorfiles.each {|f| load f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
0
+ def initialize_thorfiles(include_system = true)
0
+ thorfiles(include_system).each {|f| load f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
0
   end
0
 
0
   map "-T" => :list, "-i" => :install, "-u" => :update
0
   
0
- desc "install NAME [AS]", "install a Thor file into your system tasks, optionally named for future updates"
0
- def install(name, as = name)
0
+ desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates"
0
+ method_options :as => :optional
0
+ def install(name, opts)
0
     initialize_thorfiles
0
     begin
0
       contents = open(name).read
0
     rescue OpenURI::HTTPError
0
       puts "The URI you provided: `#{name}' was invalid"
0
+ return
0
     rescue Errno::ENOENT
0
       puts "`#{name}' is not a valid file"
0
+ return
0
     end
0
     
0
+ puts "Your Thorfile contains: "
0
+ puts contents
0
+ print "Do you wish to continue [y/N]? "
0
+ response = Readline.readline
0
+
0
+ return unless response =~ /^\s*y/i
0
+
0
+ constants = Thor::Util.constants_in_contents(contents)
0
+
0
     name = name =~ /\.thor$/ ? name : "#{name}.thor"
0
     
0
- thor_root = File.join(ENV["HOME"], ".thor")
0
+ as = opts["as"] || begin
0
+ first_line = contents.split("\n")[0]
0
+ (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
0
+ end
0
+
0
+ if !as
0
+ print "Please specify a name for #{name} in the system repository [#{name}]: "
0
+ as = Readline.readline
0
+ as = name if as.empty?
0
+ end
0
+
0
     FileUtils.mkdir_p thor_root
0
     
0
     yaml_file = File.join(thor_root, "thor.yml")
0
     FileUtils.touch(yaml_file)
0
- yaml = YAML.load_file(yaml_file) || {}
0
+ yaml = thor_yaml
0
     
0
- yaml[as] = {:filename => Digest::MD5.hexdigest(name), :location => name}
0
+ yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => name, :constants => constants}
0
     
0
- File.open(yaml_file, "w") do |file|
0
- file.puts yaml.to_yaml
0
- end
0
+ save_yaml(yaml)
0
     
0
     puts "Storing thor file in your system repository"
0
     
0
@@ -83,28 +127,43 @@ class Thor::Runner < Thor
0
     end
0
   end
0
   
0
+ desc "uninstall NAME", "uninstall a named Thor module"
0
+ def uninstall(name)
0
+ yaml = thor_yaml
0
+ unless yaml[name]
0
+ puts "There was no module by that name installed"
0
+ return
0
+ end
0
+
0
+ puts "Uninstalling #{name}."
0
+
0
+ file = File.join(thor_root, "#{yaml[name][:filename]}.thor")
0
+ File.delete(file)
0
+ yaml.delete(name)
0
+ save_yaml(yaml)
0
+
0
+ puts "Done."
0
+ end
0
+
0
   desc "update NAME", "update a Thor file from its original location"
0
   def update(name)
0
- thor_root = File.join(ENV["HOME"], ".thor")
0
- yaml_file = File.join(thor_root, "thor.yml")
0
- yaml = YAML.load_file(yaml_file) || {}
0
+ yaml = thor_yaml
0
     if !yaml[name] || !yaml[name][:location]
0
       puts "`#{name}' was not found in the system repository"
0
     else
0
       puts "Updating `#{name}' from #{yaml[name][:location]}"
0
- install(yaml[name][:location], name)
0
+ install(yaml[name][:location], "as" => name)
0
     end
0
   end
0
   
0
   def installed
0
- Dir["#{ENV["HOME"]}/.thor/**/*.thor"].each do |f|
0
+ Dir["#{ENV["HOME"]}/.thor/**/*.thor"].each do |f|
0
       load f unless Thor.subclass_files.keys.include?(File.expand_path(f))
0
     end
0
- display_klasses
0
+ display_klasses(true)
0
   end
0
   
0
   desc "list [SEARCH]", "list the available thor tasks"
0
- method_options :force => :boolean
0
   def list(search = "")
0
     initialize_thorfiles
0
     search = /.*#{search}.*/
0
@@ -113,7 +172,7 @@ class Thor::Runner < Thor
0
   end
0
     
0
   def method_missing(meth, *args)
0
- initialize_thorfiles
0
+ initialize_thorfiles(false)
0
     meth = meth.to_s
0
     unless meth =~ /:/
0
       puts "Thor tasks must contain a :"
0
@@ -122,19 +181,30 @@ class Thor::Runner < Thor
0
     
0
     thor_klass = meth.split(":")[0...-1].join(":")
0
     to_call = meth.split(":").last
0
- begin
0
+
0
+ thor_root = File.join(ENV["HOME"], ".thor")
0
+ yaml_file = File.join(thor_root, "thor.yml")
0
+ yaml = YAML.load_file(yaml_file) || {}
0
+
0
+ klass_str = Thor::Util.to_constant(thor_klass)
0
+ files = yaml.inject([]) { |a,(k,v)| a << v[:filename] if v[:constants].include?(klass_str); a }
0
+
0
+ unless files.empty?
0
+ files.each do |f|
0
+ load File.join(thor_root, "#{f}.thor")
0
+ end
0
       klass = Thor::Util.constant_from_thor_path(thor_klass)
0
- rescue NameError
0
+ else
0
       puts "There was no available namespace `#{thor_klass}'."
0
- return
0
+ return
0
     end
0
     
0
     unless klass.ancestors.include?(Thor)
0
       puts "`#{thor_klass}' is not a Thor module"
0
       return
0
     end
0
-
0
- ARGV.replace [to_call, *args].compact
0
+
0
+ ARGV.replace [to_call, *(args + ARGV)].compact
0
     begin
0
       klass.start
0
     rescue ArgumentError
0
@@ -145,8 +215,48 @@ class Thor::Runner < Thor
0
   end
0
   
0
   private
0
- def display_klasses
0
- klasses = Thor.subclasses.reject {|x| x == Thor::Runner}
0
+ def thor_root
0
+ File.join(ENV["HOME"], ".thor")
0
+ end
0
+
0
+ def thor_yaml
0
+ yaml_file = File.join(thor_root, "thor.yml")
0
+ YAML.load_file(yaml_file) || {}
0
+ end
0
+
0
+ def save_yaml(yaml)
0
+ yaml_file = File.join(thor_root, "thor.yml")
0
+ File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml }
0
+ end
0
+
0
+ def display_klasses(with_modules = false)
0
+ klasses = Thor.subclasses - [Thor::Runner]
0
+
0
+ if klasses.empty?
0
+ puts "No thorfiles available"
0
+ return
0
+ end
0
+
0
+ if with_modules
0
+ yaml = thor_yaml
0
+ max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size
0
+
0
+ print "%-#{max_name + 4}s" % "Name"
0
+ puts "Modules"
0
+ print "%-#{max_name + 4}s" % "----"
0
+ puts "-------"
0
+
0
+ yaml.each do |name, info|
0
+ print "%-#{max_name + 4}s" % name
0
+ puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ")
0
+ end
0
+
0
+ puts
0
+ end
0
+
0
+ puts "Tasks"
0
+ puts "-----"
0
+
0
     # Calculate the largest base class name
0
     max_base = klasses.max do |x,y|
0
       Thor::Util.constant_to_thor_path(x.name).size <=> Thor::Util.constant_to_thor_path(y.name).size
0
@@ -188,7 +298,7 @@ class Thor::Runner < Thor
0
     end.join(" ")
0
   end
0
   
0
- def thorfiles
0
+ def thorfiles(include_system = true)
0
     path = Dir.pwd
0
     system_thorfiles = Dir["#{ENV["HOME"]}/.thor/**/*.thor"]
0
     thorfiles = []
0
@@ -199,7 +309,7 @@ class Thor::Runner < Thor
0
       path = File.dirname(path)
0
       break if path == "/"
0
     end
0
- thorfiles + system_thorfiles
0
+ thorfiles + (include_system ? system_thorfiles : [])
0
   end
0
     
0
 end
...
1
 
 
2
3
4
...
73
74
75
76
 
77
78
79
...
96
97
98
 
 
99
100
101
102
103
 
 
 
104
105
106
107
108
 
109
110
111
...
124
125
126
127
 
128
129
 
130
131
132
...
 
1
2
3
4
5
...
74
75
76
 
77
78
79
80
...
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
...
131
132
133
 
134
135
 
136
137
138
139
0
@@ -1,4 +1,5 @@
0
-require "#{File.dirname(__FILE__)}/getopt"
0
+$:.unshift File.expand_path(File.dirname(__FILE__))
0
+require "getopt"
0
 
0
 class Thor
0
   def self.inherited(klass)
0
@@ -73,7 +74,7 @@ class Thor
0
   end
0
   
0
   def self.format_opts(opts)
0
- return "" unless opts
0
+ return "" if !opts
0
     opts.map do |opt, val|
0
       if val == true || val == "BOOLEAN"
0
         "[#{opt}]"
0
@@ -96,16 +97,22 @@ class Thor
0
       meth = @map[meth].to_s
0
     end
0
     
0
+ args = ARGV.dup
0
+
0
     if @opts.assoc(meth)
0
       opts = @opts.assoc(meth).last.map {|opt, val| [opt, val == true ? Getopt::BOOLEAN : Getopt.const_get(val)].flatten}
0
       options = Getopt::Long.getopts(*opts)
0
       params << options
0
     end
0
+
0
+ ARGV.replace args
0
+
0
     new(meth, params).instance_variable_get("@results")
0
   end
0
 
0
   def initialize(op, params)
0
     begin
0
+ op ||= "help"
0
       @results = send(op.to_sym, *params) if public_methods.include?(op) || !methods.include?(op)
0
     rescue ArgumentError
0
       puts "`#{op}' was called incorrectly. Call as `#{usage(op)}'"
0
@@ -124,9 +131,9 @@ class Thor
0
     list = self.class.help_list
0
     puts "Options"
0
     puts "-------"
0
- list.usages.each do |meth, usage|
0
+ list.usages.each do |meth, use|
0
       format = "%-" + (list.max.usage + list.max.opt + 4).to_s + "s"
0
- print format % (usage)
0
+ print format % ("#{usage(meth)}")
0
       puts list.descriptions.assoc(meth)[1]
0
     end
0
   end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0
@@ -0,0 +1,15 @@
0
+# module: random
0
+
0
+class Amazing < Thor
0
+ desc "describe NAME", "say that someone is amazing"
0
+ method_options :forcefully => :boolean
0
+ def describe(name, opts)
0
+ ret = "#{name} is amazing"
0
+ puts opts["forcefully"] ? ret.upcase : ret
0
+ end
0
+
0
+ desc "hello", "say hello"
0
+ def hello
0
+ puts "Hello"
0
+ end
0
+end

Comments

    No one has commented yet.