234 lines
5.6 KiB
Lua
234 lines
5.6 KiB
Lua
|
local lfs = require 'lfs'
|
||
|
local core = require 'git.core'
|
||
|
local deflate = core.deflate
|
||
|
local inflate = core.inflate
|
||
|
local sha = core.sha
|
||
|
|
||
|
module(..., package.seeall)
|
||
|
|
||
|
local BUF_SIZE = 4096
|
||
|
|
||
|
local dirsep = package.config:sub(1,1)
|
||
|
|
||
|
-- replaces '/' path separators on Windows with the correct ones ('\\')
|
||
|
function correct_separators(path)
|
||
|
return path:gsub('/', dirsep)
|
||
|
end
|
||
|
|
||
|
-- joins several path components into a single path, uses system-specific directory
|
||
|
-- separator, cleans input, i.e. join_path('a/', 'b', 'c/') => 'a/b/c'
|
||
|
function join_path(...)
|
||
|
local n = select('#', ...)
|
||
|
local args = {...}
|
||
|
for i=1,n do
|
||
|
args[i] = args[i]:gsub(dirsep..'?$', '')
|
||
|
end
|
||
|
return table.concat(args, dirsep, 1, n)
|
||
|
end
|
||
|
|
||
|
-- Return the path with the all occurences of '/.' or '\.' (representing
|
||
|
-- the current directory) removed.
|
||
|
local function remove_curr_dir_dots(path)
|
||
|
while path:match(dirsep .. "%." .. dirsep) do -- match("/%./")
|
||
|
path = path:gsub(dirsep .. "%." .. dirsep, dirsep) -- gsub("/%./", "/")
|
||
|
end
|
||
|
return path:gsub(dirsep .. "%.$", "") -- gsub("/%.$", "")
|
||
|
end
|
||
|
|
||
|
-- Return whether the path is a root.
|
||
|
local function is_root(path)
|
||
|
return path:find("^[%u%U.]?:?[/\\]$")
|
||
|
end
|
||
|
|
||
|
-- Return the path with the unnecessary trailing separator removed.
|
||
|
local function remove_trailing(path)
|
||
|
if path:sub(-1) == dirsep and not is_root(path) then path = path:sub(1,-2) end
|
||
|
return path
|
||
|
end
|
||
|
|
||
|
-- Extract file or directory name from its path.
|
||
|
local function extract_name(path)
|
||
|
if is_root(path) then return path end
|
||
|
|
||
|
path = remove_trailing(path)
|
||
|
path = path:gsub("^.*" .. dirsep, "")
|
||
|
return path
|
||
|
end
|
||
|
|
||
|
-- Return the string 'str', with all magic (pattern) characters escaped.
|
||
|
local function escape_magic(str)
|
||
|
local escaped = str:gsub('[%-%.%+%[%]%(%)%^%%%?%*%^%$]','%%%1')
|
||
|
return escaped
|
||
|
end
|
||
|
|
||
|
-- Return parent directory of the 'path' or nil if there's no parent directory.
|
||
|
-- If 'path' is a path to file, return the directory the file is in.
|
||
|
function parent_dir(path)
|
||
|
path = remove_curr_dir_dots(path)
|
||
|
path = remove_trailing(path)
|
||
|
|
||
|
local dir = path:gsub(escape_magic(extract_name(path)) .. "$", "")
|
||
|
if dir == "" then
|
||
|
return nil
|
||
|
else
|
||
|
return remove_trailing(dir)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Make a new directory, making also all of its parent directories that doesn't exist.
|
||
|
function make_dir(path)
|
||
|
if lfs.attributes(path) then
|
||
|
return true
|
||
|
else
|
||
|
local par_dir = parent_dir(path)
|
||
|
if par_dir then
|
||
|
assert(make_dir(par_dir))
|
||
|
end
|
||
|
return lfs.mkdir(path)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Reader class
|
||
|
-- adapted from Penlight: https://raw.github.com/stevedonovan/Penlight/master/lua/pl/stringio.lua
|
||
|
|
||
|
local SR = {}
|
||
|
SR.__index = SR
|
||
|
|
||
|
function SR:_read(fmt)
|
||
|
local i,str = self.i,self.str
|
||
|
local sz = #str
|
||
|
if i > sz then return nil, "past end of file" end
|
||
|
local res
|
||
|
if fmt == '*l' or fmt == '*L' then
|
||
|
local idx = str:find('\n',i) or (sz+1)
|
||
|
res = str:sub(i,fmt == '*l' and idx-1 or idx)
|
||
|
self.i = idx+1
|
||
|
elseif fmt == '*a' then
|
||
|
res = str:sub(i)
|
||
|
self.i = sz+1
|
||
|
elseif fmt == '*n' then
|
||
|
local _,i2,i2,idx
|
||
|
_,idx = str:find ('%s*%d+',i)
|
||
|
_,i2 = str:find ('^%.%d+',idx+1)
|
||
|
if i2 then idx = i2 end
|
||
|
_,i2 = str:find ('^[eE][%+%-]*%d+',idx+1)
|
||
|
if i2 then idx = i2 end
|
||
|
local val = str:sub(i,idx)
|
||
|
res = tonumber(val)
|
||
|
self.i = idx+1
|
||
|
elseif type(fmt) == 'number' then
|
||
|
res = str:sub(i,i+fmt-1)
|
||
|
self.i = i + fmt
|
||
|
else
|
||
|
error("bad read format",2)
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
function SR:read(...)
|
||
|
if select('#',...) == 0 then
|
||
|
return self:_read('*l')
|
||
|
else
|
||
|
local res, fmts = {},{...}
|
||
|
for i = 1, #fmts do
|
||
|
res[i] = self:_read(fmts[i])
|
||
|
end
|
||
|
return unpack(res)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function SR:seek(whence,offset)
|
||
|
local base
|
||
|
whence = whence or 'cur'
|
||
|
offset = offset or 0
|
||
|
if whence == 'set' then
|
||
|
base = 1
|
||
|
elseif whence == 'cur' then
|
||
|
base = self.i
|
||
|
elseif whence == 'end' then
|
||
|
base = #self.str
|
||
|
end
|
||
|
self.i = base + offset
|
||
|
return self.i
|
||
|
end
|
||
|
|
||
|
function SR:close() -- for compatibility only
|
||
|
end
|
||
|
|
||
|
--- create a file-like object for reading from a given string.
|
||
|
-- @param s The input string.
|
||
|
function reader(s)
|
||
|
return setmetatable({str=s,i=1},SR)
|
||
|
end
|
||
|
|
||
|
|
||
|
-- decompress the file and return a handle to temporary uncompressed file
|
||
|
function decompressed(path)
|
||
|
local fi = assert(io.open(path, 'rb'))
|
||
|
local result = {}
|
||
|
|
||
|
local z = inflate()
|
||
|
repeat
|
||
|
local str = fi:read(BUF_SIZE)
|
||
|
local data = z(str)
|
||
|
if type(data) == 'string' then
|
||
|
result[#result+1] = data
|
||
|
else print('!!!', data) end
|
||
|
until not str
|
||
|
fi:close()
|
||
|
|
||
|
return reader(table.concat(result))
|
||
|
end
|
||
|
|
||
|
-- reads until the byte \0, consumes it and returns the string up to the \0
|
||
|
function read_until_nul(f)
|
||
|
local t = {}
|
||
|
repeat
|
||
|
local c = f:read(1)
|
||
|
if c and c ~= '\0' then t[#t+1] = c end
|
||
|
until not c or c == '\0'
|
||
|
if #t > 0 then
|
||
|
return table.concat(t)
|
||
|
else
|
||
|
return nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- converts a string to lowercase hex
|
||
|
function to_hex(s)
|
||
|
return (s:gsub('.', function(c)
|
||
|
return string.format('%02x', string.byte(c))
|
||
|
end))
|
||
|
end
|
||
|
|
||
|
-- converts a string from hex to binary
|
||
|
function from_hex(s)
|
||
|
return (s:gsub('..', function(cc)
|
||
|
return string.char(tonumber(cc, 16))
|
||
|
end))
|
||
|
end
|
||
|
|
||
|
-- always returns readable (hex) hash
|
||
|
function readable_sha(s)
|
||
|
if #s ~= 40 then return to_hex(s)
|
||
|
else return s end
|
||
|
end
|
||
|
|
||
|
-- always returns binary hash
|
||
|
function binary_sha(s)
|
||
|
if #s ~= 20 then return from_hex(s)
|
||
|
else return s end
|
||
|
end
|
||
|
|
||
|
function object_sha(data, len, type)
|
||
|
local header = type .. ' ' .. len .. '\0'
|
||
|
local res = sha(header .. data)
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
function deflate(data)
|
||
|
local c = deflate()
|
||
|
return c(data, "finish")
|
||
|
end
|