--- LUA support for TAR packages -- @module ltar local microtar = require("lmicrotar") local lfs = require("lfs") local tar = {} local function strip_from_prefix(prefix, filename) local name = filename:gsub(prefix, "") name = name:sub(2, -1) return name end local function get_filename(file) return file:match("^.+/(.+)$") end local function basedir(p) return p:gsub('[^\\/]+[\\/]?$', '') end local function mkdirp(p) if lfs.attributes(p, 'mode') == 'directory' then return nil, 'already exists' end local b = basedir(p) if #b > 0 and lfs.attributes(b, 'mode') ~= 'directory' then local r, m = mkdirp(b) if not r then return r, m end end return lfs.mkdir(p) end local function dirtree(dir) assert(dir and dir ~= "", "directory parameter is missing or empty") if string.sub(dir, -1) == "/" then dir = string.sub(dir, 1, -2) end local function yieldtree(dir) for entry in lfs.dir(dir) do if entry ~= "." and entry ~= ".." then entry = dir .. "/" .. entry local attr = lfs.attributes(entry) coroutine.yield(entry, attr) if attr.mode == "directory" then yieldtree(entry) end end end end return coroutine.wrap(function() yieldtree(dir) end) end local function write_tarfile_chunks(handle, fd) local size = 1024 * 512 collectgarbage() while true do local block = fd:read(size) if not block then break end handle:write_data(block, block:len()) end end local function read_tarfile_chunks(handle, fd, total_size) local block_size = 1024 * 512 local to_read = {}; collectgarbage() while total_size > 0 do if total_size > block_size then to_read = block_size else to_read = total_size end fd:write(handle:read_data(to_read)) total_size = total_size - to_read end end --- Create empty tar file -- @function create -- @param path where to put newly created tar file -- @return tar handle or nil function tar.create(path) Handle = { handle = microtar.open(path, "w"), add_file = function(self, filename) local size = lfs.attributes(filename, "size") local fd = io.open(filename, "r") self.handle:write_file_header(filename, size) write_tarfile_chunks(self.handle, fd) fd:close() end, add_directory = function(self, path) self.handle:write_dir_header(path) end, close = function(self) self.handle:close() end } return Handle end --- Iterate over contents of tar file -- @function iter_by_handle -- @param handle tar handle create by @{create} function tar.iter_by_handle(handle) return function() local f = handle:read_header() if f then handle:next() return f end end end --- Iterate over contents of tar file -- @function iter_by_path -- @param path path to tar file function tar.iter_by_path(path) local handle = microtar.open(path) return function() local f = handle:read_header() if f then handle:next() return f end end end --- Pack contents of specified dir to tar file -- @function create_from_path -- @param path directory -- @param where where to save tar file function tar.create_from_path(path, where) tar.create_from_path_match(path, where, ".*") end --- Pack contents of specified dir to tar file using regex -- @function create_from_path_regex -- @param path directory -- @param where where to save tar file -- @param matcher regex expression function tar.create_from_path_regex(path, where, matcher) local handle = microtar.open(where, "w") for filename, attr in dirtree(path) do local name = strip_from_prefix(path, filename) if name:match(matcher) then if attr.mode == "directory" then handle:write_dir_header(name) else handle:write_file_header(name, attr.size) local fd = io.open(filename, "r") write_tarfile_chunks(handle, fd) fd:close() end end end handle:close() end --- Unpack tar file to specified directory -- @function unpack -- @param path directory -- @param where where to store unpacked files function tar.unpack(path, where) local handle = microtar.open(path) local header = handle:read_header() while header do if header.type == microtar.TDIR then mkdirp(where .. "/" .. header.name) elseif header.type == microtar.TREG then local fd = io.open(where .. "/" .. header.name, "w") read_tarfile_chunks(handle, fd, header.size) fd:close() end handle:next() header = handle:read_header() end handle:close() end --- Append directory to already existing tar file -- @function append -- @param path directory -- @param where location of already existing tar file function tar.append(path, where) local handle = microtar.open(where, "a") for filename, attr in dirtree(path) do if attr.mode == "directory" then handle:write_dir_header(filename) else handle:write_file_header(filename, attr.size) local fd = io.open(filename, "r") write_tarfile_chunks(handle, fd) fd:close() end end handle:close() end --- Append file to already existing tar file -- @function append_file -- @param path file path -- @param where location of already existing tar file function tar.append_file(path, where) local filename = get_filename(path) local size = lfs.attributes(path, 'size') local handle = microtar.open(where, "a") handle:write_file_header(filename, size) local fd = io.open(path, "r") write_tarfile_chunks(handle, fd) fd:close() handle:close() end function tar.find(tar_path, what) local handle = microtar.open(tar_path) local stats = handle:find(what) if stats ~= nil then return handle:read_data() end return nil end return tar