Files
BlockLua/src/util/libts-lua.lua

216 lines
6.0 KiB
Lua

-- This Lua code provides some built-in utilities for writing Lua add-ons
-- It is eval'd automatically once BLLua3 has loaded the TS API and environment
-- It only has access to the sandboxed lua environment, just like user code.
ts = _bllua_ts
-- Provide limited OS functions
os = os or {}
function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end
function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end
-- Virtual file class, emulating a file object as returned by io.open
-- Used to wrap io.open to allow reading from zips (using TS)
-- Not perfect because TS file I/O sucks
-- Can't read nulls, can't distinguish between CRLF and LF.
-- Todo someday: actually read the zip in lua?
local file_meta = {
read = function(file, mode)
file:_init()
if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end
if file._is_open ~= true then error('File:read: File is closed', 2) end
if mode=='*n' then
local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos)
if n then
file.pos = file.pos + #ws + #n
return n
else
return nil
end
elseif mode=='*a' then
local d = file.data:sub(file.pos, #file.data)
file.pos = #file.data + 1
return d
elseif mode=='*l' then
local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos)
if not l then
l = file.data:match('^([^\r\n]*)$', file.pos); ws = '';
if l=='' then return nil end
end
if l then
file.pos = file.pos + #l + #ws
return l
else
return nil
end
elseif type(mode)=='number' then
local d = file.data:sub(file.pos, file.pos+mode)
file.pos = file.pos + #d
return d
else
error('File:read: Invalid mode \''..mode..'\'', 2)
end
end,
lines = function(file)
file:_init()
return function()
return file:read('*l')
end
end,
close = function(file)
if not file._is_open then error('File:close: File is not open', 2) end
file._is_open = false
end,
__index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end,
_init = function(f)
if not f.data then
f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename)
end
end,
}
local function new_file_obj(fn)
local file = {
_is_file = true,
_is_open = true,
pos = 1,
__index = file_meta.__index,
filename = fn,
data = nil,
}
setmetatable(file, file_meta)
return file
end
local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
local allowed_zip_dirs = tflip{
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
}
local function io_open_absolute(fn, mode)
-- if file exists, use original mode
local res, err = _bllua_io_open(fn, mode)
if res then return res
elseif err and not err:find('No such file or directory$') then return nil, err end
-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
local dir = fn:match('^[^/]+')
if not allowed_zip_dirs[dir:lower()] then return nil, 'Zip is not in one of the allowed directories' end
local exist = _bllua_ts.call('isFile', fn) == '1'
if not exist then return nil, err end
if mode~=nil and mode~='r' and mode~='rb' then
return nil, 'Files in zips can only be opened in read mode' end
-- return a temp lua file object with the data
local fi = new_file_obj(fn)
return fi
end
io = io or {}
function io.open(fn, mode, errn)
errn = errn or 1
-- try to open the file with relative path, otherwise use absolute path
local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File')
if curfn == '' then curfn = nil end
if fn:find('^%.') then
local relfn = curfn and fn:find('^%./') and
curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '')
if relfn then
local fi, err = io_open_absolute(relfn, mode)
return fi, err, relfn
else
return nil, 'Invalid path', fn
end
else
local fi, err = io_open_absolute(fn, mode)
return fi, err, fn
end
end
function io.lines(fn)
local fi, err, fn2 = io.open(fn, nil, 2)
if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end
return fi:lines()
end
function io.type(f)
if type(f)=='table' and f._is_file then
return f._is_open and 'file' or 'closed file'
else
return _bllua_io_type(f)
end
end
-- provide dofile
function dofile(fn, errn)
errn = errn or 1
local fi, err, fn2 = io.open(fn, 'r', errn+1)
if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end
print('Executing '..fn2)
local text = fi:read('*a')
fi:close()
return assert(loadstring('--[['..fn2..']]'..text))()
end
-- provide require (just a wrapper for dofile)
-- searches for ?.lua and ?/init.lua in the following directories:
-- location of current file
-- blockland directory
-- current add-on
local function file_exists(fn, errn)
local fi, err, fn2 = io.open(fn, 'r', errn+1)
if fi then
fi:close()
return fn2
else
return nil
end
end
local require_memo = {}
function require(mod)
if require_memo[mod] then return unpack(require_memo[mod]) end
local fp = mod:gsub('%.', '/')
local fns = {
'./'..fp..'.lua', -- local file
'./'..fp..'/init.lua', -- local library
fp..'.lua', -- global file
fp..'/init.lua', -- global library
}
if fp:lower():find('^add-ons/') then
local addonpath = fp:lower():match('^add-ons/[^/]+')..'/'
table.insert(fns, addonpath..fp..'.lua') -- add-on file
table.insert(fns, addonpath..fp..'/init.lua') -- add-on library
end
for _,fn in ipairs(fns) do
local fne = file_exists(fn, 2)
if fne then
local res = {dofile(fne, 2)}
require_memo[mod] = res
return unpack(res)
end
end
return _bllua_requiresecure(mod)
end
local function isValidCode(code)
local f,e = loadstring(code)
return f~=nil
end
function _bllua_smarteval(code)
if (not code:find('^print%(')) and isValidCode('print('..code..')') then
code = 'print('..code..')' end
local f,e = loadstring(code)
if f then
return f()
else
print(e)
end
end
function ts.setvar(name, val)
_bllua_ts.call('_bllua_set_var', name, val)
end
_bllua_ts.call('echo', ' Executed libts-lua.lua')