--- For working with mod configurations. -- @module Misc.Config -- @usage require('__stdlib__/stdlib/config/config') --- -- @tfield function new -- @tfield function get -- @tfield function set -- @tfield function delete -- @tfield function is_set -- @table Config local M = { __class = 'Config', __index = require('__stdlib__/stdlib/core') } setmetatable(M, M) local table = require('__stdlib__/stdlib/utils/table') local string = require('__stdlib__/stdlib/utils/string') ----------------------------------------------------------------------- --Setup repeated code for use in sub functions here ----------------------------------------------------------------------- local reservedCharacters = [[`~!@#$%^&*+=|;:/\\\'",?()[]{}<>]] local testReservedCharacters = function(path) local reserved = reservedCharacters for c in reserved:gmatch('.') do if path:find(c, 1, true) then return c end end return nil end --- Creates a new Config object to ease the management of a config table. -- @tparam table config_table the config table to manage -- @treturn Config the Config object to manage the config table -- -- @usage --[Use a global table for config that persists across game save/loads] -- CONFIG = Config.new(global.testtable) -- -- @usage --[You can also create a temporary scratch pad config] -- CONFIG = Config.new({}) -- Temporary scratch pad -- -- @usage --[Setting data in Config] -- CONFIG = Config.new(global.testtable) -- CONFIG.set("your.path.here", "myvalue") -- -- @usage --[Getting data out of Config] -- CONFIG = Config.new(global.testtable) -- my_data = CONFIG.get("your.path.here") -- -- @usage --[Getting data out of Config with a default to use if path is not found in Config] -- CONFIG = Config.new(global.testtable) -- my_data = CONFIG.get("your.path.here", "Your Default here") -- -- @usage --[Deleting a path from Config] -- CONFIG = Config.new(global.testtable) -- CONFIG.delete("your.path.here") -- -- @usage --[Checking if a path exists in Config] -- CONFIG = Config.new(global.testtable) -- CONFIG.is_set("your.path.here") function M.new(config_table) if not config_table then error('config_table is a required parameter.', 2) elseif type(config_table) ~= 'table' then error('config_table must be a table. Was given [' .. type(config_table) .. ']', 2) elseif type(config_table.get) == 'function' then error("Config can't manage another Config object", 2) end ----------------------------------------------------------------------- --Setup the Config object ----------------------------------------------------------------------- local Config = {} --- Get a stored config value. -- @tparam string path the variable to retrieve -- @tparam[opt] Mixed default value to be used if path is nil -- @treturn Mixed value at path or nil if not found and no default given function Config.get(path, default) if type(path) ~= 'string' or path == '' then error('path is invalid', 2) end local config = config_table local c = testReservedCharacters(path) if c ~= nil then error("path '" .. path .. "' contains the reserved character '" .. c .. "'", 2) end local pathParts = string.split(path, '.') local part = config local value = nil for key = 1, #pathParts, 1 do local partKey = pathParts[key] if (type(part) ~= 'table') then value = nil break end value = part[partKey] part = part[partKey] end if (type(value) == 'table') then --Force break references. return table.deepcopy(value) elseif (value ~= nil) then return value else return default end end --- Set a stored config value. -- @tparam string path the config path to set -- @tparam ?|nil|Mixed data the value to set the path to. If *nil*, it behaves identical to @{delete|Config.delete()} -- @treturn uint 0 on failure or the number of affected paths on success function Config.set(path, data) if type(path) ~= 'string' or path == '' then error('path is invalid', 2) end local config = config_table local c = testReservedCharacters(path) if c ~= nil then error("path contains the reserved character '" .. c .. "'", 2) end local pathParts = string.split(path, '.') local part = config for key = 1, #pathParts - 1, 1 do local partKey = pathParts[key] if (type(part[partKey]) ~= 'table') then part[partKey] = {} end part = part[partKey] end part[pathParts[#pathParts]] = data return 1 end --- Delete a stored config value. -- @tparam string path the config path to delete -- @treturn uint 0 on failure or the number of affected paths on success function Config.delete(path) if type(path) ~= 'string' or string == '' then error('path is invalid', 2) end local config = config_table local c = testReservedCharacters(path) if c ~= nil then error("path contains the reserved character '" .. c .. "'", 2) end local pathParts = string.split(path, '.') local part = config for key = 1, #pathParts - 1, 1 do local partKey = pathParts[key] if (type(part[partKey]) ~= 'table') then return 0 end part = part[partKey] end if part[pathParts[#pathParts]] == nil then return 0 else part[pathParts[#pathParts]] = nil return 1 end end --- Test the existence of a stored config value. -- @tparam string path the config path to test -- @treturn boolean true if the value exists, false otherwise function Config.is_set(path) if type(path) ~= 'string' or path == '' then error('path is invalid', 2) end return Config.get(path) ~= nil end return Config end return M