core.configmanager: Add ways to read config values from files

Inspired by something MattJ said

Allows retrieving config values from files which are expected to be
relative to the config directory, extending on the ENV_ method of
retrieving config values from outside the config file.

- FileLine retrieves the first line, stripping any trailing newline
- FileContents reads the whole file
- FileLines reads lines into an array
This commit is contained in:
Kim Alvefur 2025-01-16 15:05:00 +01:00
parent 76f00fc2e3
commit 2f37c443b8
4 changed files with 37 additions and 4 deletions

View file

@ -60,6 +60,7 @@ TRUNK
- The configuration file now supports referring and appending to options previously set
- Direct usage of the Lua API in the config file is deprecated, but can now be accessed via Lua.* instead
- Convenience functions for reading values from files
## Changes

View file

@ -161,15 +161,43 @@ do
end;
};
-- For reading config values out of files.
local function filereader(basepath, defaultmode)
return function(filename, mode)
local f, err = io.open(resolve_relative_path(basepath, filename));
if not f then error(err, 2); end
local content, err = f:read(mode or defaultmode);
f:close();
if not content then error(err, 2); end
return content;
end
end
-- Collect lines into an array
local function linereader(basepath)
return function(filename)
local ret = {};
for line in io.lines(resolve_relative_path(basepath, filename)) do
t_insert(ret, line);
end
return ret;
end
end
parser = {};
function parser.load(data, config_file, config_table)
local set_options = {}; -- set_options[host.."/"..option_name] = true (when the option has been set already in this file)
local warnings = {};
local env;
local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
-- The ' = true' are needed so as not to set off __newindex when we assign the functions below
env = setmetatable({
Host = true, host = true, VirtualHost = true,
Component = true, component = true,
FileContents = true,
FileLine = true,
FileLines = true,
Include = true, include = true, RunScript = true }, {
__index = function (_, k)
if k:match("^ENV_") then
@ -293,7 +321,6 @@ do
end
local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
local path = file:sub(1, math_max(path_pos-2,0));
local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
if #path > 0 then
path = resolve_relative_path(config_path, path);
else
@ -308,7 +335,7 @@ do
return;
end
-- Not a wildcard, so resolve (potentially) relative path and run through config parser
file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
file = resolve_relative_path(config_path, file);
local f, err = io.open(file);
if f then
local ret, err = parser.load(f:read("*a"), file, config_table);
@ -325,9 +352,13 @@ do
env.include = env.Include;
function env.RunScript(file)
return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
return dofile(resolve_relative_path(config_path, file));
end
env.FileContents = filereader(config_path, "*a");
env.FileLine = filereader(config_path, "*l");
env.FileLines = linereader(config_path);
local chunk, err = envload(data, "@"..config_file, env);
if not chunk then

1
spec/scansion/admins.txt Normal file
View file

@ -0,0 +1 @@
admin@localhost

View file

@ -1,6 +1,6 @@
--luacheck: ignore
admins = { "admin@localhost" }
admins = FileLines("admins.txt")
network_backend = ENV_PROSODY_NETWORK_BACKEND or "epoll"
network_settings = Lua.require"prosody.util.json".decode(ENV_PROSODY_NETWORK_SETTINGS or "{}")