require 'pathname'
require 'chrysalis/errors'
module Chrysalis
# Represents a repository from which dependencies can be retrieved.
#
# This class is intended to be subclassed in order to implement support for
# concrete types of repositories.
class Repository
@@repositories = []
def self.inherited(repository) # :nodoc:
@@repositories << repository
end
# Retrieves a WorkingCopy from the repository located at +url+. The working
# copy is placed at the path +to+, which defaults to the current directory.
# An optional set of parameters can be specified in +params+.
#
# Returns a WorkingCopy.
#
# Using the factory design pattern, this method locates a subclass of
# Repository that implements support for the given URL. That subclass will
# be instructed to retrieve a working copy from the repository.
#
# If the working copy has already been retrieved, it will be found in the
# cache and returned directly.
#
# If support for the given URL is not implemented, a RepositoryError will be
# raised.
def self.retrieve(url, to = '.', params = {})
cached = WorkingCopy.create(Chrysalis::Cache.instance[url])
return cached if cached
@@repositories.reverse.each { |repository|
if repository.retrieves?(url, params)
working_copy = repository.new(url, params).retrieve(to)
Chrysalis::Cache.instance[url] = working_copy
return working_copy
end
}
raise RepositoryError, "Unknown version control system. (URL: #{url})"
end
# Returns +true+ if this class implements support for +url+. Otherwise,
# returns +false+.
#
# Because this class is abstract, +false+ is returned unconditionally.
# Subclasses are expected to provide an implementation.
def self.retrieves?(url, params = {})
false
end
def initialize(url, params = {})
end
# Retrieves a working copy from the repository.
#
# Because this class is abstract, always raises an UnimplementedError.
# Subclasses are expected to provide an implementation.
def retrieve(to = '.')
raise UnimplementedError, "Repository#retrieve not implemented"
end
end
# Represents a working copy that has been retrieved from a repository.
#
# This class implements support for a file system-based working copy stored in
# a directory.
#
# Subclasses can provide implementation for working copies with additional
# functionality, such as those retrieved from a version control system.
class WorkingCopy
@@working_copies = [self]
def self.inherited(working_copies) # :nodoc:
@@working_copies << working_copies
end
# Returns a WorkingCopy constructed with +params+.
#
# Using the factory design pattern, this method instantiates a new instance
# of WorkingCopy based on the key :type in the +params+ hash.
#
# If :type is not supported, +nil+ is returned.
#
# This method is intended to be used for the purpose of instantiating a
# working copies directly from the cache, to obviate the need for retrieval
# when the working copy already exists on the system.
def self.create(params)
return nil if params.nil?
type = params[:type]
@@working_copies.each do |working_copy|
return working_copy.new(params) if working_copy.type == type
end
nil
end
attr_reader :url
attr_reader :path
def self.type
:directory
end
def initialize(params = {})
@url = params[:url]
@path = params[:path]
end
# Returns +true+ if the working copy exists on disk. Otherwise, returns
# +false+.
#
# Typically, if the working copy no longer exits on disk, it has been
# explicitly removed by a developer.
def exist?
Pathname.new(@path).exist?
end
# Returns a hash which will be serialized to the cache.
#
# At a minimum, a :type key must be present in the hash. This key
# is used when loading the cache, to instantiate working copies that have
# previously been retrieved.
#
# Subclasses can add any additional keys and values to the hash, and utilize
# them for purposes of deserialization.
def to_hash
{ :type => self.class.type,
:url => @url,
:path => @path }
end
end
end