svenfuchs
9/22/2010 - 2:55 PM

repository.rb

require 'pathname'

# Bundler gemfile support for local/remote workspaces/repositories for work in 
# development teams.
#
# Usage:
#
#   # define paths to be searched for repositories:
#   workspace '~/.projects ~/Development/{projects,work}'
#
#   # define developer preferences for using local or remote repositories (uses ENV['user']):
#   developer :sven, :prefer => :local
#
#   # define repositories to be used for particular gems:
#   adva_cms  = repository('adva-cms2', :git => 'git@github.com:svenfuchs/adva-cms2.git', :ref => 'c2af0de')
#   adva_shop = repository('adva-shop', :source => :local)
# 
#   # now use repositories to define gems:
#   adva_cms.gem  'adva-core'
#   adva_shop.gem 'adva-catalog'
#
#   # The gem definition will now be proxied to Bundler with arguments according
#   # to the setup defined earlier. E.g. as:
#
#   gem 'adva-core', :path => 'Development/projects/adva-cms2/adva-core'                 # for developer 'sven'
#   gem 'adva-core', :git => 'git@github.com:svenfuchs/adva-cms2.git', :ref => 'c2af0de' # for other developers
#   gem 'adva-catalog', :path => 'Development/projects/adva-shop/adva-catalog'           # for all developers
#
#  One can also set an environment variable FORCE_REMOTE which will force remote
#  repositories to be used *except* when a repository was defined with :source => :local
#  which always forces the local repository to be used.
#
class Repository
  class << self
    def paths
      @paths ||= []
    end
    
    def path(*paths)
      paths.join(' ').split(' ').each do |path|
        self.paths.concat(Pathname.glob(File.expand_path(path)))
      end
    end
    
    def developer(name, preferences)
      developers[name] = preferences
      workspaces(preferences[:workspace])
    end
    
    def current_developer
      developers[ENV['USER'].to_sym] || {}
    end
    
    def developers(developers = nil)
      @developers ||= {}
    end
  end
  
  class Gem < Array
    def initialize(name, repository)
      if repository.local?
        sub_path = repository.path.join(name)
        super([name, { :path => sub_path.exist? ? sub_path.to_s : repository.path.to_s }])
      else
        super([name, repository.options.dup])
      end
    end
  end
  
  attr_reader :bundler, :name, :options, :source
  
  def initialize(bundler, name, options)
    @bundler = bundler
    @name    = name
    @source  = options.delete(:source)
    @options = options
  end
  
  def gem(name)
    bundler.gem(*Gem.new(name, self))
  end
  
  def local?
    source == :local && path
  end
  
  def source
    @source ||= forced_source || preferred_source || :remote
  end
  
  def forced_source
    :remote if ENV['FORCE_REMOTE']
  end
  
  def preferred_source
    self.class.current_developer[:prefer]
  end

  def path
    @path ||= begin
      path = self.class.paths.detect { |path| path.join(name).exist? }
      path.join(name) if path
    end
  end
end

def workspace(*paths)
  Repository.path(*paths)
end
alias :workspaces :workspace

def developer(name, preferences)
  Repository.developer(name, preferences)
end

def repository(*args)
  Repository.new(self, *args)
end