configer is small library for merging default and user-provided configs, allowing the user to only specify the things they want to change without having to copy the entire configuration.
For example, if some library uses a configuration that looks something like this:
{
commands = {
hello = {
response = "Hello there!",
color = "green",
},
goodbye = { ... },
...
},
aliases = {
hi = "hello",
bye = "goodbye",
...
},
cooldown = 5,
...
}and the user wants to change commands.hello.response, double the cooldown and rename commands.goodbye to commands.farewell without having to copy the entire configuration (which could be very long), they can just specify:
{
commands = {
hello = {
response = "Greetings!",
},
goodbye = NIL,
farewell = DEFAULT.commands.goodbye,
},
aliases = {
bye = "farewell",
},
cooldown = UPDATE (function(old) return old * 2 end),
}Get it from LuaRocks:
luarocks install configer
Needs Lua 5.2 or higher, but should also work under LuaJIT with compatibility options.
configer can be required via
local configer = require("configer")and contains the following functions and keywords as values.
Takes a default configuration and a user-provided one and merges them. Normally, the operation is a simple deep merge --- values in new overwrite values in default, except if both the default value and the user value is a table, in which case they are recursively merged in the same manner --- but this behavior can be changed by the presence of Keywords.
This function does not modify default nor new and deepcopies any tables it uses from either source.
The options table may contain the following fields:
merger: A function that can be used to copy or merge any custom objects that shouldn't be passed through configer's default copy function. Themergershould take three values,new,oldandcheck, and should return two values,okandres.newis the kept or incoming value,oldmay be an existing value, andcheckis a boolean which is truthy if the function is being called only to determine if a certain value is an object or not (this information is used to prevent objects being checked for containing duplicate tables). Ifokis truthy, configer takesresverbatim as the result of the merge; otherwise, the result of the merger is discarded and the value is copied/merged normally. As an example, a minimal merger function which just passes any custom objects through unaltered would look like this:
local function merger(new, old, _)
return isCustomObject(new) or isCustomObject(old), new
endA slightly more sophisticated a merger which also copies/merges objects will probably look like this:
local function merger(new, old, justChecking)
local newIsCustom = isCustomObject(new)
local oldIsCustom = isCustomObject(old)
if justChecking then
return newIsCustom or oldIsCustom, nil
elseif newIsCustom and oldIsCustom then
return true, mergeCustomObjects(new, old)
elseif newIsCustom or oldIsCustom then
return true, newIsCustom and copyCustomObject(new) or new
end
endInjects the Keywords into the provided environment, throwing an error if the environment already contains values under the same keys.
Returns env.
configer understands a set of keywords which dictate how user configurations are merged. Keywords are meant to be injected into the config loader environment by the library using configer. The only guarantees about values produced by keywords are that they are truthy and not primitive. Programs using configer should treat them as black boxes.
Keywords cannot be nested in any way. That is, they cannot be passed to SET or be returned from UPDATE.
Simply represents the nil value. Use this to remove values from the default config.
configer.resolve(
{
a = "b",
c = "d",
},
{
a = NIL,
}
)results in
{
c = "d",
}The UPDATE keyword takes a function and then uses it to modify the default value instead of blindly replacing it. Said function receives a deepcopy of the default value as the first argument and its return is used as the final value verbatim. Additional arguments may be supplied to the UPDATE keyword; they are passed directly to the function as additional arguments.
configer.resolve(
{
a = 40,
},
{
a = UPDATE (function(x, n) return x + n end, 2),
}
)results in
{
a = 42,
}If the default value doesn't exist, such as when trying to merge a table value with a non-table one, the updater will receive nil.
The SET keyword is meant to be used with tables to specify that the given table should completely overwrite the default value instead of being merged with it.
configer.resolve(
{
a = {
b = "c",
}
},
{
a = SET {
d = "e",
}
}
)results in
{
a = {
d = "e",
}
}Note that SET(nil) is functionally identical to the NIL keyword.
The DEFAULT keyword can be used to refer to members of the default configuration.
configer.resolve(
{
a = "3",
},
{
b = DEFAULT.a,
}
)results in
{
a = "3",
b = "3",
}Note that if the default value being referenced is a table, it will be deepcopied again. In other words, in the example above, if a table had been used instead of "3", result.a and result.b would have different identities.
- Metatables are never copied.
- Table keys are never copied.
- Every table in either config (default or user) must be unique. That is, using the same table object twice is not supported. This limitation doesn't apply to tables supplied to the
SETkeyword or returned via theUPDATEkeyword. This is to avoid ambiguous cases such as the following:
local a = { b = "c" }
configer.resolve({
a1 = a,
a2 = a,
}, {
a1 = {
b = "c1",
},
a2 = {
b = "c2",
},
})luarocks make && luarocks test