compression package wip + debugger bugfixes
This commit is contained in:
@@ -901,11 +901,7 @@ actions = {
|
||||
if fs.isReadOnly(filename) then
|
||||
actions.error("access denied")
|
||||
else
|
||||
local s, m = pcall(function()
|
||||
if not Util.writeLines(filename, tLines) then
|
||||
error("Failed to open " .. filename)
|
||||
end
|
||||
end)
|
||||
local s, m = pcall(Util.writeFile, filename, table.concat(tLines, '\n'))
|
||||
|
||||
if s then
|
||||
lastSave = undo.chain[#undo.chain]
|
||||
|
||||
6
compress/.package
Normal file
6
compress/.package
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
title = 'Compress',
|
||||
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/compress',
|
||||
description = [[untar / gunzip]],
|
||||
license = 'MIT',
|
||||
}
|
||||
530
compress/apis/deflatelua.lua
Normal file
530
compress/apis/deflatelua.lua
Normal file
@@ -0,0 +1,530 @@
|
||||
--[[
|
||||
see: https://github.com/davidm/lua-compress-deflatelua/
|
||||
for licensing / details
|
||||
--]]
|
||||
|
||||
local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'}
|
||||
|
||||
local assert = assert
|
||||
local error = error
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local tostring = tostring
|
||||
local type = type
|
||||
local setmetatable = setmetatable
|
||||
local io = io
|
||||
local math = math
|
||||
local table_sort = table.sort
|
||||
local math_max = math.max
|
||||
local string_char = string.char
|
||||
local band = bit32.band
|
||||
local lshift = bit32.lshift
|
||||
local rshift = bit32.rshift
|
||||
|
||||
local function runtime_error(s, level)
|
||||
level = level or 1
|
||||
error(s, level+1)
|
||||
end
|
||||
|
||||
|
||||
local function make_outstate(outbs)
|
||||
local outstate = {}
|
||||
outstate.outbs = outbs
|
||||
outstate.window = {}
|
||||
outstate.window_pos = 1
|
||||
return outstate
|
||||
end
|
||||
|
||||
|
||||
local function output(outstate, byte)
|
||||
local window_pos = outstate.window_pos
|
||||
outstate.outbs(byte)
|
||||
outstate.window[window_pos] = byte
|
||||
outstate.window_pos = window_pos % 32768 + 1 -- 32K
|
||||
end
|
||||
|
||||
|
||||
local function noeof(val)
|
||||
return assert(val, 'unexpected end of file')
|
||||
end
|
||||
|
||||
|
||||
local function hasbit(bits, bit)
|
||||
return bits % (bit + bit) >= bit
|
||||
end
|
||||
|
||||
|
||||
local function memoize(f)
|
||||
local mt = {}
|
||||
local t = setmetatable({}, mt)
|
||||
function mt:__index(k)
|
||||
local v = f(k)
|
||||
t[k] = v
|
||||
return v
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
-- small optimization (lookup table for powers of 2)
|
||||
local pow2 = memoize(function(n) return 2^n end)
|
||||
|
||||
--local tbits = memoize(
|
||||
-- function(bits)
|
||||
-- return memoize( function(bit) return getbit(bits, bit) end )
|
||||
-- end )
|
||||
|
||||
|
||||
-- weak metatable marking objects as bitstream type
|
||||
local is_bitstream = setmetatable({}, {__mode='k'})
|
||||
|
||||
local function bytestream_from_file(fh)
|
||||
local o = {}
|
||||
function o.read()
|
||||
local sb = fh:read(1)
|
||||
if sb then return sb:byte() end
|
||||
end
|
||||
return o
|
||||
end
|
||||
|
||||
|
||||
local function bytestream_from_string(s)
|
||||
local i = 1
|
||||
local o = {}
|
||||
function o.read()
|
||||
local by
|
||||
if i <= #s then
|
||||
by = s:byte(i)
|
||||
i = i + 1
|
||||
end
|
||||
return by
|
||||
end
|
||||
return o
|
||||
end
|
||||
|
||||
|
||||
local function bytestream_from_function(f)
|
||||
local o = {}
|
||||
function o.read()
|
||||
return f()
|
||||
end
|
||||
return o
|
||||
end
|
||||
|
||||
|
||||
local function bitstream_from_bytestream(bys)
|
||||
local buf_byte = 0
|
||||
local buf_nbit = 0
|
||||
local o = {}
|
||||
|
||||
function o.nbits_left_in_byte()
|
||||
return buf_nbit
|
||||
end
|
||||
|
||||
function o:read(nbits)
|
||||
nbits = nbits or 1
|
||||
while buf_nbit < nbits do
|
||||
local byte = bys:read()
|
||||
if not byte then return end -- note: more calls also return nil
|
||||
buf_byte = buf_byte + lshift(byte, buf_nbit)
|
||||
buf_nbit = buf_nbit + 8
|
||||
end
|
||||
local bits
|
||||
if nbits == 0 then
|
||||
bits = 0
|
||||
elseif nbits == 32 then
|
||||
bits = buf_byte
|
||||
buf_byte = 0
|
||||
else
|
||||
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
|
||||
buf_byte = rshift(buf_byte, nbits)
|
||||
end
|
||||
buf_nbit = buf_nbit - nbits
|
||||
return bits
|
||||
end
|
||||
|
||||
|
||||
is_bitstream[o] = true
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
|
||||
local function get_bitstream(o)
|
||||
local bs
|
||||
if is_bitstream[o] then
|
||||
return o
|
||||
elseif io.type(o) == 'file' then
|
||||
bs = bitstream_from_bytestream(bytestream_from_file(o))
|
||||
elseif type(o) == 'string' then
|
||||
bs = bitstream_from_bytestream(bytestream_from_string(o))
|
||||
elseif type(o) == 'function' then
|
||||
bs = bitstream_from_bytestream(bytestream_from_function(o))
|
||||
else
|
||||
runtime_error 'unrecognized type'
|
||||
end
|
||||
return bs
|
||||
end
|
||||
|
||||
|
||||
local function get_obytestream(o)
|
||||
local bs
|
||||
if io.type(o) == 'file' then
|
||||
bs = function(sbyte) o:write(string_char(sbyte)) end
|
||||
elseif type(o) == 'function' then
|
||||
bs = o
|
||||
else
|
||||
runtime_error('unrecognized type: ' .. tostring(o))
|
||||
end
|
||||
return bs
|
||||
end
|
||||
|
||||
|
||||
local function HuffmanTable(init, is_full)
|
||||
local t = {}
|
||||
if is_full then
|
||||
for val,nbits in pairs(init) do
|
||||
if nbits ~= 0 then
|
||||
t[#t+1] = {val=val, nbits=nbits}
|
||||
end
|
||||
end
|
||||
else
|
||||
for i=1,#init-2,2 do
|
||||
local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
|
||||
if nbits ~= 0 then
|
||||
for val=firstval,nextval-1 do
|
||||
t[#t+1] = {val=val, nbits=nbits}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table_sort(t, function(a,b)
|
||||
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
|
||||
end)
|
||||
|
||||
-- assign codes
|
||||
local code = 1 -- leading 1 marker
|
||||
local nbits = 0
|
||||
for _,s in ipairs(t) do
|
||||
if s.nbits ~= nbits then
|
||||
code = code * pow2[s.nbits - nbits]
|
||||
nbits = s.nbits
|
||||
end
|
||||
s.code = code
|
||||
code = code + 1
|
||||
end
|
||||
|
||||
local minbits = math.huge
|
||||
local look = {}
|
||||
for _,s in ipairs(t) do
|
||||
minbits = math.min(minbits, s.nbits)
|
||||
look[s.code] = s.val
|
||||
end
|
||||
|
||||
local msb = function(bits, nbits)
|
||||
local res = 0
|
||||
for _=1,nbits do
|
||||
res = lshift(res, 1) + band(bits, 1)
|
||||
bits = rshift(bits, 1)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local tfirstcode = memoize(
|
||||
function(bits) return pow2[minbits] + msb(bits, minbits) end)
|
||||
|
||||
function t:read(bs)
|
||||
local code = 1 -- leading 1 marker
|
||||
local nbits = 0
|
||||
while 1 do
|
||||
if nbits == 0 then -- small optimization (optional)
|
||||
code = tfirstcode[noeof(bs:read(minbits))]
|
||||
nbits = nbits + minbits
|
||||
else
|
||||
local b = noeof(bs:read())
|
||||
nbits = nbits + 1
|
||||
code = code * 2 + b -- MSB first
|
||||
end
|
||||
local val = look[code]
|
||||
if val then
|
||||
return val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
local function parse_gzip_header(bs)
|
||||
-- local FLG_FTEXT = 2^0
|
||||
local FLG_FHCRC = 2^1
|
||||
local FLG_FEXTRA = 2^2
|
||||
local FLG_FNAME = 2^3
|
||||
local FLG_FCOMMENT = 2^4
|
||||
|
||||
local id1 = bs:read(8)
|
||||
local id2 = bs:read(8)
|
||||
if id1 ~= 31 or id2 ~= 139 then
|
||||
runtime_error 'not in gzip format'
|
||||
end
|
||||
bs:read(8) -- compression method
|
||||
local flg = bs:read(8) -- FLaGs
|
||||
local mtime = bs:read(32) -- Modification TIME
|
||||
local xfl = bs:read(8) -- eXtra FLags
|
||||
local os = bs:read(8) -- Operating System
|
||||
|
||||
if not os then runtime_error 'invalid header' end
|
||||
|
||||
if hasbit(flg, FLG_FEXTRA) then
|
||||
local xlen = bs:read(16)
|
||||
local extra = 0
|
||||
for i=1,xlen do
|
||||
extra = bs:read(8)
|
||||
end
|
||||
if not extra then runtime_error 'invalid header' end
|
||||
end
|
||||
|
||||
local function parse_zstring(bs)
|
||||
repeat
|
||||
local by = bs:read(8)
|
||||
if not by then runtime_error 'invalid header' end
|
||||
until by == 0
|
||||
end
|
||||
|
||||
if hasbit(flg, FLG_FNAME) then
|
||||
parse_zstring(bs)
|
||||
end
|
||||
|
||||
if hasbit(flg, FLG_FCOMMENT) then
|
||||
parse_zstring(bs)
|
||||
end
|
||||
|
||||
if hasbit(flg, FLG_FHCRC) then
|
||||
local crc16 = bs:read(16)
|
||||
if not crc16 then runtime_error 'invalid header' end
|
||||
-- IMPROVE: check CRC. where is an example .gz file that
|
||||
-- has this set?
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_zlib_header(bs)
|
||||
local cm = bs:read(4) -- Compression Method
|
||||
local cinfo = bs:read(4) -- Compression info
|
||||
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
|
||||
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
|
||||
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
|
||||
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
|
||||
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
|
||||
|
||||
if cm ~= 8 then -- not "deflate"
|
||||
runtime_error("unrecognized zlib compression method: " + cm)
|
||||
end
|
||||
if cinfo > 7 then
|
||||
runtime_error("invalid zlib window size: cinfo=" + cinfo)
|
||||
end
|
||||
local window_size = 2^(cinfo + 8)
|
||||
|
||||
if (cmf*256 + flg) % 31 ~= 0 then
|
||||
runtime_error("invalid zlib header (bad fcheck sum)")
|
||||
end
|
||||
|
||||
if fdict == 1 then
|
||||
runtime_error("FIX:TODO - FDICT not currently implemented")
|
||||
local dictid_ = bs:read(32)
|
||||
end
|
||||
|
||||
return window_size
|
||||
end
|
||||
|
||||
local function parse_huffmantables(bs)
|
||||
local hlit = bs:read(5) -- # of literal/length codes - 257
|
||||
local hdist = bs:read(5) -- # of distance codes - 1
|
||||
local hclen = noeof(bs:read(4)) -- # of code length codes - 4
|
||||
|
||||
local ncodelen_codes = hclen + 4
|
||||
local codelen_init = {}
|
||||
local codelen_vals = {
|
||||
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
||||
for i=1,ncodelen_codes do
|
||||
local nbits = bs:read(3)
|
||||
local val = codelen_vals[i]
|
||||
codelen_init[val] = nbits
|
||||
end
|
||||
local codelentable = HuffmanTable(codelen_init, true)
|
||||
|
||||
local function decode(ncodes)
|
||||
local init = {}
|
||||
local nbits
|
||||
local val = 0
|
||||
while val < ncodes do
|
||||
local codelen = codelentable:read(bs)
|
||||
--FIX:check nil?
|
||||
local nrepeat
|
||||
if codelen <= 15 then
|
||||
nrepeat = 1
|
||||
nbits = codelen
|
||||
elseif codelen == 16 then
|
||||
nrepeat = 3 + noeof(bs:read(2))
|
||||
-- nbits unchanged
|
||||
elseif codelen == 17 then
|
||||
nrepeat = 3 + noeof(bs:read(3))
|
||||
nbits = 0
|
||||
elseif codelen == 18 then
|
||||
nrepeat = 11 + noeof(bs:read(7))
|
||||
nbits = 0
|
||||
else
|
||||
error 'ASSERT'
|
||||
end
|
||||
for i=1,nrepeat do
|
||||
init[val] = nbits
|
||||
val = val + 1
|
||||
end
|
||||
end
|
||||
local huffmantable = HuffmanTable(init, true)
|
||||
return huffmantable
|
||||
end
|
||||
|
||||
local nlit_codes = hlit + 257
|
||||
local ndist_codes = hdist + 1
|
||||
|
||||
local littable = decode(nlit_codes)
|
||||
local disttable = decode(ndist_codes)
|
||||
|
||||
return littable, disttable
|
||||
end
|
||||
|
||||
|
||||
local tdecode_len_base
|
||||
local tdecode_len_nextrabits
|
||||
local tdecode_dist_base
|
||||
local tdecode_dist_nextrabits
|
||||
local function parse_compressed_item(bs, outstate, littable, disttable)
|
||||
local val = littable:read(bs)
|
||||
if val < 256 then -- literal
|
||||
output(outstate, val)
|
||||
elseif val == 256 then -- end of block
|
||||
return true
|
||||
else
|
||||
if not tdecode_len_base then
|
||||
local t = {[257]=3}
|
||||
local skip = 1
|
||||
for i=258,285,4 do
|
||||
for j=i,i+3 do t[j] = t[j-1] + skip end
|
||||
if i ~= 258 then skip = skip * 2 end
|
||||
end
|
||||
t[285] = 258
|
||||
tdecode_len_base = t
|
||||
end
|
||||
if not tdecode_len_nextrabits then
|
||||
local t = {}
|
||||
|
||||
for i=257,285 do
|
||||
local j = math_max(i - 261, 0)
|
||||
t[i] = rshift(j, 2)
|
||||
end
|
||||
|
||||
t[285] = 0
|
||||
tdecode_len_nextrabits = t
|
||||
end
|
||||
local len_base = tdecode_len_base[val]
|
||||
local nextrabits = tdecode_len_nextrabits[val]
|
||||
local extrabits = bs:read(nextrabits)
|
||||
local len = len_base + extrabits
|
||||
|
||||
if not tdecode_dist_base then
|
||||
local t = {[0]=1}
|
||||
local skip = 1
|
||||
for i=1,29,2 do
|
||||
for j=i,i+1 do t[j] = t[j-1] + skip end
|
||||
if i ~= 1 then skip = skip * 2 end
|
||||
end
|
||||
tdecode_dist_base = t
|
||||
end
|
||||
if not tdecode_dist_nextrabits then
|
||||
local t = {}
|
||||
|
||||
for i=0,29 do
|
||||
local j = math_max(i - 2, 0)
|
||||
t[i] = rshift(j, 1)
|
||||
end
|
||||
|
||||
tdecode_dist_nextrabits = t
|
||||
end
|
||||
local dist_val = disttable:read(bs)
|
||||
local dist_base = tdecode_dist_base[dist_val]
|
||||
local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
|
||||
local dist_extrabits = bs:read(dist_nextrabits)
|
||||
local dist = dist_base + dist_extrabits
|
||||
|
||||
for i=1,len do
|
||||
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
|
||||
output(outstate, assert(outstate.window[pos], 'invalid distance'))
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function parse_block(bs, outstate, throttle)
|
||||
local bfinal = bs:read(1)
|
||||
local btype = bs:read(2)
|
||||
|
||||
local BTYPE_NO_COMPRESSION = 0
|
||||
local BTYPE_FIXED_HUFFMAN = 1
|
||||
local BTYPE_DYNAMIC_HUFFMAN = 2
|
||||
local BTYPE_RESERVED_ = 3
|
||||
|
||||
if btype == BTYPE_NO_COMPRESSION then
|
||||
bs:read(bs:nbits_left_in_byte())
|
||||
local len = bs:read(16)
|
||||
local nlen_ = noeof(bs:read(16))
|
||||
|
||||
for _=1,len do
|
||||
local by = noeof(bs:read(8))
|
||||
output(outstate, by)
|
||||
end
|
||||
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||
local littable, disttable
|
||||
if btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||
littable, disttable = parse_huffmantables(bs)
|
||||
else
|
||||
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
|
||||
disttable = HuffmanTable {0,5, 32,nil}
|
||||
end
|
||||
|
||||
repeat
|
||||
local is_done = parse_compressed_item(
|
||||
bs, outstate, littable, disttable)
|
||||
throttle()
|
||||
until is_done
|
||||
else
|
||||
runtime_error 'unrecognized compression type'
|
||||
end
|
||||
|
||||
return bfinal ~= 0
|
||||
end
|
||||
|
||||
function M.inflate(t)
|
||||
local bs = get_bitstream(t.input)
|
||||
local outbs = get_obytestream(t.output)
|
||||
local outstate = make_outstate(outbs)
|
||||
|
||||
repeat
|
||||
local is_final = parse_block(bs, outstate, t.throttle)
|
||||
until is_final
|
||||
end
|
||||
local inflate = M.inflate
|
||||
|
||||
function M.gunzip(t)
|
||||
local bs = get_bitstream(t.input)
|
||||
local outbs = get_obytestream(t.output)
|
||||
|
||||
parse_gzip_header(bs)
|
||||
|
||||
inflate{input=bs, output=outbs, throttle=t.throttle or function() end}
|
||||
|
||||
bs:read(bs:nbits_left_in_byte())
|
||||
end
|
||||
|
||||
return M
|
||||
142
compress/apis/lzw.lua
Normal file
142
compress/apis/lzw.lua
Normal file
@@ -0,0 +1,142 @@
|
||||
-- see: https://github.com/Rochet2/lualzw
|
||||
-- MIT License - Copyright (c) 2016 Rochet2
|
||||
|
||||
local char = string.char
|
||||
local type = type
|
||||
local sub = string.sub
|
||||
local tconcat = table.concat
|
||||
|
||||
local SIGC = 'LZWC'
|
||||
|
||||
local basedictcompress = {}
|
||||
local basedictdecompress = {}
|
||||
for i = 0, 255 do
|
||||
local ic, iic = char(i), char(i, 0)
|
||||
basedictcompress[ic] = iic
|
||||
basedictdecompress[iic] = ic
|
||||
end
|
||||
|
||||
local function dictAddA(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[str] = char(a,b)
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function compress(input)
|
||||
if type(input) ~= "string" then
|
||||
error ("string expected, got "..type(input))
|
||||
end
|
||||
local len = #input
|
||||
if len <= 1 then
|
||||
return input
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = { SIGC }
|
||||
local resultlen = 1
|
||||
local n = 2
|
||||
local word = ""
|
||||
for i = 1, len do
|
||||
local c = sub(input, i, i)
|
||||
local wc = word..c
|
||||
if not (basedictcompress[wc] or dict[wc]) then
|
||||
local write = basedictcompress[word] or dict[word]
|
||||
if not write then
|
||||
error "algorithm error, could not fetch word"
|
||||
end
|
||||
result[n] = write
|
||||
resultlen = resultlen + #write
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return input
|
||||
end
|
||||
dict, a, b = dictAddA(wc, dict, a, b)
|
||||
word = c
|
||||
else
|
||||
word = wc
|
||||
end
|
||||
end
|
||||
result[n] = basedictcompress[word] or dict[word]
|
||||
resultlen = resultlen+#result[n]
|
||||
if len <= resultlen then
|
||||
return input
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
local function dictAddB(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[char(a,b)] = str
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function decompress(input)
|
||||
if type(input) ~= "string" then
|
||||
error( "string expected, got "..type(input))
|
||||
end
|
||||
|
||||
if #input <= 1 then
|
||||
return input
|
||||
end
|
||||
|
||||
local control = sub(input, 1, 4)
|
||||
if control ~= SIGC then
|
||||
return input
|
||||
end
|
||||
input = sub(input, 5)
|
||||
local len = #input
|
||||
|
||||
if len < 2 then
|
||||
error("invalid input - not a compressed string")
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = {}
|
||||
local n = 1
|
||||
local last = sub(input, 1, 2)
|
||||
result[n] = basedictdecompress[last] or dict[last]
|
||||
n = n+1
|
||||
for i = 3, len, 2 do
|
||||
local code = sub(input, i, i+1)
|
||||
local lastStr = basedictdecompress[last] or dict[last]
|
||||
if not lastStr then
|
||||
error( "could not find last from dict. Invalid input?")
|
||||
end
|
||||
local toAdd = basedictdecompress[code] or dict[code]
|
||||
if toAdd then
|
||||
result[n] = toAdd
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
|
||||
else
|
||||
local tmp = lastStr..sub(lastStr, 1, 1)
|
||||
result[n] = tmp
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(tmp, dict, a, b)
|
||||
end
|
||||
last = code
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
return {
|
||||
compress = compress,
|
||||
decompress = decompress,
|
||||
}
|
||||
211
compress/apis/tar.lua
Normal file
211
compress/apis/tar.lua
Normal file
@@ -0,0 +1,211 @@
|
||||
|
||||
-- see: https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua
|
||||
-- A pure-Lua implementation of untar (unpacking .tar archives)
|
||||
|
||||
local tar = { }
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local blocksize = 512
|
||||
|
||||
local function get_typeflag(flag)
|
||||
if flag == "0" or flag == "\0" then return "file"
|
||||
elseif flag == "1" then return "link"
|
||||
elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
|
||||
elseif flag == "3" then return "character"
|
||||
elseif flag == "4" then return "block"
|
||||
elseif flag == "5" then return "directory"
|
||||
elseif flag == "6" then return "fifo"
|
||||
elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
|
||||
elseif flag == "x" then return "next file"
|
||||
elseif flag == "g" then return "global extended header"
|
||||
elseif flag == "L" then return "long name"
|
||||
elseif flag == "K" then return "long link name"
|
||||
end
|
||||
return "unknown"
|
||||
end
|
||||
|
||||
local function octal_to_number(octal)
|
||||
local exp = 0
|
||||
local number = 0
|
||||
octal = octal:gsub("%s", "")
|
||||
for i = #octal,1,-1 do
|
||||
local digit = tonumber(octal:sub(i,i))
|
||||
if not digit then
|
||||
break
|
||||
end
|
||||
number = number + (digit * 8^exp)
|
||||
exp = exp + 1
|
||||
end
|
||||
return number
|
||||
end
|
||||
|
||||
local function checksum_header(block)
|
||||
local sum = 256
|
||||
for i = 1,148 do
|
||||
local b = block:byte(i) or 0
|
||||
sum = sum + b
|
||||
end
|
||||
for i = 157,500 do
|
||||
local b = block:byte(i) or 0
|
||||
sum = sum + b
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
local function nullterm(s)
|
||||
_G._zz = s
|
||||
return s:match("^[^%z]*")
|
||||
end
|
||||
|
||||
local function read_header_block(block)
|
||||
local header = {}
|
||||
header.name = nullterm(block:sub(1,100))
|
||||
header.mode = nullterm(block:sub(101,108)):gsub(" ", "")
|
||||
header.uid = octal_to_number(nullterm(block:sub(109,116)))
|
||||
header.gid = octal_to_number(nullterm(block:sub(117,124)))
|
||||
header.size = octal_to_number(nullterm(block:sub(125,136)))
|
||||
header.mtime = octal_to_number(nullterm(block:sub(137,148)))
|
||||
header.chksum = octal_to_number(nullterm(block:sub(149,156)))
|
||||
header.typeflag = get_typeflag(block:sub(157,157))
|
||||
header.linkname = nullterm(block:sub(158,257))
|
||||
header.magic = block:sub(258,263)
|
||||
header.version = block:sub(264,265)
|
||||
header.uname = nullterm(block:sub(266,297))
|
||||
header.gname = nullterm(block:sub(298,329))
|
||||
header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
|
||||
header.devminor = octal_to_number(nullterm(block:sub(338,345)))
|
||||
header.prefix = block:sub(346,500)
|
||||
if not checksum_header(block) == header.chksum then
|
||||
return false, "Failed header checksum"
|
||||
end
|
||||
return header
|
||||
end
|
||||
|
||||
function tar.untar(filename, destdir, verbose)
|
||||
assert(type(filename) == "string")
|
||||
assert(type(destdir) == "string")
|
||||
|
||||
local tar_handle = io.open(filename, "rb")
|
||||
if not tar_handle then return nil, "Error opening file "..filename end
|
||||
|
||||
local long_name, long_link_name
|
||||
local ok, err
|
||||
|
||||
local make_dir = function(a)
|
||||
if not fs.exists(a) then
|
||||
fs.makeDir(a)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
while true do
|
||||
local block
|
||||
repeat
|
||||
block = tar_handle:read(blocksize)
|
||||
until (not block) or checksum_header(block) > 256
|
||||
if not block then break end
|
||||
if #block < blocksize then
|
||||
ok, err = nil, "Invalid block size -- corrupted file?"
|
||||
break
|
||||
end
|
||||
local header
|
||||
header, err = read_header_block(block)
|
||||
if not header then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
|
||||
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
|
||||
|
||||
if header.typeflag == "long name" then
|
||||
long_name = nullterm(file_data)
|
||||
elseif header.typeflag == "long link name" then
|
||||
long_link_name = nullterm(file_data)
|
||||
else
|
||||
if long_name then
|
||||
header.name = long_name
|
||||
long_name = nil
|
||||
end
|
||||
if long_link_name then
|
||||
header.name = long_link_name
|
||||
long_link_name = nil
|
||||
end
|
||||
end
|
||||
local pathname = fs.combine(destdir, header.name)
|
||||
|
||||
if header.typeflag == "directory" then
|
||||
ok, err = make_dir(pathname)
|
||||
if not ok then
|
||||
break
|
||||
end
|
||||
elseif header.typeflag == "file" then
|
||||
local dirname = fs.getDir(pathname)
|
||||
if dirname ~= "" then
|
||||
ok, err = make_dir(dirname)
|
||||
if not ok then
|
||||
break
|
||||
end
|
||||
end
|
||||
local file_handle
|
||||
if verbose then
|
||||
print(pathname)
|
||||
end
|
||||
file_handle, err = io.open(pathname, "wb")
|
||||
if not file_handle then
|
||||
ok = nil
|
||||
break
|
||||
end
|
||||
file_handle:write(file_data)
|
||||
file_handle:close()
|
||||
end
|
||||
end
|
||||
tar_handle:close()
|
||||
return ok, err
|
||||
end
|
||||
|
||||
local function create_header_block(filename, abspath)
|
||||
local block = ('\0'):rep(512)
|
||||
|
||||
local function number_to_octal(n)
|
||||
return ('%o'):format(n)
|
||||
end
|
||||
|
||||
local function ins(pos, istr)
|
||||
block = block:sub(1, pos - 1) .. istr .. block:sub(pos + #istr)
|
||||
end
|
||||
|
||||
ins(1, filename) -- header
|
||||
ins(125, number_to_octal(fs.getSize(abspath)))
|
||||
ins(157, '0') -- typeflag
|
||||
|
||||
ins(149, number_to_octal(checksum_header(block)))
|
||||
|
||||
return block
|
||||
end
|
||||
|
||||
-- the bare minimum for this program to untar
|
||||
function tar.tar(filename, root, files)
|
||||
assert(type(filename) == "string")
|
||||
assert(type(root) == "string")
|
||||
assert(type(files) == "table")
|
||||
|
||||
local tar_handle = io.open(filename, "wb")
|
||||
if not tar_handle then return nil, "Error opening file "..filename end
|
||||
|
||||
for _, file in pairs(files) do
|
||||
local abs = fs.combine(root, file)
|
||||
local block = create_header_block(file, abs)
|
||||
tar_handle:write(block)
|
||||
local f = require('opus.util').readFile(abs, 'rb')
|
||||
tar_handle:write(f)
|
||||
local padding = #f % 512
|
||||
if padding > 0 then
|
||||
tar_handle:write(('\0'):rep(512 - padding))
|
||||
end
|
||||
end
|
||||
tar_handle:close()
|
||||
return true
|
||||
end
|
||||
|
||||
return tar
|
||||
46
compress/untar.lua
Normal file
46
compress/untar.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
-- see: https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua
|
||||
|
||||
-- TODO: support normal tar syntax -- tar xvf
|
||||
|
||||
local DEFLATE = require('compress.deflatelua')
|
||||
local tar = require('compress.tar')
|
||||
|
||||
local fs = _G.fs
|
||||
local io = _G.io
|
||||
local shell = _ENV.shell
|
||||
|
||||
local args = { ... }
|
||||
|
||||
if not args[2] then
|
||||
error('Syntax: tar FILE DESTDIR')
|
||||
end
|
||||
|
||||
local inFile = shell.resolve(args[1])
|
||||
local outDir = shell.resolve(args[2])
|
||||
|
||||
if inFile:match('(.+)%.[gG][zZ]$') then
|
||||
local TMP_FILE = '.out.tar'
|
||||
|
||||
local fh = io.open(inFile, 'rb') or error('Error opening ' .. inFile)
|
||||
|
||||
fs.mount(TMP_FILE, 'ramfs', 'file')
|
||||
local ofh = io.open(TMP_FILE, 'wb')
|
||||
|
||||
DEFLATE.gunzip {input=fh, output=ofh, disable_crc=true}
|
||||
|
||||
fh:close()
|
||||
ofh:close()
|
||||
|
||||
local s, m = tar.untar(TMP_FILE, outDir, true)
|
||||
|
||||
fs.delete(TMP_FILE)
|
||||
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
else
|
||||
local s, m = tar.untar(inFile, outDir)
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
end
|
||||
@@ -9,10 +9,33 @@ local dbg = {
|
||||
breakpoints = nil,
|
||||
}
|
||||
|
||||
local romFiles = {
|
||||
load = function(self)
|
||||
local function recurse(dir)
|
||||
local files = fs.list(dir)
|
||||
for _,f in ipairs(files) do
|
||||
local fullName = fs.combine(dir, f)
|
||||
if fs.isDir(fullName) then
|
||||
recurse(fullName)
|
||||
else
|
||||
self.files[f] = fullName
|
||||
end
|
||||
end
|
||||
end
|
||||
recurse('rom/apis')
|
||||
end,
|
||||
get = function(self, file)
|
||||
return self.files[file]
|
||||
end,
|
||||
files = { },
|
||||
}
|
||||
romFiles:load()
|
||||
|
||||
local function breakpointHook(info)
|
||||
if dbg.breakpoints then
|
||||
local src = romFiles:get(info.short_src) or info.short_src
|
||||
for _,v in pairs(dbg.breakpoints) do
|
||||
if v.line == info.currentline and v.file == info.short_src then
|
||||
if v.line == info.currentline and v.file == src then
|
||||
return not v.disabled
|
||||
end
|
||||
end
|
||||
|
||||
@@ -376,7 +376,7 @@ local page = UI.Page {
|
||||
local line = self.source:getSelected().line
|
||||
multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = { ('edit --line=%d %s'):format(line , file) },
|
||||
args = { ('edit --line=%d %s'):format(line , '/' .. file) },
|
||||
focused = true,
|
||||
})
|
||||
end
|
||||
|
||||
30
tests/test-ramfs.lua
Normal file
30
tests/test-ramfs.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
local ramfs = require('opus.fs.ramfs')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local str = 'hello'
|
||||
|
||||
local node = fs.mount('test.bin', 'ramfs', 'file')
|
||||
local file = ramfs.open(node, 'test.bin', 'wb')
|
||||
for i = 1, 5 do
|
||||
file.write(str:sub(i, i))
|
||||
end
|
||||
file.close()
|
||||
|
||||
node = fs.getNode('test.bin')
|
||||
file = ramfs.open(node, 'test.bin', 'r')
|
||||
local s = file.read(5)
|
||||
file.close()
|
||||
|
||||
assert(s == str)
|
||||
|
||||
file = ramfs.open(node, 'test.bin', 'w')
|
||||
file.write(str)
|
||||
file.close()
|
||||
|
||||
node = fs.getNode('test.bin')
|
||||
file = ramfs.open(node, 'test.bin', 'rb')
|
||||
s = file.read(5)
|
||||
file.close()
|
||||
|
||||
assert(s == str)
|
||||
20
tests/test-tar.lua
Normal file
20
tests/test-tar.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
local tar = require('compress.tar')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local files = {
|
||||
'test-ramfs.lua',
|
||||
'test-tar.lua',
|
||||
}
|
||||
|
||||
fs.mount('packages/tests/test.tar', 'ramfs', 'file')
|
||||
tar.tar('packages/tests/test.tar', 'packages/tests', files)
|
||||
|
||||
fs.mount('packages/tests/untar', 'ramfs', 'directory')
|
||||
tar.untar('packages/tests/test.tar', 'packages/tests/untar')
|
||||
|
||||
local s1 = Util.readFile('packages/tests/test-ramfs.lua', 'r')
|
||||
local s2 = Util.readFile('packages/tests/untar/test-ramfs.lua', 'r')
|
||||
|
||||
assert(s1 == s2)
|
||||
Reference in New Issue
Block a user