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