2026-01-30 01:06:32 -05:00
|
|
|
#!/bin/lua
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
--[[ Copyright (c) 2026 Alexander Hill
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
|
|
|
copyright notice and this permission notice appear in all copies.
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
|
PERFORMANCE OF THIS SOFTWARE. ]]
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
local posix = require("posix")
|
|
|
|
|
local tinytoml = require("tinytoml")
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-02-01 21:06:35 -05:00
|
|
|
local config
|
|
|
|
|
local config_path = "/etc/maple.toml"
|
2026-01-30 01:06:32 -05:00
|
|
|
local sysroot = "/"
|
|
|
|
|
local templates = "/usr/share/mapleconf"
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-02-01 21:06:35 -05:00
|
|
|
local function tokenize_block(inner)
|
|
|
|
|
local buffer = ""
|
|
|
|
|
local escaped = false
|
|
|
|
|
local quoted = false
|
|
|
|
|
local tokens = {}
|
|
|
|
|
|
|
|
|
|
for char in inner:gmatch(".") do
|
|
|
|
|
if escaped then
|
|
|
|
|
buffer = buffer .. char
|
|
|
|
|
escaped = false
|
|
|
|
|
|
|
|
|
|
elseif char == "\\" then
|
|
|
|
|
if quoted then
|
|
|
|
|
buffer = buffer .. char
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
escaped = true
|
|
|
|
|
|
|
|
|
|
elseif quoted then
|
|
|
|
|
buffer = buffer .. char
|
|
|
|
|
|
|
|
|
|
if char == "\"" then
|
|
|
|
|
quoted = false
|
|
|
|
|
table.insert(tokens, buffer)
|
|
|
|
|
buffer = ""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
if char:match("%s") then
|
|
|
|
|
if #buffer > 0 then
|
|
|
|
|
table.insert(tokens, buffer)
|
|
|
|
|
buffer = ""
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
elseif char == "\"" then
|
|
|
|
|
buffer = buffer .. char
|
|
|
|
|
quoted = true
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
buffer = buffer .. char
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-02-01 21:06:35 -05:00
|
|
|
if #buffer > 0 then
|
|
|
|
|
table.insert(tokens, buffer)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return tokens
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Symbol Reference:
|
|
|
|
|
-- {"if", condition, true, false} - Executes true if condition is "truthy" and false otherwise
|
|
|
|
|
-- {"literal", string} - Writes `string` to the target
|
|
|
|
|
-- {"string", string} - Format and write string to the target
|
|
|
|
|
-- {"value", identifier} - Returns the value of the identifier
|
|
|
|
|
|
|
|
|
|
local function tokenize_template(raw)
|
2026-01-30 01:06:32 -05:00
|
|
|
local current = 0
|
|
|
|
|
local last = 0
|
|
|
|
|
local literal = true
|
2026-02-01 21:06:35 -05:00
|
|
|
local template = {}
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
while current do
|
|
|
|
|
current = string.find(raw, "@", current + 1)
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
if current then
|
2026-02-01 21:06:35 -05:00
|
|
|
-- TODO: What happens when a line ends with a backslash? ~ahill
|
|
|
|
|
if raw:sub(current - 1, current - 1) ~= "\\" then
|
|
|
|
|
if literal then
|
|
|
|
|
table.insert(template, { "literal", string.sub(raw, last + 1, current - 1) })
|
|
|
|
|
literal = false
|
2026-01-30 01:06:32 -05:00
|
|
|
|
|
|
|
|
else
|
2026-02-01 21:06:35 -05:00
|
|
|
if current - last == 1 then
|
|
|
|
|
table.insert(template, { "literal", "@" })
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
local inner = raw:sub(last + 1, current - 1)
|
|
|
|
|
|
|
|
|
|
if inner:sub(1, 1) ~= "-" then
|
|
|
|
|
local tokens = tokenize_block(inner)
|
|
|
|
|
|
|
|
|
|
print(#tokens)
|
|
|
|
|
for _, v in ipairs(tokens) do
|
|
|
|
|
print(v)
|
|
|
|
|
if v == "nil" then
|
|
|
|
|
table.insert(template, { "literal", "" })
|
|
|
|
|
|
|
|
|
|
elseif v:sub(1, 1) == "\"" and v:sub(-1, -1) == "\"" then
|
|
|
|
|
table.insert(template, { "string", v:sub(2, -2) })
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
table.insert(template, {"value", v})
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- ...
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
literal = true
|
2026-01-30 01:06:32 -05:00
|
|
|
end
|
2026-02-01 21:06:35 -05:00
|
|
|
|
|
|
|
|
last = current
|
2026-01-30 01:06:32 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
table.insert(template, { "literal", string.sub(raw, last + 1) })
|
2026-02-01 21:06:35 -05:00
|
|
|
last = current
|
2026-01-30 01:06:32 -05:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-01 21:06:35 -05:00
|
|
|
return template
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function render_template(source, target)
|
|
|
|
|
local raw = source:read("*all")
|
|
|
|
|
local result = ""
|
|
|
|
|
local template = tokenize_template(raw)
|
|
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
for _, v in ipairs(template) do
|
|
|
|
|
if v[1] == "literal" then
|
|
|
|
|
result = result .. v[2]
|
2026-02-01 21:06:35 -05:00
|
|
|
|
|
|
|
|
elseif v[1] == "string" then
|
|
|
|
|
result = result .. v[2]
|
|
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
elseif v[1] == "value" then
|
|
|
|
|
if config[v[2]] then
|
|
|
|
|
result = result .. config[v[2]]
|
|
|
|
|
else
|
2026-02-01 21:06:35 -05:00
|
|
|
print("Unable to locate " .. v[2])
|
2026-01-30 01:06:32 -05:00
|
|
|
return
|
|
|
|
|
end
|
2026-02-01 21:06:35 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
else
|
|
|
|
|
print("BUG: Symbol type " .. v[1] .. " was not recognized!")
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
target:write(result)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function render_directory(stack)
|
|
|
|
|
local path
|
|
|
|
|
if #stack == 0 then
|
|
|
|
|
path = "/"
|
2026-01-24 17:47:33 -05:00
|
|
|
else
|
2026-01-30 01:06:32 -05:00
|
|
|
path = "/" .. table.concat(stack, "/") .. "/"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local files = posix.dir(templates .. path)
|
|
|
|
|
for _, entry in ipairs(files) do
|
|
|
|
|
if not string.match(entry, "^%.+$") then
|
|
|
|
|
local fullpath = path .. entry
|
|
|
|
|
|
|
|
|
|
local stat = posix.stat(templates .. fullpath)
|
|
|
|
|
if stat.type == "directory" then
|
|
|
|
|
if not posix.access(sysroot .. fullpath) then
|
|
|
|
|
posix.mkdir(sysroot .. fullpath)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local newstack = {}
|
|
|
|
|
-- Why does Lua lack the ability to copy tables? ~ahill
|
|
|
|
|
for _, v in ipairs(stack) do
|
|
|
|
|
table.insert(newstack, v)
|
|
|
|
|
end
|
|
|
|
|
table.insert(newstack, entry)
|
|
|
|
|
render_directory(newstack)
|
|
|
|
|
|
|
|
|
|
elseif stat.type == "regular" then
|
|
|
|
|
print("Updating " .. fullpath)
|
|
|
|
|
|
|
|
|
|
local template = io.open(templates .. fullpath, "r")
|
|
|
|
|
if template then
|
|
|
|
|
local target = io.open(sysroot .. fullpath, "w")
|
|
|
|
|
if target then
|
|
|
|
|
render_template(template, target)
|
|
|
|
|
target:close()
|
|
|
|
|
end
|
|
|
|
|
template:close()
|
|
|
|
|
end
|
2026-01-24 17:47:33 -05:00
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
else
|
|
|
|
|
print("BUG: Directory entry type " .. stat.type .. " not recognized.")
|
|
|
|
|
end
|
|
|
|
|
end
|
2026-01-24 17:47:33 -05:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-01 21:06:35 -05:00
|
|
|
-- ENTRY POINT
|
|
|
|
|
|
|
|
|
|
for opt, optarg, optind in posix.unistd.getopt(arg, "c:hr:t:") do
|
|
|
|
|
if opt == "c" then
|
|
|
|
|
config_path = optarg
|
|
|
|
|
elseif opt == "h" then
|
|
|
|
|
-- Pardon the formatting. This should be cleaned up later. ~ahill
|
|
|
|
|
print[[Usage: mapleconf [option [option ...] ]
|
|
|
|
|
-c <file> - Sets the file to source configuration data from
|
|
|
|
|
-h - Displays this help message
|
|
|
|
|
-r <path> - Sets the sysroot to configure
|
|
|
|
|
-t <path> - Sets the path of the template directory]]
|
|
|
|
|
os.exit(1)
|
|
|
|
|
elseif opt == "r" then
|
|
|
|
|
sysroot = optarg
|
|
|
|
|
elseif opt == "t" then
|
|
|
|
|
templates = optarg
|
|
|
|
|
else
|
|
|
|
|
print("Unknown option: " .. arg[optind - 1])
|
|
|
|
|
os.exit(1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
config = tinytoml.parse(config_path)
|
|
|
|
|
|
2026-01-30 01:06:32 -05:00
|
|
|
render_directory{}
|