Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --- Zippy - Archive compressor/decompressor for ComputerCraft
- -- @version 0.6-rc1
- -- @author Shefla
- -- @license MIT
- -- @changelog
- -- 0.1 Initial version, concatenates file and folders
- -- 0.2 Added basic RLE compression/decompression
- -- 0.3 Public release, added comments to API section
- -- 0.4 Removed root from file/folder paths inside archive
- -- Added relative path support to CLI parameters
- -- 0.5 Added end delimiters to run-length encoded chunks
- -- 0.6 Fixed bug when zippy is compressing itself
- -- Added token dictionnary for better compression ratio
- local ch, header, trim, isAPI = string.char, '', {}, shell == nil
- local log = isAPI and function () end or print
- local STX, ETX, SUB, FS, GS = ch(2), ch(3), ch(26), ch(28), ch(29)
- -- header = [SOH]zippy1[DLE]
- for _, val in ipairs({ 1, 122, 105, 112, 112, 121, 49, 16 }) do
- header = header..ch(val)
- end
- -- trim = { [TAB]=[DC1], [LF]=[DC2], [CR]=[DC3], space=[DC4] }
- for key, val in pairs({ ['9']=17, ['10']='18', ['13']='19', ['32']='20' }) do
- trim[ch(key)] = ch(val)
- end
- -- Compression
- --------------------------------------------------------------------------------
- local function concat (root, path, tokens)
- local rel, file, data = path:gsub(root, '', 1)
- local buf = { rel }
- if fs.isDir(path) then
- table.insert(buf, GS)
- for _, file in ipairs(fs.list(path)) do
- data, tokens = concat(root, fs.combine(path, file), tokens)
- for _, chunk in ipairs(data) do table.insert(buf, chunk) end
- end
- else
- file = fs.open(path, 'r')
- data = file.readAll()
- file.close()
- table.insert(buf, FS..data..GS)
- for token in data:gmatch('%w+') do
- tokens[token] = tokens[token] and tokens[token] + 1 or 1
- end
- end
- return buf, tokens
- end
- local function shrink (data, tokens)
- local dict, buf, size, len, tmp = {}, {}, 3
- for chr, DCX in pairs(trim) do
- for index, chunk in ipairs(data) do
- buf[index] = chunk:gsub(chr:rep(3)..'+', function (str)
- return DCX..#str..DCX
- end)
- end
- end
- for token, count in pairs(tokens) do
- len = #token
- if len * count > len + 1 + (count * size) then
- table.insert(dict, token)
- len = #dict
- size = #tostring(len) + 2
- for index, chunk in ipairs(buf) do
- buf[index] = chunk:gsub(token, function () return SUB..len..SUB end)
- end
- end
- end
- return table.concat(dict, STX)..ETX..table.concat(buf, '')
- end
- -- Decompression
- --------------------------------------------------------------------------------
- function parse (data)
- assert(data:sub(1, 8) == header, 'Invalid archive format')
- local index, dict, files = 9, {}, {}
- local split = data:find(ETX)
- for token in data:sub(index, split - 1):gmatch('[^'..STX..']%w+') do
- table.insert(dict, token)
- end
- for chunk in data:sub(split + 1):gmatch('(.-)'..GS) do
- chunk = expand(chunk, dict)
- index = chunk:find(FS)
- if not index then files[chunk] = true
- else files[chunk:sub(1, index - 1)] = chunk:sub(index + 1)
- end
- end
- return files
- end
- function expand (data, dict)
- for char, DCX in pairs(trim) do
- data = data:gsub(DCX..'(%d+)'..DCX, function (count)
- return char:rep(count)
- end)
- end
- for index, token in ipairs(dict) do
- data = data:gsub(SUB..index..SUB, token)
- end
- return data
- end
- -- Public API
- --------------------------------------------------------------------------------
- --- Compress a file or folder into zippy archive
- -- @param path {string} - Absolute path to compress
- -- @param dest {string} - Absolute path of created archive
- function compress (path, dest)
- assert(fs.exists(path), 'File not found: '..path)
- log('Creating archive: '..dest)
- local file = fs.open(dest, 'w')
- assert(file, 'File is not writeable: '..dest)
- file.write(header..shrink(
- concat(fs.getDir(path), fs.combine(path, ''), {})
- ))
- file.close()
- end
- --- Extract a zippy archive contents
- -- @param path {string} - Absolute path to archive
- -- @param dest {string} - Absolute path of extracted contents
- function extract (path, dest)
- local file = fs.open(path, 'r')
- assert(file, 'File is not readable: '..path)
- log('Extracting archive: '..path)
- local data = parse(file.readAll())
- file.close()
- for path, contents in pairs(data) do
- path = fs.combine(dest, path)
- if contents == true then fs.makeDir(path)
- else
- file = fs.open(path, 'w')
- file.write(contents)
- file.close()
- end
- end
- end
- -- Command line interface
- --------------------------------------------------------------------------------
- if isAPI then return end
- local args, root = {...}, fs.getDir(shell.getRunningProgram())
- local function resolve (path)
- return shell.resolve(path:sub(1, 1) ~= '/'
- and fs.combine(root, path)
- or path
- )
- end
- if #args < 3 then
- print('Usage: zippy <action> <path> <dest>')
- print('actions:')
- print(' -x, extract Extract path to dest')
- print(' -c, compress Compress path into dest')
- else
- local action, path, dest = args[1], resolve(args[2]), resolve(args[3])
- if action == '-x' or action == 'extract' then extract(path, dest)
- elseif action == '-c' or action == 'compress' then compress(path, dest)
- else error('Invalid action parameter')
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment