Created
January 7, 2017 05:54
-
-
Save kaeza/59054b7d4140a2a126da728d969dc4f6 to your computer and use it in GitHub Desktop.
ZIP module loader for Lua.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
-- 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