8589b1698d0b169b570d374f990a44ca53efc8c9
[feedcatcher.git] / vendor / rails / railties / lib / commands / plugin.rb
1 # Rails Plugin Manager.
2 #
3 # Listing available plugins:
4 #
5 # $ ./script/plugin list
6 # continuous_builder http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder
7 # asset_timestamping http://svn.aviditybytes.com/rails/plugins/asset_timestamping
8 # enumerations_mixin http://svn.protocool.com/rails/plugins/enumerations_mixin/trunk
9 # calculations http://techno-weenie.net/svn/projects/calculations/
10 # ...
11 #
12 # Installing plugins:
13 #
14 # $ ./script/plugin install continuous_builder asset_timestamping
15 #
16 # Finding Repositories:
17 #
18 # $ ./script/plugin discover
19 #
20 # Adding Repositories:
21 #
22 # $ ./script/plugin source http://svn.protocool.com/rails/plugins/
23 #
24 # How it works:
25 #
26 # * Maintains a list of subversion repositories that are assumed to have
27 # a plugin directory structure. Manage them with the (source, unsource,
28 # and sources commands)
29 #
30 # * The discover command scrapes the following page for things that
31 # look like subversion repositories with plugins:
32 # http://wiki.rubyonrails.org/rails/pages/Plugins
33 #
34 # * Unless you specify that you want to use svn, script/plugin uses plain old
35 # HTTP for downloads. The following bullets are true if you specify
36 # that you want to use svn.
37 #
38 # * If `vendor/plugins` is under subversion control, the script will
39 # modify the svn:externals property and perform an update. You can
40 # use normal subversion commands to keep the plugins up to date.
41 #
42 # * Or, if `vendor/plugins` is not under subversion control, the
43 # plugin is pulled via `svn checkout` or `svn export` but looks
44 # exactly the same.
45 #
46 # Specifying revisions:
47 #
48 # * Subversion revision is a single integer.
49 #
50 # * Git revision format:
51 # - full - 'refs/tags/1.8.0' or 'refs/heads/experimental'
52 # - short: 'experimental' (equivalent to 'refs/heads/experimental')
53 # 'tag 1.8.0' (equivalent to 'refs/tags/1.8.0')
54 #
55 #
56 # This is Free Software, copyright 2005 by Ryan Tomayko (rtomayko@gmail.com)
57 # and is licensed MIT: (http://www.opensource.org/licenses/mit-license.php)
58
59 $verbose = false
60
61
62 require 'open-uri'
63 require 'fileutils'
64 require 'tempfile'
65
66 include FileUtils
67
68 class RailsEnvironment
69 attr_reader :root
70
71 def initialize(dir)
72 @root = dir
73 end
74
75 def self.find(dir=nil)
76 dir ||= pwd
77 while dir.length > 1
78 return new(dir) if File.exist?(File.join(dir, 'config', 'environment.rb'))
79 dir = File.dirname(dir)
80 end
81 end
82
83 def self.default
84 @default ||= find
85 end
86
87 def self.default=(rails_env)
88 @default = rails_env
89 end
90
91 def install(name_uri_or_plugin)
92 if name_uri_or_plugin.is_a? String
93 if name_uri_or_plugin =~ /:\/\//
94 plugin = Plugin.new(name_uri_or_plugin)
95 else
96 plugin = Plugins[name_uri_or_plugin]
97 end
98 else
99 plugin = name_uri_or_plugin
100 end
101 unless plugin.nil?
102 plugin.install
103 else
104 puts "Plugin not found: #{name_uri_or_plugin}"
105 end
106 end
107
108 def use_svn?
109 require 'active_support/core_ext/kernel'
110 silence_stderr {`svn --version` rescue nil}
111 !$?.nil? && $?.success?
112 end
113
114 def use_externals?
115 use_svn? && File.directory?("#{root}/vendor/plugins/.svn")
116 end
117
118 def use_checkout?
119 # this is a bit of a guess. we assume that if the rails environment
120 # is under subversion then they probably want the plugin checked out
121 # instead of exported. This can be overridden on the command line
122 File.directory?("#{root}/.svn")
123 end
124
125 def best_install_method
126 return :http unless use_svn?
127 case
128 when use_externals? then :externals
129 when use_checkout? then :checkout
130 else :export
131 end
132 end
133
134 def externals
135 return [] unless use_externals?
136 ext = `svn propget svn:externals "#{root}/vendor/plugins"`
137 lines = ext.respond_to?(:lines) ? ext.lines : ext
138 lines.reject{ |line| line.strip == '' }.map do |line|
139 line.strip.split(/\s+/, 2)
140 end
141 end
142
143 def externals=(items)
144 unless items.is_a? String
145 items = items.map{|name,uri| "#{name.ljust(29)} #{uri.chomp('/')}"}.join("\n")
146 end
147 Tempfile.open("svn-set-prop") do |file|
148 file.write(items)
149 file.flush
150 system("svn propset -q svn:externals -F \"#{file.path}\" \"#{root}/vendor/plugins\"")
151 end
152 end
153
154 end
155
156 class Plugin
157 attr_reader :name, :uri
158
159 def initialize(uri, name=nil)
160 @uri = uri
161 guess_name(uri)
162 end
163
164 def self.find(name)
165 name =~ /\// ? new(name) : Repositories.instance.find_plugin(name)
166 end
167
168 def to_s
169 "#{@name.ljust(30)}#{@uri}"
170 end
171
172 def svn_url?
173 @uri =~ /svn(?:\+ssh)?:\/\/*/
174 end
175
176 def git_url?
177 @uri =~ /^git:\/\// || @uri =~ /\.git$/
178 end
179
180 def installed?
181 File.directory?("#{rails_env.root}/vendor/plugins/#{name}") \
182 or rails_env.externals.detect{ |name, repo| self.uri == repo }
183 end
184
185 def install(method=nil, options = {})
186 method ||= rails_env.best_install_method?
187 if :http == method
188 method = :export if svn_url?
189 method = :git if git_url?
190 end
191
192 uninstall if installed? and options[:force]
193
194 unless installed?
195 send("install_using_#{method}", options)
196 run_install_hook
197 else
198 puts "already installed: #{name} (#{uri}). pass --force to reinstall"
199 end
200 end
201
202 def uninstall
203 path = "#{rails_env.root}/vendor/plugins/#{name}"
204 if File.directory?(path)
205 puts "Removing 'vendor/plugins/#{name}'" if $verbose
206 run_uninstall_hook
207 rm_r path
208 else
209 puts "Plugin doesn't exist: #{path}"
210 end
211 # clean up svn:externals
212 externals = rails_env.externals
213 externals.reject!{|n,u| name == n or name == u}
214 rails_env.externals = externals
215 end
216
217 def info
218 tmp = "#{rails_env.root}/_tmp_about.yml"
219 if svn_url?
220 cmd = "svn export #{@uri} \"#{rails_env.root}/#{tmp}\""
221 puts cmd if $verbose
222 system(cmd)
223 end
224 open(svn_url? ? tmp : File.join(@uri, 'about.yml')) do |stream|
225 stream.read
226 end rescue "No about.yml found in #{uri}"
227 ensure
228 FileUtils.rm_rf tmp if svn_url?
229 end
230
231 private
232
233 def run_install_hook
234 install_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/install.rb"
235 load install_hook_file if File.exist? install_hook_file
236 end
237
238 def run_uninstall_hook
239 uninstall_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/uninstall.rb"
240 load uninstall_hook_file if File.exist? uninstall_hook_file
241 end
242
243 def install_using_export(options = {})
244 svn_command :export, options
245 end
246
247 def install_using_checkout(options = {})
248 svn_command :checkout, options
249 end
250
251 def install_using_externals(options = {})
252 externals = rails_env.externals
253 externals.push([@name, uri])
254 rails_env.externals = externals
255 install_using_checkout(options)
256 end
257
258 def install_using_http(options = {})
259 root = rails_env.root
260 mkdir_p "#{root}/vendor/plugins/#{@name}"
261 Dir.chdir "#{root}/vendor/plugins/#{@name}" do
262 puts "fetching from '#{uri}'" if $verbose
263 fetcher = RecursiveHTTPFetcher.new(uri, -1)
264 fetcher.quiet = true if options[:quiet]
265 fetcher.fetch
266 end
267 end
268
269 def install_using_git(options = {})
270 root = rails_env.root
271 install_path = mkdir_p "#{root}/vendor/plugins/#{name}"
272 Dir.chdir install_path do
273 init_cmd = "git init"
274 init_cmd += " -q" if options[:quiet] and not $verbose
275 puts init_cmd if $verbose
276 system(init_cmd)
277 base_cmd = "git pull --depth 1 #{uri}"
278 base_cmd += " -q" if options[:quiet] and not $verbose
279 base_cmd += " #{options[:revision]}" if options[:revision]
280 puts base_cmd if $verbose
281 if system(base_cmd)
282 puts "removing: .git .gitignore" if $verbose
283 rm_rf %w(.git .gitignore)
284 else
285 rm_rf install_path
286 end
287 end
288 end
289
290 def svn_command(cmd, options = {})
291 root = rails_env.root
292 mkdir_p "#{root}/vendor/plugins"
293 base_cmd = "svn #{cmd} #{uri} \"#{root}/vendor/plugins/#{name}\""
294 base_cmd += ' -q' if options[:quiet] and not $verbose
295 base_cmd += " -r #{options[:revision]}" if options[:revision]
296 puts base_cmd if $verbose
297 system(base_cmd)
298 end
299
300 def guess_name(url)
301 @name = File.basename(url)
302 if @name == 'trunk' || @name.empty?
303 @name = File.basename(File.dirname(url))
304 end
305 @name.gsub!(/\.git$/, '') if @name =~ /\.git$/
306 end
307
308 def rails_env
309 @rails_env || RailsEnvironment.default
310 end
311 end
312
313 class Repositories
314 include Enumerable
315
316 def initialize(cache_file = File.join(find_home, ".rails-plugin-sources"))
317 @cache_file = File.expand_path(cache_file)
318 load!
319 end
320
321 def each(&block)
322 @repositories.each(&block)
323 end
324
325 def add(uri)
326 unless find{|repo| repo.uri == uri }
327 @repositories.push(Repository.new(uri)).last
328 end
329 end
330
331 def remove(uri)
332 @repositories.reject!{|repo| repo.uri == uri}
333 end
334
335 def exist?(uri)
336 @repositories.detect{|repo| repo.uri == uri }
337 end
338
339 def all
340 @repositories
341 end
342
343 def find_plugin(name)
344 @repositories.each do |repo|
345 repo.each do |plugin|
346 return plugin if plugin.name == name
347 end
348 end
349 return nil
350 end
351
352 def load!
353 contents = File.exist?(@cache_file) ? File.read(@cache_file) : defaults
354 contents = defaults if contents.empty?
355 @repositories = contents.split(/\n/).reject do |line|
356 line =~ /^\s*#/ or line =~ /^\s*$/
357 end.map { |source| Repository.new(source.strip) }
358 end
359
360 def save
361 File.open(@cache_file, 'w') do |f|
362 each do |repo|
363 f.write(repo.uri)
364 f.write("\n")
365 end
366 end
367 end
368
369 def defaults
370 <<-DEFAULTS
371 http://dev.rubyonrails.com/svn/rails/plugins/
372 DEFAULTS
373 end
374
375 def find_home
376 ['HOME', 'USERPROFILE'].each do |homekey|
377 return ENV[homekey] if ENV[homekey]
378 end
379 if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
380 return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
381 end
382 begin
383 File.expand_path("~")
384 rescue StandardError => ex
385 if File::ALT_SEPARATOR
386 "C:/"
387 else
388 "/"
389 end
390 end
391 end
392
393 def self.instance
394 @instance ||= Repositories.new
395 end
396
397 def self.each(&block)
398 self.instance.each(&block)
399 end
400 end
401
402 class Repository
403 include Enumerable
404 attr_reader :uri, :plugins
405
406 def initialize(uri)
407 @uri = uri.chomp('/') << "/"
408 @plugins = nil
409 end
410
411 def plugins
412 unless @plugins
413 if $verbose
414 puts "Discovering plugins in #{@uri}"
415 puts index
416 end
417
418 @plugins = index.reject{ |line| line !~ /\/$/ }
419 @plugins.map! { |name| Plugin.new(File.join(@uri, name), name) }
420 end
421
422 @plugins
423 end
424
425 def each(&block)
426 plugins.each(&block)
427 end
428
429 private
430 def index
431 @index ||= RecursiveHTTPFetcher.new(@uri).ls
432 end
433 end
434
435
436 # load default environment and parse arguments
437 require 'optparse'
438 module Commands
439
440 class Plugin
441 attr_reader :environment, :script_name, :sources
442 def initialize
443 @environment = RailsEnvironment.default
444 @rails_root = RailsEnvironment.default.root
445 @script_name = File.basename($0)
446 @sources = []
447 end
448
449 def environment=(value)
450 @environment = value
451 RailsEnvironment.default = value
452 end
453
454 def options
455 OptionParser.new do |o|
456 o.set_summary_indent(' ')
457 o.banner = "Usage: #{@script_name} [OPTIONS] command"
458 o.define_head "Rails plugin manager."
459
460 o.separator ""
461 o.separator "GENERAL OPTIONS"
462
463 o.on("-r", "--root=DIR", String,
464 "Set an explicit rails app directory.",
465 "Default: #{@rails_root}") { |rails_root| @rails_root = rails_root; self.environment = RailsEnvironment.new(@rails_root) }
466 o.on("-s", "--source=URL1,URL2", Array,
467 "Use the specified plugin repositories instead of the defaults.") { |sources| @sources = sources}
468
469 o.on("-v", "--verbose", "Turn on verbose output.") { |verbose| $verbose = verbose }
470 o.on("-h", "--help", "Show this help message.") { puts o; exit }
471
472 o.separator ""
473 o.separator "COMMANDS"
474
475 o.separator " discover Discover plugin repositories."
476 o.separator " list List available plugins."
477 o.separator " install Install plugin(s) from known repositories or URLs."
478 o.separator " update Update installed plugins."
479 o.separator " remove Uninstall plugins."
480 o.separator " source Add a plugin source repository."
481 o.separator " unsource Remove a plugin repository."
482 o.separator " sources List currently configured plugin repositories."
483
484 o.separator ""
485 o.separator "EXAMPLES"
486 o.separator " Install a plugin:"
487 o.separator " #{@script_name} install continuous_builder\n"
488 o.separator " Install a plugin from a subversion URL:"
489 o.separator " #{@script_name} install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n"
490 o.separator " Install a plugin from a git URL:"
491 o.separator " #{@script_name} install git://github.com/SomeGuy/my_awesome_plugin.git\n"
492 o.separator " Install a plugin and add a svn:externals entry to vendor/plugins"
493 o.separator " #{@script_name} install -x continuous_builder\n"
494 o.separator " List all available plugins:"
495 o.separator " #{@script_name} list\n"
496 o.separator " List plugins in the specified repository:"
497 o.separator " #{@script_name} list --source=http://dev.rubyonrails.com/svn/rails/plugins/\n"
498 o.separator " Discover and prompt to add new repositories:"
499 o.separator " #{@script_name} discover\n"
500 o.separator " Discover new repositories but just list them, don't add anything:"
501 o.separator " #{@script_name} discover -l\n"
502 o.separator " Add a new repository to the source list:"
503 o.separator " #{@script_name} source http://dev.rubyonrails.com/svn/rails/plugins/\n"
504 o.separator " Remove a repository from the source list:"
505 o.separator " #{@script_name} unsource http://dev.rubyonrails.com/svn/rails/plugins/\n"
506 o.separator " Show currently configured repositories:"
507 o.separator " #{@script_name} sources\n"
508 end
509 end
510
511 def parse!(args=ARGV)
512 general, sub = split_args(args)
513 options.parse!(general)
514
515 command = general.shift
516 if command =~ /^(list|discover|install|source|unsource|sources|remove|update|info)$/
517 command = Commands.const_get(command.capitalize).new(self)
518 command.parse!(sub)
519 else
520 puts "Unknown command: #{command}"
521 puts options
522 exit 1
523 end
524 end
525
526 def split_args(args)
527 left = []
528 left << args.shift while args[0] and args[0] =~ /^-/
529 left << args.shift if args[0]
530 return [left, args]
531 end
532
533 def self.parse!(args=ARGV)
534 Plugin.new.parse!(args)
535 end
536 end
537
538
539 class List
540 def initialize(base_command)
541 @base_command = base_command
542 @sources = []
543 @local = false
544 @remote = true
545 end
546
547 def options
548 OptionParser.new do |o|
549 o.set_summary_indent(' ')
550 o.banner = "Usage: #{@base_command.script_name} list [OPTIONS] [PATTERN]"
551 o.define_head "List available plugins."
552 o.separator ""
553 o.separator "Options:"
554 o.separator ""
555 o.on( "-s", "--source=URL1,URL2", Array,
556 "Use the specified plugin repositories.") {|sources| @sources = sources}
557 o.on( "--local",
558 "List locally installed plugins.") {|local| @local, @remote = local, false}
559 o.on( "--remote",
560 "List remotely available plugins. This is the default behavior",
561 "unless --local is provided.") {|remote| @remote = remote}
562 end
563 end
564
565 def parse!(args)
566 options.order!(args)
567 unless @sources.empty?
568 @sources.map!{ |uri| Repository.new(uri) }
569 else
570 @sources = Repositories.instance.all
571 end
572 if @remote
573 @sources.map{|r| r.plugins}.flatten.each do |plugin|
574 if @local or !plugin.installed?
575 puts plugin.to_s
576 end
577 end
578 else
579 cd "#{@base_command.environment.root}/vendor/plugins"
580 Dir["*"].select{|p| File.directory?(p)}.each do |name|
581 puts name
582 end
583 end
584 end
585 end
586
587
588 class Sources
589 def initialize(base_command)
590 @base_command = base_command
591 end
592
593 def options
594 OptionParser.new do |o|
595 o.set_summary_indent(' ')
596 o.banner = "Usage: #{@base_command.script_name} sources [OPTIONS] [PATTERN]"
597 o.define_head "List configured plugin repositories."
598 o.separator ""
599 o.separator "Options:"
600 o.separator ""
601 o.on( "-c", "--check",
602 "Report status of repository.") { |sources| @sources = sources}
603 end
604 end
605
606 def parse!(args)
607 options.parse!(args)
608 Repositories.each do |repo|
609 puts repo.uri
610 end
611 end
612 end
613
614
615 class Source
616 def initialize(base_command)
617 @base_command = base_command
618 end
619
620 def options
621 OptionParser.new do |o|
622 o.set_summary_indent(' ')
623 o.banner = "Usage: #{@base_command.script_name} source REPOSITORY [REPOSITORY [REPOSITORY]...]"
624 o.define_head "Add new repositories to the default search list."
625 end
626 end
627
628 def parse!(args)
629 options.parse!(args)
630 count = 0
631 args.each do |uri|
632 if Repositories.instance.add(uri)
633 puts "added: #{uri.ljust(50)}" if $verbose
634 count += 1
635 else
636 puts "failed: #{uri.ljust(50)}"
637 end
638 end
639 Repositories.instance.save
640 puts "Added #{count} repositories."
641 end
642 end
643
644
645 class Unsource
646 def initialize(base_command)
647 @base_command = base_command
648 end
649
650 def options
651 OptionParser.new do |o|
652 o.set_summary_indent(' ')
653 o.banner = "Usage: #{@base_command.script_name} unsource URI [URI [URI]...]"
654 o.define_head "Remove repositories from the default search list."
655 o.separator ""
656 o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
657 end
658 end
659
660 def parse!(args)
661 options.parse!(args)
662 count = 0
663 args.each do |uri|
664 if Repositories.instance.remove(uri)
665 count += 1
666 puts "removed: #{uri.ljust(50)}"
667 else
668 puts "failed: #{uri.ljust(50)}"
669 end
670 end
671 Repositories.instance.save
672 puts "Removed #{count} repositories."
673 end
674 end
675
676
677 class Discover
678 def initialize(base_command)
679 @base_command = base_command
680 @list = false
681 @prompt = true
682 end
683
684 def options
685 OptionParser.new do |o|
686 o.set_summary_indent(' ')
687 o.banner = "Usage: #{@base_command.script_name} discover URI [URI [URI]...]"
688 o.define_head "Discover repositories referenced on a page."
689 o.separator ""
690 o.separator "Options:"
691 o.separator ""
692 o.on( "-l", "--list",
693 "List but don't prompt or add discovered repositories.") { |list| @list, @prompt = list, !@list }
694 o.on( "-n", "--no-prompt",
695 "Add all new repositories without prompting.") { |v| @prompt = !v }
696 end
697 end
698
699 def parse!(args)
700 options.parse!(args)
701 args = ['http://wiki.rubyonrails.org/rails/pages/Plugins'] if args.empty?
702 args.each do |uri|
703 scrape(uri) do |repo_uri|
704 catch(:next_uri) do
705 if @prompt
706 begin
707 $stdout.print "Add #{repo_uri}? [Y/n] "
708 throw :next_uri if $stdin.gets !~ /^y?$/i
709 rescue Interrupt
710 $stdout.puts
711 exit 1
712 end
713 elsif @list
714 puts repo_uri
715 throw :next_uri
716 end
717 Repositories.instance.add(repo_uri)
718 puts "discovered: #{repo_uri}" if $verbose or !@prompt
719 end
720 end
721 end
722 Repositories.instance.save
723 end
724
725 def scrape(uri)
726 require 'open-uri'
727 puts "Scraping #{uri}" if $verbose
728 dupes = []
729 content = open(uri).each do |line|
730 begin
731 if line =~ /<a[^>]*href=['"]([^'"]*)['"]/ || line =~ /(svn:\/\/[^<|\n]*)/
732 uri = $1
733 if uri =~ /^\w+:\/\// && uri =~ /\/plugins\// && uri !~ /\/browser\// && uri !~ /^http:\/\/wiki\.rubyonrails/ && uri !~ /http:\/\/instiki/
734 uri = extract_repository_uri(uri)
735 yield uri unless dupes.include?(uri) || Repositories.instance.exist?(uri)
736 dupes << uri
737 end
738 end
739 rescue
740 puts "Problems scraping '#{uri}': #{$!.to_s}"
741 end
742 end
743 end
744
745 def extract_repository_uri(uri)
746 uri.match(/(svn|https?):.*\/plugins\//i)[0]
747 end
748 end
749
750 class Install
751 def initialize(base_command)
752 @base_command = base_command
753 @method = :http
754 @options = { :quiet => false, :revision => nil, :force => false }
755 end
756
757 def options
758 OptionParser.new do |o|
759 o.set_summary_indent(' ')
760 o.banner = "Usage: #{@base_command.script_name} install PLUGIN [PLUGIN [PLUGIN] ...]"
761 o.define_head "Install one or more plugins."
762 o.separator ""
763 o.separator "Options:"
764 o.on( "-x", "--externals",
765 "Use svn:externals to grab the plugin.",
766 "Enables plugin updates and plugin versioning.") { |v| @method = :externals }
767 o.on( "-o", "--checkout",
768 "Use svn checkout to grab the plugin.",
769 "Enables updating but does not add a svn:externals entry.") { |v| @method = :checkout }
770 o.on( "-e", "--export",
771 "Use svn export to grab the plugin.",
772 "Exports the plugin, allowing you to check it into your local repository. Does not enable updates, or add an svn:externals entry.") { |v| @method = :export }
773 o.on( "-q", "--quiet",
774 "Suppresses the output from installation.",
775 "Ignored if -v is passed (./script/plugin -v install ...)") { |v| @options[:quiet] = true }
776 o.on( "-r REVISION", "--revision REVISION",
777 "Checks out the given revision from subversion or git.",
778 "Ignored if subversion/git is not used.") { |v| @options[:revision] = v }
779 o.on( "-f", "--force",
780 "Reinstalls a plugin if it's already installed.") { |v| @options[:force] = true }
781 o.separator ""
782 o.separator "You can specify plugin names as given in 'plugin list' output or absolute URLs to "
783 o.separator "a plugin repository."
784 end
785 end
786
787 def determine_install_method
788 best = @base_command.environment.best_install_method
789 @method = :http if best == :http and @method == :export
790 case
791 when (best == :http and @method != :http)
792 msg = "Cannot install using subversion because `svn' cannot be found in your PATH"
793 when (best == :export and (@method != :export and @method != :http))
794 msg = "Cannot install using #{@method} because this project is not under subversion."
795 when (best != :externals and @method == :externals)
796 msg = "Cannot install using externals because vendor/plugins is not under subversion."
797 end
798 if msg
799 puts msg
800 exit 1
801 end
802 @method
803 end
804
805 def parse!(args)
806 options.parse!(args)
807 environment = @base_command.environment
808 install_method = determine_install_method
809 puts "Plugins will be installed using #{install_method}" if $verbose
810 args.each do |name|
811 ::Plugin.find(name).install(install_method, @options)
812 end
813 rescue StandardError => e
814 puts "Plugin not found: #{args.inspect}"
815 puts e.inspect if $verbose
816 exit 1
817 end
818 end
819
820 class Update
821 def initialize(base_command)
822 @base_command = base_command
823 end
824
825 def options
826 OptionParser.new do |o|
827 o.set_summary_indent(' ')
828 o.banner = "Usage: #{@base_command.script_name} update [name [name]...]"
829 o.on( "-r REVISION", "--revision REVISION",
830 "Checks out the given revision from subversion.",
831 "Ignored if subversion is not used.") { |v| @revision = v }
832 o.define_head "Update plugins."
833 end
834 end
835
836 def parse!(args)
837 options.parse!(args)
838 root = @base_command.environment.root
839 cd root
840 args = Dir["vendor/plugins/*"].map do |f|
841 File.directory?("#{f}/.svn") ? File.basename(f) : nil
842 end.compact if args.empty?
843 cd "vendor/plugins"
844 args.each do |name|
845 if File.directory?(name)
846 puts "Updating plugin: #{name}"
847 system("svn #{$verbose ? '' : '-q'} up \"#{name}\" #{@revision ? "-r #{@revision}" : ''}")
848 else
849 puts "Plugin doesn't exist: #{name}"
850 end
851 end
852 end
853 end
854
855 class Remove
856 def initialize(base_command)
857 @base_command = base_command
858 end
859
860 def options
861 OptionParser.new do |o|
862 o.set_summary_indent(' ')
863 o.banner = "Usage: #{@base_command.script_name} remove name [name]..."
864 o.define_head "Remove plugins."
865 end
866 end
867
868 def parse!(args)
869 options.parse!(args)
870 root = @base_command.environment.root
871 args.each do |name|
872 ::Plugin.new(name).uninstall
873 end
874 end
875 end
876
877 class Info
878 def initialize(base_command)
879 @base_command = base_command
880 end
881
882 def options
883 OptionParser.new do |o|
884 o.set_summary_indent(' ')
885 o.banner = "Usage: #{@base_command.script_name} info name [name]..."
886 o.define_head "Shows plugin info at {url}/about.yml."
887 end
888 end
889
890 def parse!(args)
891 options.parse!(args)
892 args.each do |name|
893 puts ::Plugin.find(name).info
894 puts
895 end
896 end
897 end
898 end
899
900 class RecursiveHTTPFetcher
901 attr_accessor :quiet
902 def initialize(urls_to_fetch, level = 1, cwd = ".")
903 @level = level
904 @cwd = cwd
905 @urls_to_fetch = RUBY_VERSION >= '1.9' ? urls_to_fetch.lines : urls_to_fetch.to_a
906 @quiet = false
907 end
908
909 def ls
910 @urls_to_fetch.collect do |url|
911 if url =~ /^svn(\+ssh)?:\/\/.*/
912 `svn ls #{url}`.split("\n").map {|entry| "/#{entry}"} rescue nil
913 else
914 open(url) do |stream|
915 links("", stream.read)
916 end rescue nil
917 end
918 end.flatten
919 end
920
921 def push_d(dir)
922 @cwd = File.join(@cwd, dir)
923 FileUtils.mkdir_p(@cwd)
924 end
925
926 def pop_d
927 @cwd = File.dirname(@cwd)
928 end
929
930 def links(base_url, contents)
931 links = []
932 contents.scan(/href\s*=\s*\"*[^\">]*/i) do |link|
933 link = link.sub(/href="/i, "")
934 next if link =~ /svnindex.xsl$/
935 next if link =~ /^(\w*:|)\/\// || link =~ /^\./
936 links << File.join(base_url, link)
937 end
938 links
939 end
940
941 def download(link)
942 puts "+ #{File.join(@cwd, File.basename(link))}" unless @quiet
943 open(link) do |stream|
944 File.open(File.join(@cwd, File.basename(link)), "wb") do |file|
945 file.write(stream.read)
946 end
947 end
948 end
949
950 def fetch(links = @urls_to_fetch)
951 links.each do |l|
952 (l =~ /\/$/ || links == @urls_to_fetch) ? fetch_dir(l) : download(l)
953 end
954 end
955
956 def fetch_dir(url)
957 @level += 1
958 push_d(File.basename(url)) if @level > 0
959 open(url) do |stream|
960 contents = stream.read
961 fetch(links(url, contents))
962 end
963 pop_d if @level > 0
964 @level -= 1
965 end
966 end
967
968 Commands::Plugin.parse!