---https://github.com/drivendataorg/repro-zipfile/pull/23 ---@module rpzip ---@copyright 2025 local lfs = require "lfs" local ZipWriter = require "ZipWriter" local M = { Entry = { filename = ".", chunk_size = 1024, istext = true, isfile = true, isdir = false, mtime = 315532800, platform = 'unix', exattrib = { ZipWriter.NIX_FILE_ATTR.IFREG, ZipWriter.NIX_FILE_ATTR.IWUSR, ZipWriter.NIX_FILE_ATTR.IRUSR, ZipWriter.NIX_FILE_ATTR.IRGRP, ZipWriter.NIX_FILE_ATTR.IROTH, }, } } ---@param path string ---@return boolean function M.is_executable(path) local attr = lfs.attributes(path) or {} local perms = attr.permissions or "" if #perms < 9 then return false end return perms:sub(3, 3) == "x" or perms:sub(6, 6) == "x" or perms:sub(9, 9) == "x" end ---@param dir string ---@return boolean function M.isdir(dir) local attr = lfs.attributes(dir) return attr and attr.mode == "directory" end ---@param entry table? ---@return table entry function M.Entry:new(entry) entry = entry or {} setmetatable(entry, { __index = self }) if M.isdir(entry.filename) then entry.isdir = true entry.isfile = false entry.istext = false entry.exattrib = { ZipWriter.NIX_FILE_ATTR.IFDIR, ZipWriter.NIX_FILE_ATTR.IWUSR, ZipWriter.NIX_FILE_ATTR.IRUSR, ZipWriter.NIX_FILE_ATTR.IRGRP, ZipWriter.NIX_FILE_ATTR.IROTH, ZipWriter.NIX_FILE_ATTR.IXUSR, ZipWriter.NIX_FILE_ATTR.IXGRP, ZipWriter.NIX_FILE_ATTR.IXOTH, } elseif M.is_executable(entry.filename) then entry.istext = false entry.exattrib = { ZipWriter.NIX_FILE_ATTR.IFREG, ZipWriter.NIX_FILE_ATTR.IWUSR, ZipWriter.NIX_FILE_ATTR.IRUSR, ZipWriter.NIX_FILE_ATTR.IRGRP, ZipWriter.NIX_FILE_ATTR.IROTH, ZipWriter.NIX_FILE_ATTR.IXUSR, ZipWriter.NIX_FILE_ATTR.IXGRP, ZipWriter.NIX_FILE_ATTR.IXOTH, } end entry.mtime = os.getenv "SOURCE_DATE_EPOCH" or entry.mtime return entry end setmetatable(M.Entry, { __call = M.Entry.new }) function M.Entry:add(writer) local f = assert(io.open(self.filename, 'rb')) local reader = self.reader or function() local chunk = f:read(self.chunk_size) if chunk then return chunk end f:close() end writer:write(self.filename, self, self.isfile and reader, self.comment) end ---@param filename string ---@param entries table[] ---@return string? err function M.zip_entries(filename, entries) local f, err = io.open(filename, 'wb') if f == nil then return err end local writer = ZipWriter.new() writer:open_stream(f, true) for _, entry in ipairs(entries) do entry:add(writer) end writer:close() end ---@param path string ---@param callback fun(entry: table) function M.call(path, callback) local entry = M.Entry { filename = path } callback(entry) if entry.isdir then for file in lfs.dir(path) do if file ~= "." and file ~= ".." then M.call(path .. "/" .. file, callback) end end end end ---@param filename string ---@param ... string function M.zip(filename, ...) local entries = {} for _, path in ipairs { ... } do M.call(path, function(entry) table.insert(entries, entry) end) end local err = M.zip_entries(filename, entries) if err then print(err) end end ---@param args string[] function M.main(args) local filename local entries = {} for _, path in ipairs(args) do if path:sub(1, 1) ~= "-" then if filename == nil then filename = path else M.call(path, function(entry) table.insert(entries, entry) end) end end end local err = M.zip_entries(filename, entries) if err then print(err) end end return M