跳转到内容

模組:Database report/facade

维基百科,自由的百科全书
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://pexbot.toolforge.org/database-report?page=' .. mw.uri.encode(title.prefixedText) .. ' 立即更新]' ..
			'</div>' ..
			"<div style='margin-bottom: 5px; border-bottom: 3px solid #2F74D0; padding-right: 10px;'>" ..
			"本表格通过查询[[Wikitech:Help:Toolforge/Database|数据库副本]]生成,由[[User:PexBot|机器人]]" .. (isPeriodic and '定期自动更新。<br>' or '维护。<br>') ..
			'<mark>在表格区域内进行的编辑将在下次更新时被覆盖!</mark>' ..
			'</div>')
		.. '[[Category:PexBot数据库报告订阅]]'
		.. (lua_err or validate(args))
end

function validate(args)
	
	-- sql 参数是必需的,除非使用 lua_source
	if not args.sql and not args.lua_source then
		return error('配置无效:缺少必需参数 "sql"')
	end
	
	local deprecated_params = {
		['frequency'] = 'interval'
	}
	
	-- 检查无效参数
	for param, _ in pairs(args) do
		if deprecated_params[param] ~= nil then
			return error(param .. ' 已弃用,请改用 ' .. deprecated_params[param])
		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_') then
			return error('使用了未知参数 "' .. param .. '",将被忽略')
		end
	end
	
	-- 检查不兼容的参数组合
	if not args.row_template then
		if args.skip_table then 
			return error('配置无效:skip_table 仅可在使用 row_template 时启用')
		end 
	end
	
	if args.output_page then
		if args.pagination then 
			return error('pagination 与 output_page 不能同时使用;output_page 将被忽略')
		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 必须是当前页面的子页面')
		end
	end

	-- 检查 wikilinks 配置
	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('wikilinks 配置错误:源列编号非数字:'..parts[1]..',将被忽略。')
			end
			local ns = parts[2]
			if ns and ns:match("^c?%d+$") == nil then 
				return error('wikilinks 参数中的命名空间编号 "'.. ns..'" 无效:请参考 [[Wikipedia:命名空间]] 获取命名空间编号,或使用 "cN" 表示从第 N 列读取命名空间编号')
			end 
		end 
	end
	
	-- 检查 excerpts 配置
	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('excerpts 配置错误:源列编号非数字:'..parts[1]..',将被忽略。')
			end
			local dstColNum = parts[2]
			if dstColNum and not is_integer(dstColNum) then
				return error('excerpts 配置错误:目标列编号非数字:'..parts[2]..',将被忽略。')
			end
			local ns = parts[3]
			if ns and ns:match("^c?%d+$") == nil then 
				return error('excerpts 参数中的命名空间编号 "'.. ns..'" 无效:请参考 [[Wikipedia:命名空间]],或使用 "cN" 表示从第 N 列读取命名空间编号')
			end 
			local charLimit = parts[4]
			if charLimit and not is_integer(charLimit) then 
				return error('excerpts 配置错误:charLimit 非数字:'..parts[4]..',将被忽略。')
			end 
			local hardCharLimit = parts[5]
			if hardCharLimit and not is_integer(hardCharLimit) then 
				return error('excerpts 配置错误:hardCharLimit 非数字:'..parts[5]..',将被忽略。')
			end 
		end 
	end
	
	-- 检查 widths 参数中的列号
	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('widths 配置错误:列编号非数字:'..parts[1]..',将被忽略。')
			end
		end 
	end
	
	-- 检查数字类型参数
	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('comments 参数错误:列编号非数字:'..column..',将被忽略。')
			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('remove_underscores 参数错误:列编号非数字:'..column..',将被忽略。')
			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('hide 参数错误:列编号非数字:'..column..',将被忽略。')
			end
		end
	end
	if args.pagination and not is_integer(args.pagination) then 
		return error('pagination 应为整数,将被忽略。')
	end 
	if args.max_pages and not is_integer(args.max_pages) then
		return error('max_pages 应为整数,将被忽略。')
	end 
	if args.interval and not is_integer(args.interval) then
		return error('interval 应为整数,将被忽略。')
	end 
	if args.row_template_named_params and not args.row_template then
		return error('row_template_named_params 仅在使用 row_template 时有效')
	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 应为有效的模块名称(Module 命名空间)')
	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: 更健壮的错误检测
	if moduleOutput:find('error') ~= nil then
		return nil, moduleOutput
	end
	
	if not moduleOutput:match('^{{') or not moduleOutput:match('}}$') then
		-- XXX: 模块返回的不是 report:generate() 格式,是否报错?
		return {}, nil
	end
	local config = {}
	-- 解析配置文本以提取参数值
	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