configmanager: Rename variable to avoid name conflict [luacheck]
[prosody.git] / core / configmanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 local _G = _G;
10 local setmetatable, rawget, rawset, io, error, dofile, type, pairs, table =
11       setmetatable, rawget, rawset, io, error, dofile, type, pairs, table;
12 local format, math_max = string.format, math.max;
13
14 local fire_event = prosody and prosody.events.fire_event or function () end;
15
16 local envload = require"util.envload".envload;
17 local deps = require"util.dependencies";
18 local resolve_relative_path = require"util.paths".resolve_relative_path;
19 local glob_to_pattern = require"util.paths".glob_to_pattern;
20 local path_sep = package.config:sub(1,1);
21
22 local have_encodings, encodings = pcall(require, "util.encodings");
23 local nameprep = have_encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end
24
25 module "configmanager"
26
27 _M.resolve_relative_path = resolve_relative_path; -- COMPAT
28
29 local parsers = {};
30
31 local config_mt = { __index = function (t, k) return rawget(t, "*"); end};
32 local config = setmetatable({ ["*"] = { } }, config_mt);
33
34 -- When host not found, use global
35 local host_mt = { __index = function(_, k) return config["*"][k] end }
36
37 function getconfig()
38         return config;
39 end
40
41 function get(host, key, _oldkey)
42         if key == "core" then
43                 key = _oldkey; -- COMPAT with code that still uses "core"
44         end
45         return config[host][key];
46 end
47 function _M.rawget(host, key, _oldkey)
48         if key == "core" then
49                 key = _oldkey; -- COMPAT with code that still uses "core"
50         end
51         local hostconfig = rawget(config, host);
52         if hostconfig then
53                 return rawget(hostconfig, key);
54         end
55 end
56
57 local function set(config_table, host, key, value)
58         if host and key then
59                 local hostconfig = rawget(config_table, host);
60                 if not hostconfig then
61                         hostconfig = rawset(config_table, host, setmetatable({}, host_mt))[host];
62                 end
63                 hostconfig[key] = value;
64                 return true;
65         end
66         return false;
67 end
68
69 function _M.set(host, key, value, _oldvalue)
70         if key == "core" then
71                 key, value = value, _oldvalue; --COMPAT with code that still uses "core"
72         end
73         return set(config, host, key, value);
74 end
75
76 function load(filename, config_format)
77         config_format = config_format or filename:match("%w+$");
78
79         if parsers[config_format] and parsers[config_format].load then
80                 local f, err = io.open(filename);
81                 if f then
82                         local new_config = setmetatable({ ["*"] = { } }, config_mt);
83                         local ok, err = parsers[config_format].load(f:read("*a"), filename, new_config);
84                         f:close();
85                         if ok then
86                                 config = new_config;
87                                 fire_event("config-reloaded", {
88                                         filename = filename,
89                                         format = config_format,
90                                         config = config
91                                 });
92                         end
93                         return ok, "parser", err;
94                 end
95                 return f, "file", err;
96         end
97
98         if not config_format then
99                 return nil, "file", "no parser specified";
100         else
101                 return nil, "file", "no parser for "..(config_format);
102         end
103 end
104
105 function addparser(config_format, parser)
106         if config_format and parser then
107                 parsers[config_format] = parser;
108         end
109 end
110
111 -- _M needed to avoid name clash with local 'parsers'
112 function _M.parsers()
113         local p = {};
114         for config_format in pairs(parsers) do
115                 table.insert(p, config_format);
116         end
117         return p;
118 end
119
120 -- Built-in Lua parser
121 do
122         local pcall, setmetatable = _G.pcall, _G.setmetatable;
123         local rawget = _G.rawget;
124         parsers.lua = {};
125         function parsers.lua.load(data, config_file, config)
126                 local env;
127                 -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
128                 env = setmetatable({
129                         Host = true, host = true, VirtualHost = true,
130                         Component = true, component = true,
131                         Include = true, include = true, RunScript = true }, {
132                                 __index = function (t, k)
133                                         return rawget(_G, k);
134                                 end,
135                                 __newindex = function (t, k, v)
136                                         set(config, env.__currenthost or "*", k, v);
137                                 end
138                 });
139
140                 rawset(env, "__currenthost", "*") -- Default is global
141                 function env.VirtualHost(name)
142                         name = nameprep(name);
143                         if rawget(config, name) and rawget(config[name], "component_module") then
144                                 error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
145                                         name, config[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
146                         end
147                         rawset(env, "__currenthost", name);
148                         -- Needs at least one setting to logically exist :)
149                         set(config, name or "*", "defined", true);
150                         return function (config_options)
151                                 rawset(env, "__currenthost", "*"); -- Return to global scope
152                                 for option_name, option_value in pairs(config_options) do
153                                         set(config, name or "*", option_name, option_value);
154                                 end
155                         end;
156                 end
157                 env.Host, env.host = env.VirtualHost, env.VirtualHost;
158
159                 function env.Component(name)
160                         name = nameprep(name);
161                         if rawget(config, name) and rawget(config[name], "defined") and not rawget(config[name], "component_module") then
162                                 error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
163                                         name, name, name), 0);
164                         end
165                         set(config, name, "component_module", "component");
166                         -- Don't load the global modules by default
167                         set(config, name, "load_global_modules", false);
168                         rawset(env, "__currenthost", name);
169                         local function handle_config_options(config_options)
170                                 rawset(env, "__currenthost", "*"); -- Return to global scope
171                                 for option_name, option_value in pairs(config_options) do
172                                         set(config, name or "*", option_name, option_value);
173                                 end
174                         end
175
176                         return function (module)
177                                         if type(module) == "string" then
178                                                 set(config, name, "component_module", module);
179                                                 return handle_config_options;
180                                         end
181                                         return handle_config_options(module);
182                                 end
183                 end
184                 env.component = env.Component;
185
186                 function env.Include(file)
187                         if file:match("[*?]") then
188                                 local lfs = deps.softreq "lfs";
189                                 if not lfs then
190                                         error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file));
191                                 end
192                                 local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
193                                 local path = file:sub(1, math_max(path_pos-2,0));
194                                 local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
195                                 if #path > 0 then
196                                         path = resolve_relative_path(config_path, path);
197                                 else
198                                         path = config_path;
199                                 end
200                                 local patt = glob_to_pattern(glob);
201                                 for f in lfs.dir(path) do
202                                         if f:sub(1,1) ~= "." and f:match(patt) then
203                                                 env.Include(path..path_sep..f);
204                                         end
205                                 end
206                         else
207                                 local file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
208                                 local f, err = io.open(file);
209                                 if f then
210                                         local ret, err = parsers.lua.load(f:read("*a"), file, config);
211                                         if not ret then error(err:gsub("%[string.-%]", file), 0); end
212                                 end
213                                 if not f then error("Error loading included "..file..": "..err, 0); end
214                                 return f, err;
215                         end
216                 end
217                 env.include = env.Include;
218
219                 function env.RunScript(file)
220                         return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
221                 end
222
223                 local chunk, err = envload(data, "@"..config_file, env);
224
225                 if not chunk then
226                         return nil, err;
227                 end
228
229                 local ok, err = pcall(chunk);
230
231                 if not ok then
232                         return nil, err;
233                 end
234
235                 return true;
236         end
237
238 end
239
240 return _M;