Require from zip files - design and code review request


(Thomas Sondergaard) #1

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 © 2002 Thomas Sondergaard

rubyzip is free software; you can redistribute it and/or

modify it under the terms of the ruby license.