Files
ReCI/reci.lua
2024-07-29 17:44:56 -04:00

244 lines
5.4 KiB
Lua

-- Yes, this is ReCI
-- Yes, some parts of this code are nasty
-- No, I do not care
Logger = {}
Logger.level = 1
function Logger.log(sym, color, msg)
print(string.format("\27[0;%dm[%s]\27[0m %s", color, sym, msg))
end
function Logger.help(msg)
Logger.log("H", 36, msg)
end
function Logger.info(msg)
Logger.log("*", 32, msg)
end
function Logger.fatal(msg)
Logger.log("!", 31, msg)
end
function Logger.warning(msg)
if Logger.level >= 1 then
Logger.log("W", 33, msg)
end
end
function Logger.verbose(msg)
if Logger.level >= 2 then
Logger.log("V", 34, msg)
end
end
function Logger.debug(msg)
if Logger.level >= 3 then
Logger.log("?", 35, msg)
end
end
function Throw(msg)
Logger.fatal(msg)
os.exit(1)
end
Runner = {}
function Runner.scripthandler()
local shopts = "set -eE -o functrace"
local onfail = [===[
function onfail() {
local linenum=$1
local bashcmd=$2
local exitcode=$3
printf "\e[0;31m[!]\e[0m Failed at %d: %s <%s>\n" "$linenum" "$bashcmd" "$exitcode"
}
]===]
local trap = string.format([===[trap 'onfail $((${LINENO}-%d)) "$BASH_COMMAND" $?' ERR]===], select(2, string.gsub(shopts .. onfail, '\n', '\n'))+1)
return table.concat({shopts, onfail, trap}, '\n') .. "\n"
end
function Runner.run(script)
Logger.debug(string.format("Script Handler \n{\n%s\n}", Runner.scripthandler():gsub("[^\n]*\n", function (n) return " " .. n end)))
Logger.debug(string.format("Script Body \n{\n%s\n}", script:gsub("[^\n]*\n", function (n) return " " .. n end)))
local shell = io.popen("bash", 'w')
-- When this runs we see stdout. We just don't capture it, so it looks confusing
shell:write(Runner.scripthandler() .. script)
exitcode = select(3, shell:close())
return exitcode
end
Tag = {}
Tag.name = "#" -- # is a stand in for the meta tag name. The meta tag should never be modified.
Tag.script = ""
Tag.tags = {}
Tag.vars = {}
function Tag:new(name, script)
o = {}
setmetatable(o, self)
self.__index = self
if self.name ~= "#" then
table.insert(self.tags, o)
end
o.name = name
o.script = script
o.tags = {}
return o
end
-- {varname="value"}
function Tag:register(var)
if self.name == "#" then
return
end
for k, v in pairs(var) do
Logger.debug(string.format("<%s> Registered: %s=%s", self.name, k, v))
self.vars[k] = v
end
end
-- "varname"
function Tag:get(var)
return self.vars[var]
end
function Tag:run()
Logger.info(string.format("<%s> Running", self.name))
if self.script ~= "" then
Logger.debug(string.format("Script before macro processing\n{\n%s\n}", self.script:gsub("[^\n]*\n", function (n) return " " .. n end)))
_script = self.script:gsub("%%{(%w+.-)}", self.vars)
_undeffound = _script:match("%%{(%w+.-)}")
if _undeffound ~= nil then
Throw(string.format("<%s> Failed: var '%s' not registered", self.name, _undeffound))
end
ec = Runner.run(_script)
if ec ~= 0 then
Throw(string.format("<%s> Failed: %d", self.name, ec))
end
end
for _, i in ipairs(self.tags) do
i:run()
end
Logger.info(string.format("<%s> Success", self.name))
end
Main = Tag:new("main")
opts = {f=1, r=1, l=1, h=0}
function opts.optmatch(o)
for k,_ in pairs(opts) do
if "-" .. k == o then
return true
end
end
return false
end
-- This looks fucking terrible, but it works
function opts.getopts(...)
capture = {}
for i,v in ipairs(...) do
if opts.optmatch(v) then
k = v:sub(2)
nargs = opts[k]
if nargs < 1 then
table.insert(capture, {v, nil})
else
_capture = {v,{}}
for j=1,nargs do
if arg[i+j] == nil or arg[i+j]:sub(1,1) == "-" then
Throw(string.format("Arg %s requires %d parameter(s)", v, nargs))
end
table.insert(_capture[2], arg[i+j])
end
table.insert(capture, _capture)
end
-- Honestly I guess it doesn't matter if this is how we detect invalid args
-- The user could do -f blah blah and the second blah would just be ignored
-- because -f only cares aobut getting 1 parameter, so it only gets the
-- first blah
elseif v:sub(1,1) == "-" then
Throw(string.format("Not a valid arg: %s", v))
end
end
return capture
end
function showhelp()
Logger.help("ReCI Usage: lua reci.lua [OPTION]... [PARAM]...")
function optformat(o, h)
return string.format(" %s\t%s", o, h)
end
Logger.help("Options:")
Logger.help(optformat("-f", "ReCI script to execute. Multiple scripts may be specified with multiple invocations"))
Logger.help(optformat("-r", 'Register a var/macro for ReCI tags to inherit from Main tag. Multiple vars can be registered at once doing -r "macro1=value1, macro2=value2"'))
Logger.help(optformat("-l", "Change log level. Levels: 1 (default), 2 (verbose), 3 (debug)"))
Logger.help(optformat("-h", "Show help."))
end
function processopts(captured)
values = {}
values.filelist = {}
values.vartable = {}
values.loglevel = 1
for i,v in ipairs(captured) do
if v[1] == "-h" then
showhelp()
os.exit()
end
if v[1] == "-f" then
table.insert(values.filelist, v[2][1])
end
if v[1] == "-r" then
for k, vv in string.gmatch(v[2][1], "([%w_]+)=(.+)") do
values.vartable[k] = vv
end
end
if v[1] == "-l" then
l = tonumber(v[2][1])
if l == nil then
Throw("Parameter to -l must be a number")
end
values.loglevel = l
end
end
return values
end
values = processopts(opts.getopts(arg))
Logger.level = values.loglevel
Main:register(values.vartable)
for _,v in ipairs(values.filelist) do
Logger.info(string.format("Loaded %s", v))
dofile(v)
end
Logger.info("Running tags...")
Main:run()