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