luapack/main.lua

304 lines
5.9 KiB
Lua

lp = {
name = 'LuaPack',
version = '0.1',
copyright = '(c) Er2 2022, Zlib License',
path = {},
}
-- String interpolation
function lp.strf(str, env)
return str:gsub('%$([%w_]+)%]?', (n) ==>
if env[n]
then return tostring(env[n])
end
<==) .. ''
end
-- Helper function
function lp.insPath(t)
for _, v in pairs(t)
do table.insert(lp.path, v)
end
end
function lp.foreach(e, fn)
for _, v in pairs(e.plug)
do fn(v) end
end
function lp.reqf(file, opts)
if opts.ext[file]
then return opts.ext[file]
end
end
function lp.req(e, opts)
return lp.reqf(e.file, opts)
end
-- Polyfill
function table.contains(t, V)
for k, v in pairs(t) do
if v == V
then return k
end
end
end
-- Try to find file path
function lp.resolve(name, dir)
if path.absolute(name)
then return name
end
name = name:gsub('%.[%w_]+$', '')
for _, v in pairs(lp.path) do
v = v:gsub('%?', name)
v = path.join(dir, v)
local f = io.open(v)
if f then
f:close()
return v
end
end
end
-- Use this in plugins to get module
function lp.get(d, e, opts)
local f
for _, v in pairs(e.plug) do
f = lp.resolve(d, e.dir)
if f then break end
end
if not f then
print(lp.strf(' [WRN] Cannot find "$file"', {file = d}))
return nil
end
local c = lp.reqf(f, opts)
if not c
then c = lp.make(f, opts, f .. '.out.lua')
end
if not e.req[c.i]
then e.reqs = e.reqs + 1
end
e.req[c.i] = true
print(lp.strf(' [GOT] $file', c))
return c
end
-- Alias to makeEnt without entry flag
function lp.make(file, opts, out)
return lp.makeEnt(file, opts, out, false)
end
-- Make function
function lp.makeEnt(file, opts, out, entry)
local dir = path.abs(path.dir(file))
local ext = file:match '%.([%w_]+)$'
local oext = out:match '%.([%w_]+)$'
file = lp.resolve(file, dir, ext)
local e = lp.reqf(file, opts)
if e then return e end
local f, err = io.open(file)
assert(f, err)
local src = f:read '*a'
f:close()
e = {
i = #opts.ext + 1,
file = file,
dir = dir,
ext = ext,
src = src,
reqs = 0,
req = {},
entry = entry,
plug = opts.plug,
}
table.insert(opts.ext, file)
opts.ext[file] = e
if #e.plug == 0
then error(lp.strf('File "$file" cannot be loaded. Add plugins.', e))
end
lp.foreach(e, (v) ==>
v.load(e, ext, oext)
e.src = v.deps(e, opts) or e.src
e.src = v.transform(e, opts) or e.src
<==)
return e
end
local alp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
function lp.randgen(n)
local g = ''
for i = 1, n do
local v = math.random(#alp)
g = g.. alp:sub(v, v)
end
return g
end
function lp.save(e, out)
out = lp.strf(out, {
name = path.base(e.file):gsub('%.[%w_]+$', ''),
hash = lp.randgen(6),
})
local f, err = io.open(out, 'w')
assert(f, err)
f:write(e.src)
f:close()
return out
end
function lp.config(conf, dir)
local cfg = {
-- defaults
entry = {},
output = {},
ext = {},
plug = {
require 'plug.lua' {}
},
}
for k, v in pairs(conf) do
if k == 'entry'
or k == 'output'
then
if type(v) == 'string'
then table.insert(cfg[k], path.abs(v, dir))
elseif type(v) == 'table' then
for _, v in pairs(v)
do table.insert(cfg[k], path.abs(tostring(v), dir))
end
else error(lp.strf('Unknown type $type in "$opt"', {type = type(v), opt = k}))
end
elseif k == 'plug'
then for _, v in pairs(v) do
if type(v) == 'table'
then table.insert(cfg[k], v)
v.load = v.load or function(e, ext, oext) end
v.deps = v.deps or function(e, opts) end
v.transform = v.transform or function(e, opts) end
v.postbuild = v.postbuild or function(e, opts) end
else error(lp.strf('Unknown type $type in "$opt"', {type = type(v), opt = k}))
end
end
else error(lp.strf('Unknown option "$opt"', {opt = k}))
end
end
return cfg
end
function lp.build(conf, dir)
local opts = lp.config(conf, dir)
for k, v in pairs(opts.entry) do
local out = opts.output[math.min(k, #opts.output)]
print(lp.strf('[BUILD] $file => $out', {file = v, out = out}))
local e = lp.makeEnt(v, opts, out, true)
lp.foreach(e, function(v)
v.postbuild(e, opts)
end)
local save = lp.save(e, out)
print(lp.strf('[BUILT] $o => $r', {o = e.file, r = save}))
end
end
function main(argv)
print(lp.name ..' '.. lp.version)
print(lp.copyright)
print ''
local int = (argv[0] or ''):match '^lua' -- started using interpreter
if #argv == 0
or (#argv == 1 and int) then
print(('Usage: %s config.lua'):format(int and (argv[0] ..' '.. argv[1]) or argv[0]))
return -1
end
local t = os.time()
local ent = path.abs(int and argv[2] or argv[1])
local dir = path.dir(ent)
local f, err = io.open(ent)
assert(f, err)
local conf = f:read '*a'
f:close()
f, err = load(conf, 'config')
if not f
then error(err)
else conf = f()
end
lp.build(conf, dir)
print(lp.strf('Built under $sec seconds.', {sec = os.time() - t}))
return 0
end
path = {
absolute = (f) ==>
f = f:sub(1, 1)
return f == '/' or f == '\\'
<==
, relative = (f) => not path.absolute(f)
, abs = (f, pwd) ==>
if path.absolute(f)
then return f end
pwd or= os.getenv 'PWD'
f = pwd .. '/'.. f
f = f:gsub('(.*)[\\/]+%.%.', path.dir)
f = f:gsub('%.[\\/]+', '/')
f = f:gsub('[\\/]+', '/')
return f
<==
, rel = (f) => f
, base = (f) => f:match '([^/\\]*)[/\\]*$'
, dir = (f) => f:match '^(.*)[\\/]+[^\\/]*[\\/]*$'
, file = (f) => f:match '([^\\/]+)[\\/]*$'
, join = function(base, add)
if path.absolute(add)
then return add end
if path.relative(base)
then base = path.abs(base)
end
local p = path.abs(base ..'/'.. add)
if path.relative(p)
then return add end
return p
end,
}
math.randomseed(os.time())
os.exit(main(arg))