require 'singleton' # the library class is primarily a namespace for the Library::Version class # to live in. it contains a few usefull methods used to load libraries which # have been installed using versioning information. libraries installed in # this was will have paths like # # lib/foo.rb.0.0.0 # lib/foo.rb.2.1.1 # # and where # # lib/foo.rb # # would be a symbolic link to the latest version of the library. if one # wanted only the latest then the normal 'require' or 'load' should suffice, # but Library.link and Libray.load provide means to specify a particular # interface needed by your program. class Library attr_accessor :filenmae, :version def initialize(filename, version = nil) unless version @filename, @version = Version.parse(filename) else @filename, @version = filename, Version.new(version) end end def inspect "#{filename}.#{version}" end alias to_s inspect alias to_str inspect class Version include Comparable @@vpat = %r{(?:(?:[^\.\d]\.)|(?:^\s*))(\d|\d[\.,]\d|\d[\.,]\d[\.,]\d)\s*$}o attr_reader :current, :revision, :age def initialize current, revision=nil, age=nil if String === current m = @@vpat.match(current) raise "BAD VERSION STRING #{current}" unless m and m[1] vs = m[1] @current, @revision, @age = (vs.split %r{[\.,]}).map{|n| n.to_i} elsif Version === current @current, @revision, @age = [:current, :revision, :age].map{|m| current.send m} else @current, @revision, @age = [current, revision, age].map{|n| n.to_i} end @current ||= 0; @revision ||= 0; @age ||= 0 raise 'REQUIRE : age <= current)' unless @age <= @current end def inspect [current.to_s, revision.to_s, age.to_s].join '.' end alias to_s inspect alias to_str inspect def <=> version v = (String === version ? Version.new(version) : version) comp = current <=> v.current return comp unless comp == 0 comp = revision <=> v.revision return comp unless comp == 0 comp = age <=> v.age return comp unless comp == 0 return comp end def === version v = (String === version ? Version.new(version) : version) comp = current <=> v.current case comp when -1 return false when 0 return true when 1 return ((current - age) <= v.current) end end alias supports? === alias support? === def next! implementation_changed = true, interface_changed = false, backwards_compatible = false mod = false if interface_changed mod = true @current += 1 @revision = 0 if backwards_compatible @age += 1 else @age = 0 end else if implementation_changed mod = true @revision += 1 end end return (mod ? self : nil) end def next implementation_changed = true, interface_changed = false, backwards_compatible = false v = Version.new(self) v.next! implementation_changed, interface_changed, backwards_compatible v end class << self def parse filename f = filename.to_s.clone v = Version.new(f) f[%r{.#{v}$}] = '' [f, v] end end end class Loader include Singleton Entry = Struct.new('Entry', :path, :version) attr_reader :loaded, :sep, :absolute def initialize @loaded = {} @sep = File::SEPARATOR @absolute = %r{^\s*#{sep}}o end def link filename, interface=nil return false if loaded? (filename, interface) self.load filename, interface end def load filename, interface=nil fn = nil v = nil if interface fn = filename v = Version.new(interface) else fn, v = Version.parse(filename) end entries = [] globs(fn).each do |glob| Dir[glob].map do |path| entries << Entry.new(path, Version.new(path)) end end entries.sort! {|a,b| a.version <=> b.version} entries.reverse! entries.each do |entry| if entry.version === v Kernel::load(entry.path) return (loaded[[filename, interface]] = entry.path) end end msg = "INTERFACE #{interface}\nNOT SUPPORTED BY ANY OF\n" entries.each {|e| msg << "\t#{e.path} [#{e.version}\]\n"} raise msg end def globs filename fn = filename.clone ext = fn[%r{\.[^\.]+$}o] globs = [] if ext globs << "#{fn}.[0-9].[0-9].[0-9]" else globs << "#{fn}.rb.[0-9].[0-9].[0-9]" globs << "#{fn}.so.[0-9].[0-9].[0-9]" end if fn =~ absolute return globs else return \ $LOAD_PATH.collect do |path| globs.map {|glob| path.clone << sep << glob } end.flatten end end def loaded_features loaded.values end def loaded? filename, interface loaded[[filename, interface]] end end class << self def link filename, interface=nil Loader.instance.link (filename, interface) end def load filename, interface=nil Loader.instance.load (filename, interface) end end end