1 require 'active_resource'
2 require 'tmpdir'
3 require 'fileutils'
4 require 'rake'
5
6 module Registry
7 class Extension < ActiveResource::Base
8 self.site = ENV['REGISTRY_URL'] || "http://ext.radiantcms.org/"
9
10 def install
11 Registry.const_get(install_type).new(self).install
12 end
13
14 def uninstall
15 Uninstaller.new(self).uninstall
16 end
17
18 def inspect
19 %{
20 Name: #{name}
21 Description:
22 #{description}
23 Author: #{author.name} <#{author.email}>
24 Source code: #{repository_url}
25 Download: #{download_url}
26 Install type: #{install_type}
27 Supports: Radiant #{supports_radiant_version}
28 }.strip
29 end
30 end
31
32 class Action
33 def rake(command)
34 puts "rake #{command}"
35 puts `rake #{command} RAILS_ENV=#{RAILS_ENV}` if tasks_include? command
36 end
37
38 def tasks_include?(command)
39 command = command.split(':')
40 if command.length > 1 && command[0..1] == ['radiant','extensions']
41 extension = command[2]
42 task = "radiant:extensions:#{extension}:#{command[3].split[0]}"
43 else
44 extension = task = command[0]
45 end
46 rake_file = File.join(RAILS_ROOT, 'vendor', 'extensions', extension) + '/lib/tasks/' + extension + '_extension_tasks.rake'
47 load rake_file if File.exist? rake_file
48 tasks = Rake.application.tasks.map(&:name)
49 tasks.include? task
50 end
51
52 def file_utils
53 FileUtils
54 end
55
56 delegate :cd, :cp_r, :rm_r, :to => :file_utils
57 end
58
59 class Installer < Action
60 attr_accessor :url, :path, :name
61 def initialize(url, name)
62 self.url, self.name = url, name
63 end
64
65 def install
66 copy_to_vendor_extensions
67 migrate
68 update
69 end
70
71 def copy_to_vendor_extensions
72 cp_r(self.path, File.expand_path(File.join(RAILS_ROOT, 'vendor', 'extensions', name)))
73 rm_r(self.path)
74 end
75
76 def migrate
77 rake "radiant:extensions:#{name}:migrate"
78 end
79
80 def update
81 rake "radiant:extensions:#{name}:update"
82 end
83 end
84
85 class Uninstaller < Action
86 attr_accessor :name
87 def initialize(extension)
88 self.name = extension.name
89 end
90
91 def uninstall
92 migrate_down
93 remove_extension_directory
94 end
95
96 def migrate_down
97 rake "radiant:extensions:#{name}:migrate VERSION=0"
98 end
99
100 def remove_extension_directory
101 rm_r(File.join(RAILS_ROOT, 'vendor', 'extensions', name))
102 end
103 end
104
105 class Checkout < Installer
106 def initialize(extension)
107 super(extension.repository_url, extension.name)
108 end
109
110 def checkout_command
111 raise "Not Implemented!"
112 end
113
114 def install
115 checkout
116 super
117 end
118
119 def checkout
120 self.path = File.join(Dir.tmpdir, name)
121 cd(Dir.tmpdir) { system "#{checkout_command}" }
122 end
123 end
124
125 class Download < Installer
126 def initialize(extension)
127 super(extension.download_url, extension.name)
128 end
129
130 def install
131 download
132 unpack
133 super
134 end
135
136 def unpack
137 raise "Not Implemented!"
138 end
139
140 def filename
141 File.basename(self.url)
142 end
143
144 def download
145 require 'open-uri'
146 File.open(File.join(Dir.tmpdir, self.filename), 'w') {|f| f.write open(self.url).read }
147 end
148 end
149
150 class Git < Checkout
151 def project_in_git?
152 @in_git ||= File.directory?(".git")
153 end
154
155 def checkout_command
156 "git clone #{url} #{name}"
157 end
158
159 def checkout
160 if project_in_git?
161 system "git submodule add #{url} vendor/extensions/#{name}"
162 cd(File.join('vendor', 'extensions', name)) do
163 system "git submodule init && git submodule update"
164 end
165 else
166 super
167 cd(path) do
168 system "git submodule init && git submodule update"
169 end
170 end
171 end
172
173 def copy_to_vendor_extensions
174 super unless project_in_git?
175 end
176 end
177
178 class Subversion < Checkout
179 def checkout_command
180 "svn checkout #{url} #{name}"
181 end
182 end
183
184 class Gem < Download
185 def gem_name(name)
186 name.gsub(/-\d+\.\d+\.\d+(.+)?\.gem/, '')
187 end
188
189 def download
190 # Don't download the gem if it's already installed
191 extension = gem_name(filename)
192 begin
193 gem extension
194 rescue ::Gem::LoadError
195 super
196 `gem install #{extension}`
197 end
198 end
199
200 def unpack
201 output = nil
202 cd(Dir.tmpdir) do
203 output = `gem unpack #{gem_name(filename)}`
204 end
205 self.path = output.match(/'(.*)'/)[1]
206 end
207 end
208
209 class Tarball < Download
210 def filename
211 "#{self.name}.tar"
212 end
213
214 def unpack
215 output = nil
216 cd(Dir.tmpdir) { output = `tar xvf #{filename}` }
217 self.path = File.join(Dir.tmpdir, output.split(/\n/).first.split('/').first)
218 end
219 end
220
221 class Gzip < Tarball
222 def filename
223 @unpacked ? super : "#{self.name}.tar.gz"
224 end
225
226 def unpack
227 cd(Dir.tmpdir) { system "gunzip #{self.filename}" }
228 @unpacked = true
229 super
230 end
231 end
232
233 class Bzip2 < Tarball
234 def filename
235 @unpacked ? super : "#{self.name}.tar.bz2"
236 end
237
238 def unpack
239 cd(Dir.tmpdir) { system "bunzip2 #{self.filename}" }
240 @unpacked = true
241 super
242 end
243 end
244
245 class Zip < Download
246 def unpack
247 output = nil
248 cd(Dir.tmpdir) { output = `unzip #{filename} -d #{name}` }
249 self.path = File.join(Dir.tmpdir, name)
250 end
251 end
252 end
253
254 module Radiant
255 class Extension
256 module Script
257 class << self
258 def execute(args)
259 command = args.shift || 'help'
260 begin
261 const_get(command.camelize).new(args)
262 rescue ArgumentError => e
263 puts e.message
264 Help.new [command]
265 end
266 end
267 end
268
269 module Util
270 attr_accessor :extension_name, :extension
271
272 def to_extension_name(string)
273 string.to_s.underscore
274 end
275
276 def installed?
277 path_match = Regexp.compile("(^|/|\\\\)#{extension_name}$")
278 extension_paths.any? {|p| p =~ path_match }
279 end
280
281 def registered?
282 self.extension
283 end
284
285 def extension_paths
286 paths = [RAILS_ROOT, RADIANT_ROOT].uniq.map { |p| Dir["#{p}/vendor/extensions/*"] }
287 paths.unshift Dir["#{RADIANT_ROOT}/test/fixtures/extensions/*"] if RAILS_ENV == 'test' #nasty
288 paths.flatten
289 end
290
291 def load_extensions
292 Registry::Extension.find(:all)
293 end
294
295 def find_extension
296 self.extension = load_extensions.find{|e| e.name == self.extension_name }
297 end
298 end
299
300 class Install
301 include Util
302
303 def initialize(args=[])
304 raise ArgumentError, "You must specify an extension to install." if args.blank?
305 self.extension_name = to_extension_name(args.shift)
306 if installed?
307 puts "#{extension_name} is already installed."
308 else
309 find_extension
310 if registered?
311 extension.install
312 else
313 raise ArgumentError, "#{extension_name} is not available in the registry."
314 end
315 end
316 end
317 end
318
319 class Uninstall
320 include Util
321
322 def initialize(args=[])
323 raise ArgumentError, "You must specify an extension to uninstall." if args.blank?
324 self.extension_name = to_extension_name(args.shift)
325 if installed?
326 find_extension && extension.uninstall
327 else
328 puts "#{extension_name} is not installed."
329 end
330 end
331 end
332
333 class Info
334 include Util
335
336 def initialize(args=[])
337 raise ArgumentError, "You must specify an extension to get info on" if args.blank?
338 self.extension_name = to_extension_name(args.shift)
339 find_extension and puts extension.inspect
340 end
341 end
342
343 class Help
344 def initialize(args=[])
345 command = args.shift
346 command = 'help' unless self.class.instance_methods(false).collect {|im| im.to_s}.include?(command.to_s)
347 send(command)
348 end
349
350 def help
351 $stdout.puts %{Usage: script/extension command [arguments]
352
353 Available commands:
354 #{command_names}
355
356 For help on an individual command:
357 script/extension help command
358
359 You may install extensions from another registry by setting the REGISTRY_URL
360 By default the REGISTRY_URL is set to http://ext.radiantcms.org
361
362 Code for the registry application may be found at:
363 http://github.com/radiant/radiant-extension-registry/
364 }
365 end
366
367 def install
368 $stdout.puts %{Usage: script/extension install extension_name
369
370 Installs an extension from information in the global registry.
371 }
372 end
373
374 def uninstall
375 $stdout.puts %{Usage: script/extension uninstall extension_name
376
377 Removes a previously installed extension from the current project.
378 }
379 end
380
381 def info
382 $stdout.puts %{Usage: script/extension info extension_name
383
384 Displays registry information about the extension.
385 }
386 end
387
388 private
389 def command_names
390 (Radiant::Extension::Script.constants - ['Util']).sort.map {|n| n.to_s.underscore }.join(", ")
391 end
392 end
393 end
394 end
395 end