configmanager: Rename variable to avoid name conflicts [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, _) 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 = _G.pcall;
123         parsers.lua = {};
124         function parsers.lua.load(data, config_file, config_table)
125                 local env;
126                 -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
127                 env = setmetatable({
128                         Host = true, host = true, VirtualHost = true,
129                         Component = true, component = true,
130                         Include = true, include = true, RunScript = true }, {
131                                 __index = function (t, k)
132                                         return rawget(_G, k);
133                                 end,
134                                 __newindex = function (t, k, v)
135                                         set(config, env.__currenthost or "*", k, v);
136                                 end
137                 });
138
139                 rawset(env, "__currenthost", "*") -- Default is global
140                 function env.VirtualHost(name)
141                         name = nameprep(name);
142                         if rawget(config_table, name) and rawget(config_table[name], "component_module") then
143                                 error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
144                                         name, config_table[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
145                         end
146                         rawset(env, "__currenthost", name);
147                         -- Needs at least one setting to logically exist :)
148                         set(config_table, name or "*", "defined", true);
149                         return function (config_options)
150                                 rawset(env, "__currenthost", "*"); -- Return to global scope
151                                 for option_name, option_value in pairs(config_options) do
152                                         set(config_table, name or "*", option_name, option_value);
153                                 end
154                         end;
155                 end
156                 env.Host, env.host = env.VirtualHost, env.VirtualHost;
157
158                 function env.Component(name)
159                         name = nameprep(name);
160                         if rawget(config_table, name) and rawget(config_table[name], "defined") and not rawget(config_table[name], "component_module") then
161                                 error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
162                                         name, name, name), 0);
163                         end
164                         set(config_table, name, "component_module", "component");
165                         -- Don't load the global modules by default
166                         set(config_table, name, "load_global_modules", false);
167                         rawset(env, "__currenthost", name);
168                         local function handle_config_options(config_options)
169                                 rawset(env, "__currenthost", "*"); -- Return to global scope
170                                 for option_name, option_value in pairs(config_options) do
171                                         set(config_table, name or "*", option_name, option_value);
172                                 end
173                         end
174
175                         return function (module)
176                                         if type(module) == "string" then
177                                                 set(config_table, name, "component_module", module);
178                                                 return handle_config_options;
179                                         end
180                                         return handle_config_options(module);
181                                 end
182                 end
183                 env.component = env.Component;
184
185                 function env.Include(file)
186                         if file:match("[*?]") then
187                                 local lfs = deps.softreq "lfs";
188                                 if not lfs then
189                                         error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file));
190                                 end
191                                 local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
192                                 local path = file:sub(1, math_max(path_pos-2,0));
193                                 local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
194                                 if #path > 0 then
195                                         path = resolve_relative_path(config_path, path);
196                                 else
197                                         path = config_path;
198                                 end
199                                 local patt = glob_to_pattern(glob);
200                                 for f in lfs.dir(path) do
201                                         if f:sub(1,1) ~= "." and f:match(patt) then
202                                                 env.Include(path..path_sep..f);
203                                         end
204                                 end
205                         else
206                                 local file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
207                                 local f, err = io.open(file);
208                                 if f then
209                                         local ret, err = parsers.lua.load(f:read("*a"), file, config);
210                                         if not ret then error(err:gsub("%[string.-%]", file), 0); end
211                                 end
212                                 if not f then error("Error loading included "..file..": "..err, 0); end
213                                 return f, err;
214                         end
215                 end
216                 env.include = env.Include;
217
218                 function env.RunScript(file)
219                         return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
220                 end
221
222                 local chunk, err = envload(data, "@"..config_file, env);
223
224                 if not chunk then
225                         return nil, err;
226                 end
227
228                 local ok, err = pcall(chunk);
229
230                 if not ok then
231                         return nil, err;
232                 end
233
234                 return true;
235         end
236
237 end
238
239 return _M;