Skip to content

Instantly share code, notes, and snippets.

@kaeza
Created January 7, 2017 05:54
Show Gist options
  • Save kaeza/59054b7d4140a2a126da728d969dc4f6 to your computer and use it in GitHub Desktop.
Save kaeza/59054b7d4140a2a126da728d969dc4f6 to your computer and use it in GitHub Desktop.
ZIP module loader for Lua.
---
-- ZIP module loader.
--
-- This is a module that contains a custom module loader that
-- loads modules from ZIP files.
--
-- Currently, only pure Lua modules are supported.
--
-- Intended usage:
--
-- local zpackage = require "zpackage"
--
-- -- This also installs the loader.
-- zpackage.addzip("/path/to/file.zip")
--
-- -- `othermod` is assumed to reside either in `othermod.lua` at
-- -- the root of `file.zip`, or in `init.lua` in an `othermod`
-- -- subdirectory inside the ZIP.
-- local othermod = require "othermod"
--
-- @module zpackage
-- @author kaeza <https://github.com/kaeza>
local M = { _NAME=(...) }
-- Lua 5.1/5.3 compat.
local loadstring = loadstring or load
local zip = require "zip"
---
-- Module search path.
--
-- This must be a semicolon-separated list of patterns in
-- the same format as for Lua's `package.path`.
--
-- Each resulting path is looked for in each ZIP file in turn.
-- If the resulting file can be opened, it tries to load it
-- as a Lua
--
-- Default value is `?.lua;?/init.lua`.
M.path = "?.lua;?/init.lua"
local zipfiles = { }
local zipfilesknown = { }
local strfind, strsub = string.find, string.sub
local function split(str, sep)
local pos, endp = 1, #str+1
local t, n = { }, 0
repeat
local sp, ep = strfind(str, sep, pos, true)
n = n + 1
t[n] = strsub(str, pos, sp and sp-1)
pos = ep and ep+1
until (not pos) or pos >= endp
return t
end
local function tryone(zf, paths)
for _, path in ipairs(paths) do
local f = zf:open(path)
if f then
local data = f:read("*a")
f:close()
if data then
return assert(loadstring(data))
end
end
end
end
local function loader(name)
if not M.path then return end
local paths = split(M.path, ";")
local ename = name:gsub("%%", "%%%%")
for i, path in ipairs(paths) do
paths[i] = path:gsub("%?", ename)
end
for _, file in ipairs(zipfiles) do
local zf, err = zip.open(file)
if zf then
local m = tryone(zf, paths)
zf:close()
if m ~= nil then
return m
end
end
end
end
local installed
---
-- Add a ZIP file to the search path.
--
-- @tparam string filename Path to the ZIP file.
-- @tparam ?boolean first Add to the head of the list (to be
-- searched before others). Default is false.
function M.addzip(filename, first)
if not installed then
installed = true
package.loaders[#package.loaders+1] = loader
end
if not zipfilesknown[filename] then
if first then
table.insert(zipfiles, 1, filename)
else
zipfiles[#zipfiles+1] = filename
end
zipfilesknown[filename] = #zipfiles
end
end
---
-- Remove a ZIP file from the search path.
--
-- @tparam string filename Path to the ZIP file.
function M.removezip(filename)
if zipfilesknown[filename] then
table.remove(zipfiles, zipfilesknown[filename])
zipfilesknown[filename] = nil
end
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment