From: Thomas Sondergaard Date: 2002-06-27T04:03:41+09:00 Subject: Require from zip files - design and code review request I have some spare time coming up, and I want to finish a little module for requiring ruby modules stored in zip files that I have written. A number of questions have come up, and I'd like to get some opinions. 1) Performance vs Complexity vs Liveliness(?) I can either gather the list of zip archives in $: and reread the zip directories for every require, or I can keep a weak reference to the zip archives and reuse them. Should I, then check the modification date of the zip archives and rescan them if they have changed, or is it not worth the extra complexity? After all, how often will the zip archives change after the program has been started? 2) What needs to be set? What needs to be set when I eval() the contents of the require'd ruby module? __FILE__ and what else? Is there a better way than to call eval() btw? 3) Search order in $: My prototype searches zip files before searching regular directories - this means the order of the elements in $: is not obeyed. Does this matter? The reason I've done it this way is that I can use the original definition of require to load modules not stored in zip archives. 4) Requiring .so/.dll files. dlopen requires a filename and can thus not be loaded directly from a zip archive. Is it worth to do some behind the scenes unpacking to /tmp? Thanks, Thomas The prototype implementation is reasonably short (64 lines), so I've appended it here. Comments are more than welcome. #!/usr/bin/env ruby require 'zip' class ZipList def initialize(zipFileList) @zipFileList = zipFileList end def getInputStream(entry, &aProc) @zipFileList.each { |zfName| Zip::ZipFile.open(zfName) { |zf| begin return zf.getInputStream(entry, &aProc) rescue Zip::ZipNoSuchEntryError end } } raise Zip::ZipNoSuchEntryError, "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+ " for '#{entry}'" end end module Kernel alias :oldRequire :require def require(moduleName) zipRequire(moduleName) || oldRequire(moduleName) end def zipRequire(moduleName) return false if alreadyLoaded?(moduleName) getResource(ensureRbExtension(moduleName)) { |zis| eval(zis.read); $" << moduleName } return true rescue Zip::ZipNoSuchEntryError => ex return false end def getResource(resourceName, &aProc) zl = ZipList.new($:.grep(/\.zip$/)) zl.getInputStream(resourceName, &aProc) end def alreadyLoaded?(moduleName) moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$") $".detect { |e| e =~ moduleRE } != nil end def ensureRbExtension(aString) aString.sub(/(\.rb)?$/i, ".rb") end end # Copyright (C) 2002 Thomas Sondergaard # rubyzip is free software; you can redistribute it and/or # modify it under the terms of the ruby license.