~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/railties/lib/commands/plugin.rb

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
      mkdir_p(install_path = "#{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!