Mercurial > repo
view share/lua/5.2/luarocks/tools/zip.lua @ 8065:591b1467ccdf
<int-e> le/rn paste/"Paste" is a short story by Henry James. Its contents has been cut into pieces and distributed over numerous tin boxes on the World Wide Web, little pearls of wisdom buried among ordinary pastes.
author | HackBot |
---|---|
date | Sun, 15 May 2016 13:14:57 +0000 |
parents | d137f631bad5 |
children |
line wrap: on
line source
--- A Lua implementation of .zip file archiving (used for creating .rock files), -- using only lua-zlib. module("luarocks.tools.zip", package.seeall) local zlib = require("zlib") local fs = require("luarocks.fs") local dir = require("luarocks.dir") local function number_to_bytestring(number, nbytes) local out = {} for i = 1, nbytes do local byte = number % 256 table.insert(out, string.char(byte)) number = (number - byte) / 256 end return table.concat(out) end --- Begin a new file to be stored inside the zipfile. -- @param self handle of the zipfile being written. -- @param filename filenome of the file to be added to the zipfile. -- @return true if succeeded, nil in case of failure. local function zipwriter_open_new_file_in_zip(self, filename) if self.in_open_file then self:close_file_in_zip() return nil end local lfh = {} self.local_file_header = lfh lfh.last_mod_file_time = 0 -- TODO lfh.last_mod_file_date = 0 -- TODO lfh.crc32 = 0 -- initial value lfh.compressed_size = 0 -- unknown yet lfh.uncompressed_size = 0 -- unknown yet lfh.file_name_length = #filename lfh.extra_field_length = 0 lfh.file_name = filename:gsub("\\", "/") lfh.external_attr = 0 -- TODO properly store permissions self.in_open_file = true self.data = {} return true end --- Write data to the file currently being stored in the zipfile. -- @param self handle of the zipfile being written. -- @param buf string containing data to be written. -- @return true if succeeded, nil in case of failure. local function zipwriter_write_file_in_zip(self, buf) if not self.in_open_file then return nil end local lfh = self.local_file_header local cbuf = zlib.compress(buf):sub(3, -5) lfh.crc32 = zlib.crc32(lfh.crc32, buf) lfh.compressed_size = lfh.compressed_size + #cbuf lfh.uncompressed_size = lfh.uncompressed_size + #buf table.insert(self.data, cbuf) return true end --- Complete the writing of a file stored in the zipfile. -- @param self handle of the zipfile being written. -- @return true if succeeded, nil in case of failure. local function zipwriter_close_file_in_zip(self) local zh = self.ziphandle if not self.in_open_file then return nil end -- Local file header local lfh = self.local_file_header lfh.offset = zh:seek() zh:write(number_to_bytestring(0x04034b50, 4)) -- signature zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0 zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag zh:write(number_to_bytestring(8, 2)) -- compression method: deflate zh:write(number_to_bytestring(lfh.last_mod_file_time, 2)) zh:write(number_to_bytestring(lfh.last_mod_file_date, 2)) zh:write(number_to_bytestring(lfh.crc32, 4)) zh:write(number_to_bytestring(lfh.compressed_size, 4)) zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) zh:write(number_to_bytestring(lfh.file_name_length, 2)) zh:write(number_to_bytestring(lfh.extra_field_length, 2)) zh:write(lfh.file_name) -- File data for _, cbuf in ipairs(self.data) do zh:write(cbuf) end -- Data descriptor zh:write(number_to_bytestring(lfh.crc32, 4)) zh:write(number_to_bytestring(lfh.compressed_size, 4)) zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) table.insert(self.files, lfh) self.in_open_file = false return true end -- @return boolean or (boolean, string): true on success, -- false and an error message on failure. local function zipwriter_add(self, file) local fin local ok, err = self:open_new_file_in_zip(file) if not ok then err = "error in opening "..file.." in zipfile" else fin = io.open(fs.absolute_name(file), "rb") if not fin then ok = false err = "error opening "..file.." for reading" end end if ok then local buf = fin:read("*a") if not buf then err = "error reading "..file ok = false else ok = self:write_file_in_zip(buf) if not ok then err = "error in writing "..file.." in the zipfile" end end end if fin then fin:close() end if ok then ok = self:close_file_in_zip() if not ok then err = "error in writing "..file.." in the zipfile" end end return ok == true, err end --- Complete the writing of the zipfile. -- @param self handle of the zipfile being written. -- @return true if succeeded, nil in case of failure. local function zipwriter_close(self) local zh = self.ziphandle local central_directory_offset = zh:seek() local size_of_central_directory = 0 -- Central directory structure for _, lfh in ipairs(self.files) do zh:write(number_to_bytestring(0x02014b50, 4)) -- signature zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0 zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag zh:write(number_to_bytestring(8, 2)) -- compression method: deflate zh:write(number_to_bytestring(lfh.last_mod_file_time, 2)) zh:write(number_to_bytestring(lfh.last_mod_file_date, 2)) zh:write(number_to_bytestring(lfh.crc32, 4)) zh:write(number_to_bytestring(lfh.compressed_size, 4)) zh:write(number_to_bytestring(lfh.uncompressed_size, 4)) zh:write(number_to_bytestring(lfh.file_name_length, 2)) zh:write(number_to_bytestring(lfh.extra_field_length, 2)) zh:write(number_to_bytestring(0, 2)) -- file comment length zh:write(number_to_bytestring(0, 2)) -- disk number start zh:write(number_to_bytestring(0, 2)) -- internal file attributes zh:write(number_to_bytestring(lfh.external_attr, 4)) -- external file attributes zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header zh:write(lfh.file_name) size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length end -- End of central directory record zh:write(number_to_bytestring(0x06054b50, 4)) -- signature zh:write(number_to_bytestring(0, 2)) -- number of this disk zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir on this disk zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir zh:write(number_to_bytestring(size_of_central_directory, 4)) zh:write(number_to_bytestring(central_directory_offset, 4)) zh:write(number_to_bytestring(0, 2)) -- zip file comment length zh:close() return true end --- Return a zip handle open for writing. -- @param name filename of the zipfile to be created. -- @return a zip handle, or nil in case of error. function new_zipwriter(name) local zw = {} zw.ziphandle = io.open(fs.absolute_name(name), "wb") if not zw.ziphandle then return nil end zw.files = {} zw.in_open_file = false zw.add = zipwriter_add zw.close = zipwriter_close zw.open_new_file_in_zip = zipwriter_open_new_file_in_zip zw.write_file_in_zip = zipwriter_write_file_in_zip zw.close_file_in_zip = zipwriter_close_file_in_zip return zw end --- Compress files in a .zip archive. -- @param zipfile string: pathname of .zip archive to be created. -- @param ... Filenames to be stored in the archive are given as -- additional arguments. -- @return boolean or (boolean, string): true on success, -- false and an error message on failure. function zip(zipfile, ...) local zw = new_zipwriter(zipfile) if not zw then return nil, "error opening "..zipfile end local ok, err for _, file in pairs({...}) do if fs.is_dir(file) then for _, entry in pairs(fs.find(file)) do local fullname = dir.path(file, entry) if fs.is_file(fullname) then ok, err = zw:add(fullname) if not ok then break end end end else ok, err = zw:add(file) if not ok then break end end end local ok = zw:close() if not ok then return false, "error closing "..zipfile end return ok, err end