#require 'rbconfig' require 'roll/version' require 'roll/metadata' require 'roll/errors' module Roll # = Library class # class Library # Dynamic link extension. #DLEXT = '.' + ::Config::CONFIG['DLEXT'] # #SUFFIXES = ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar'] SUFFIXES = ['', '.rb', '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar'] # SUFFIX_PATTERN = "{#{SUFFIXES.join(',')}}" # Get an instance of a library by name, or name and version. # Libraries are singleton, so once loaded the same object is # always returned. def self.instance(name, constraint=nil) name = name.to_s #raise "no library -- #{name}" unless ledger.include?(name) return nil unless ledger.include?(name) library = ledger[name] if Library===library if constraint # TODO: it's okay if constraint fits current raise VersionConflict, "previously selected version -- #{ledger[name].version}" else library end else # library is an array of versions if constraint compare = Version.constraint_lambda(constraint) library = library.select(&compare).max else library = library.max end unless library raise VersionError, "no library version -- #{name} #{constraint}" end #ledger[name] = library #library.activate return library end end # A shortcut for #instance. def self.[](name, constraint=nil) instance(name, constraint) end # Same as #instance but will raise and error if the library is # not found. This can also take a block to yield on the library. def self.open(name, constraint=nil) #:yield: lib = instance(name, constraint) unless lib raise LoadError, "no library -- #{name}" end yield(lib) if block_given? lib end # def initialize(location, name=nil) @location = location @name = name end # def location @location end # def name @name ||= metadata.name end # def version @version ||= metadata.version end # def active? @active ||= metadata.active end # def loadpath @loadpath ||= metadata.loadpath end # def requires @requires ||= metadata.requires end # def released @released ||= metadata.released end # def verify requires.each do |(name, constraint)| Library.open(name, constraint) end end # Find first matching +file+. #def find(file, suffix=true) # case File.extname(file) # when *SUFFIXES # find = File.join(lookup_glob, file) # else # find = File.join(lookup_glob, file + SUFFIX_PATTERN) #'{' + ".rb,#{DLEXT}" + '}') # end # Dir[find].first #end # Standard loadpath search. # def find(file, suffix=true) if suffix SUFFIXES.each do |ext| loadpath.each do |lpath| f = File.join(location, lpath, file + ext) return f if File.file?(f) end end else loadpath.each do |lpath| f = File.join(location, lpath, file) return f if File.file?(f) end end nil end # Does this library have a matching +file+? If so, the full-path # of the file is returned. # # Unlike #find, this also matches within the library directory # itself, eg. <tt>lib/foo/*</tt>. It is used by #aquire. # def include?(file, suffix=true) if suffix SUFFIXES.each do |ext| loadpath.each do |lpath| f = File.join(location, lpath, name, file + ext) return f if File.file?(f) f = File.join(location, lpath, file + ext) return f if File.file?(f) end end else loadpath.each do |lpath| f = File.join(location, lpath, name, file) return f if File.file?(f) f = File.join(location, lpath, file) return f if File.file?(f) end end nil end #def include?(file) # case File.extname(file) # when *SUFFIXES # find = File.join(lookup_glob, "{#{name}/,}" + file) # else # find = File.join(lookup_glob, "{#{name}/,}" + file + SUFFIX_PATTERN) #'{' + ".rb,#{DLEXT}" + '}') # end # Dir[find].first #end # def require(file) if path = include?(file) require_absolute(path) else load_error = LoadError.new("no such file to require -- #{name}:#{file}") raise clean_backtrace(load_error) end end # NOT SURE ABOUT USING THIS def require_absolute(file) #Library.load_monitor[file] << caller if $LOAD_MONITOR Library.load_stack << self begin success = original_require(file) #rescue LoadError => load_error # raise clean_backtrace(load_error) ensure Library.load_stack.pop end success end # def load(file, wrap=nil) if path = include?(file, false) load_absolute(path, wrap) else load_error = LoadError.new("no such file to load -- #{name}:#{file}") clean_backtrace(load_error) end end # def load_absolute(file, wrap=nil) #Library.load_monitor[file] << caller if $LOAD_MONITOR Library.load_stack << self begin success = original_load(file, wrap) #rescue LoadError => load_error # raise clean_backtrace(load_error) ensure Library.load_stack.pop end success end # Inspection. def inspect if @version %[#<Library #{name}/#{@version} @location="#{location}">] else %[#<Library #{name} @location="#{location}">] end end def to_s inspect end # Compare by version. def <=>(other) version <=> other.version end # # List of subdirectories that are searched when loading. # #-- # # This defualts to ['lib/{name}', 'lib']. The first entry is # # usually proper location; the latter is added for default # # compatability with the traditional require system. # #++ # def libdir # loadpath.map{ |path| File.join(location, path) } # end # # # Does the library have any lib directories? # def libdir? # lib.any?{ |d| File.directory?(d) } # end # Location of executable. This is alwasy bin/. This is a fixed # convention, unlike lib/ which needs to be more flexable. def bindir ; File.join(location, 'bin') ; end # Is there a <tt>bin/</tt> location? def bindir? ; File.exist?(bindir) ; end # Location of library system configuration files. # This is alwasy the <tt>etc/</tt> directory. def confdir ; File.join(location, 'etc') ; end # Is there a <tt>etc/</tt> location?metadata.name def confdir? ; File.exist?(confdir) ; end # Location of library shared data directory. # This is always the <tt>data/</tt> directory. def datadir ; File.join(location, 'data') ; end # Is there a <tt>data/</tt> location? def datadir? ; File.exist?(datadir) ; end # Access to secondary metadata. def metadata @metadata ||= Metadata.new(location) end private # #def lookup_glob # @lookup_glob ||= File.join(location, '{' + loadpath.join(',') + '}') #end # def clean_backtrace(error) if $DEBUG error else bt = error.backtrace bt = bt.reject{ |e| /roll/ =~ e } if bt error.set_backtrace(bt) error end end end end