2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
10 local setmetatable
, rawget, rawset, io
, os
, error, dofile, type, pairs
, ipairs
=
11 setmetatable
, rawget, rawset, io
, os
, error, dofile, type, pairs
, ipairs
;
12 local format, math_max
, t_insert
= string.format, math
.max, table.insert
;
14 local envload
= require
"util.envload".envload
;
15 local deps
= require
"util.dependencies";
16 local resolve_relative_path
= require
"util.paths".resolve_relative_path
;
17 local glob_to_pattern
= require
"util.paths".glob_to_pattern
;
18 local path_sep
= package
.config
:sub(1,1);
19 local get_traceback_table
= require
"util.debug".get_traceback_table
;
21 local encodings
= deps
.softreq
"util.encodings";
22 local nameprep
= encodings
and encodings
.stringprep
.nameprep
or function (host
) return host
:lower(); end
28 _M
.resolve_relative_path
= resolve_relative_path
; -- COMPAT
32 local config_mt
= { __index
= function (t
, _
) return rawget(t
, "*"); end};
33 local config
= setmetatable({ ["*"] = { } }, config_mt
);
35 -- When host not found, use global
36 local host_mt
= { __index
= function(_
, k
) return config
["*"][k
] end }
38 function _M
.getconfig()
42 function _M
.get(host
, key
, _oldkey
)
44 key
= _oldkey
; -- COMPAT with code that still uses "core"
46 return config
[host
][key
];
48 function _M
.rawget(host
, key
, _oldkey
)
50 key
= _oldkey
; -- COMPAT with code that still uses "core"
52 local hostconfig
= rawget(config
, host
);
54 return rawget(hostconfig
, key
);
58 local function set(config_table
, host
, key
, value
)
60 local hostconfig
= rawget(config_table
, host
);
61 if not hostconfig
then
62 hostconfig
= rawset(config_table
, host
, setmetatable({}, host_mt
))[host
];
64 hostconfig
[key
] = value
;
70 function _M
.set(host
, key
, value
, _oldvalue
)
72 key
, value
= value
, _oldvalue
; --COMPAT with code that still uses "core"
74 return set(config
, host
, key
, value
);
77 function _M
.load(filename
, config_format
)
78 config_format
= config_format
or filename
:match("%w+$");
80 if config_format
== "lua" then
81 local f
, err
= io
.open(filename
);
83 local new_config
= setmetatable({ ["*"] = { } }, config_mt
);
84 local ok
, err
= parser
.load(f
:read("*a"), filename
, new_config
);
89 return ok
, "parser", err
;
91 return f
, "file", err
;
94 if not config_format
then
95 return nil, "file", "no parser specified";
97 return nil, "file", "no parser for "..(config_format
);
101 -- Built-in Lua parser
103 local pcall
= _G
.pcall
;
104 local function get_line_number(config_file
)
105 local tb
= get_traceback_table(nil, 2);
107 if tb
[i
].info
.short_src
== config_file
then
108 return tb
[i
].info
.currentline
;
113 function parser
.load(data
, config_file
, config_table
)
114 local set_options
= {}; -- set_options[host.."/"..option_name] = true (when the option has been set already in this file)
117 -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
119 Host
= true, host
= true, VirtualHost
= true,
120 Component
= true, component
= true,
121 Include
= true, include
= true, RunScript
= true }, {
122 __index
= function (_
, k
)
123 if k
:match("^ENV_") then
124 return os
.getenv(k
:sub(5));
126 return rawget(_G
, k
);
128 __newindex
= function (_
, k
, v
)
129 local host
= env
.__currenthost
or "*";
130 local option_path
= host
.."/"..k
;
131 if set_options
[option_path
] then
132 t_insert(warnings
, ("%s:%d: Duplicate option '%s'"):format(config_file
, get_line_number(config_file
), k
));
134 set_options
[option_path
] = true;
135 set(config_table
, env
.__currenthost
or "*", k
, v
);
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);
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
);
156 env
.Host
, env
.host
= env
.VirtualHost
, env
.VirtualHost
;
158 function env
.Component(name
)
159 name
= nameprep(name
);
160 if rawget(config_table
, name
) and rawget(config_table
[name
], "defined")
161 and not rawget(config_table
[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);
165 set(config_table
, name
, "component_module", "component");
166 -- Don't load the global modules by default
167 set(config_table
, 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_table
, name
or "*", option_name
, option_value
);
176 return function (module
)
177 if type(module
) == "string" then
178 set(config_table
, name
, "component_module", module
);
179 return handle_config_options
;
181 return handle_config_options(module
);
184 env
.component
= env
.Component
;
186 function env
.Include(file
)
187 -- Check whether this is a wildcard Include
188 if file
:match("[*?]") then
189 local lfs
= deps
.softreq
"lfs";
191 error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file
));
193 local path_pos
, glob
= file
:match("()([^"..path_sep
.."]+)$");
194 local path
= file
:sub(1, math_max(path_pos
-2,0));
195 local config_path
= config_file
:gsub("[^"..path_sep
.."]+$", "");
197 path
= resolve_relative_path(config_path
, path
);
201 local patt
= glob_to_pattern(glob
);
202 for f
in lfs
.dir(path
) do
203 if f
:sub(1,1) ~= "." and f
:match(patt
) then
204 env
.Include(path
..path_sep
..f
);
209 -- Not a wildcard, so resolve (potentially) relative path and run through config parser
210 file
= resolve_relative_path(config_file
:gsub("[^"..path_sep
.."]+$", ""), file
);
211 local f
, err
= io
.open(file
);
213 local ret
, err
= parser
.load(f
:read("*a"), file
, config_table
);
214 if not ret
then error(err
:gsub("%[string.-%]", file
), 0); end
216 for _
, warning
in ipairs(err
) do
217 t_insert(warnings
, warning
);
221 if not f
then error("Error loading included "..file
..": "..err
, 0); end
224 env
.include
= env
.Include
;
226 function env
.RunScript(file
)
227 return dofile(resolve_relative_path(config_file
:gsub("[^"..path_sep
.."]+$", ""), file
));
230 local chunk
, err
= envload(data
, "@"..config_file
, env
);
236 local ok
, err
= pcall(chunk
);
242 return true, warnings
;