Module:Database report/facade
Appearance
This module is used from {{Database report}}.
local Arguments = require('Module:Arguments')
local TableTools = require('Module:TableTools')
local p = {}
local function error(text)
return '\n*' .. require('Module:Error').error{ text, tag = 'p' }
end
local function str_split(inputstr, sep)
local t = {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
local function is_integer(str)
if str:match("%D") then
return false
end
return true
end
local function trim(s)
return s:match("^%s*(.-)%s*$")
end
p.main = function(frame)
local args = Arguments.getArgs(frame)
local title = mw.title.getCurrentTitle()
local lua_config, lua_err = invoke_lua_source(args, frame)
local headContent = lua_config and lua_config.head_content or ''
local isSilent = args.silent ~= nil or (lua_config and lua_config.silent ~= nil)
local isPeriodic = args.interval ~= nil or (lua_config and lua_config.interval ~= nil)
return headContent ..
(isSilent and '' or
"<div style='float: right; padding-left: 15px;'>" ..
'[https://sdzerobot.toolforge.org/database-report?page='..mw.uri.encode(title.prefixedText)..' Update the table now]' ..
'</div>' ..
"<div style='margin-bottom: 5px; border-bottom: 3px solid #2F74D0; padding-right: 10px;'>" ..
"This table is generated by querying the [[wikitech:Help:Toolforge/Database|database replica]] "..(isPeriodic and 'and is periodically updated' or '').." by [[User:SDZeroBot|a bot]].<br> " ..
'<mark>Edits made within the table area will be removed on the next update!</mark>' ..
'</div>')
.. '[[Category:SDZeroBot database report subscriptions]]'
.. (lua_err or validate(args))
end
function validate(args)
-- sql param is required, except if the config is lua-generated
if not args.sql and not args.lua_source then
return error('Invalid config: required parameter "sql" is missing')
end
local deprecated_params = {
['frequency'] = 'interval'
}
-- check for invalid parameters
for param, _ in pairs(args) do
if deprecated_params[param] ~= nil then
return error(param .. ' is deprecated, please use ' .. deprecated_params[param] .. ' instead')
end
if not TableTools.inArray({
'sql', 'wikilinks', 'excerpts', 'comments', 'widths', 'hide', 'silent',
'table_style', 'table_class', 'remove_underscores', 'pagination', 'output_page',
'max_pages', 'interval', 'row_template', 'skip_table', 'header_template',
'footer_template', 'row_template_named_params', 'postprocess_js',
'lua_source', 'lua_function'
}, param) and not param:find('^lua_arg_') == nil then
return error('Unknown parameter "' .. param .. '" used, ignoring')
end
end
-- check incompatible param combinations
if not args.row_template then
if args.skip_table then
return error('Invalid config: skip_table can only be used with row_template')
end
end
if args.output_page then
if args.pagination then
return error('pagination and output_page cannot be used together; output_page will be ignored')
end
local t1 = mw.title.getCurrentTitle()
local t2 = mw.title.new(args.output_page)
if t1.namespace ~= t2.namespace or t2.text:find(t1.text..'/', 1, true) ~= 1 then
return error('output_page must be a subpage of the current page')
end
end
-- check wikilinks config
if args.wikilinks then
local configs = str_split(args.wikilinks, ',')
for _, config in ipairs(configs) do
local parts = str_split(trim(config), ':')
local srcColNum = parts[1]
if not is_integer(srcColNum) then
return error('Invalid wikilink config: Non-numeral source column number: '..parts[1]..'. Will be ignored.')
end
local ns = parts[2]
if ns and ns:match("^c?%d+$") == nil then
return error('Invalid namespace number "'.. ns..'" in wikilinks parameter: refer to [[WP:NS]] for namespace numbers, or use "cN" to take namespace number from column number N')
end
end
end
-- check excerpts config
if args.excerpts then
local configs = str_split(args.excerpts, ',')
for _, config in ipairs(configs) do
local parts = str_split(trim(config), ':')
local srcColNum = parts[1]
if not is_integer(srcColNum) then
return error('Invalid excerpts config: Non-numeral source column number: '..parts[1]..'. Will be ignored.')
end
local dstColNum = parts[2]
if dstColNum and not is_integer(dstColNum) then
return error('Invalid excerpts config: Non-numeral destination column number: '..parts[2]..'. Will be ignored.')
end
local ns = parts[3]
if ns and ns:match("^c?%d+$") == nil then
return error('Invalid namespace number "'.. ns..'" in excerpts parameter: refer to [[WP:NS]] for namespace numbers, or use "cN" to take namespace number from column number N')
end
local charLimit = parts[4]
if charLimit and not is_integer(charLimit) then
return error('Invalid excerpts config: Non-numeral in charLimit: '..parts[4]..'. Will be ignored.')
end
local hardCharLimit = parts[5]
if hardCharLimit and not is_integer(hardCharLimit) then
return error('Invalid excerpts config: Non-numeral in hardCharLimit: '..parts[5]..'. Will be ignored.')
end
end
end
-- check column numbers in widths param
if args.widths then
local configs = str_split(args.widths, ',')
for _, config in ipairs(configs) do
local parts = str_split(trim(config), ':')
local column = parts[1]
if not is_integer(column) then
return error('Invalid widths config: Non-numeral column number: '..parts[1]..'. Will be ignored.')
end
end
end
-- check numeric configs
if args.comments then
local columns = str_split(args.comments, ',')
for _, column in ipairs(columns) do
if not is_integer(trim(column)) then
return error('Invalid comments parameter: Non-numeral column number: '..column..'. Will be ignored.')
end
end
end
if args.remove_underscores then
local columns = str_split(args.remove_underscores, ',')
for _, column in ipairs(columns) do
if not is_integer(trim(column)) then
return error('Invalid remove_underscores parameter: Non-numeral column number: '..column..'. Will be ignored.')
end
end
end
if args.hide then
local columns = str_split(args.hide, ',')
for _, column in ipairs(columns) do
if not is_integer(trim(column)) then
return error('Invalid hide parameter: Non-numeral column number: '..column..'. Will be ignored.')
end
end
end
if args.pagination and not is_integer(args.pagination) then
return error('pagination should be an integer. Will be ignored.')
end
if args.max_pages and not is_integer(args.max_pages) then
return error('max_pages should be an integer. Will be ignored.')
end
if args.interval and not is_integer(args.interval) then
return error('interval should be an integer. Will be ignored.')
end
if args.row_template_named_params and not args.row_template then
return error('row_template_named_params is only applicable when row_template is used')
end
return ''
end
function invoke_lua_source(args, frame)
if not args.lua_source then
return nil, nil
end
local module_title = mw.title.new(args.lua_source, 828)
if module_title == nil or module_title.namespace ~= 828 then
return nil, error('lua_source should be a valid Module title')
end
local module_name = module_title.text
local lua_params = ''
for key, val in pairs(args) do
if key:find('^lua_arg_') then
local argName = string.sub(key, string.len('lua_arg_') + 1)
local argValue = val
lua_params = lua_params .. '|'..argName..'='..argValue
end
end
local invoke = '{{#invoke:'..module_name..'|'..(args.lua_function or 'main')..lua_params..'}}'
local moduleOutput = frame:preprocess(invoke)
-- FIXME: more robust error detection
if moduleOutput:find('error') ~= nil then
return nil, moduleOutput
end
if not moduleOutput:match('^{{') or not moduleOutput:match('}}$') then
-- XXX: Module is returning something other than report:generate(), error out?
return {}, nil
end
local config = {}
-- Parse the configuration text to extract parameter values
for line in moduleOutput:gmatch("[^\n]+") do
local param, value = line:match("^|([^=]+) = (.*)$")
if param and value then
value = trim(value)
if value ~= "" and value ~= "nil" then
config[param] = value
end
end
end
return config, nil
end
return p