8,058 bytes added
, 05:00, 16 July 2021
local checkType = require('libraryUtil').checkType
local mRepr = require('Module:Repr')
local p = {}
local mockTitleRegistry = {}
--[[
-- Capitalize a string.
--]]
local function capitalize(s)
return s:sub(1, 1):upper() .. s:sub(2, -1)
end
--[[
-- Check that a named argument is one of multiple types.
--]]
local function checkTypeForNamedArgMulti(name, argName, arg, expectTypes)
local argType = type(arg)
for _, expectedType in ipairs(expectTypes) do
if argType == expectedType then
return
end
end
error(
string.format(
"bad named argument %s to '%s' (%s expected, got %s)",
argName,
name,
mw.text.listToText(expectTypes, ", ", " or "),
argType
),
3
)
end
--[[
-- Set a property on an object to the given value, if that value is not nil.
--]]
local function maybeSetProperty(object, property, value)
if value ~= nil then
rawset(object, property, value)
end
end
--[[
-- Construct a mock title from a string, an ID or an options table. If we were
-- passed a mock title object to start with, return it as-is.
--]]
local function constructMockTitle(titleOrOptions)
if titleOrOptions == nil then
return nil
end
local titleOrOptionsType = type(titleOrOptions)
if titleOrOptionsType == "string" or titleOrOptionsType == "number" then
return p.MockTitle{page = titleOrOptions}
elseif titleOrOptionsType == "table" then
if type(titleOrOptions.getContent) == "function" then
return titleOrOptions
else
return p.MockTitle(titleOrOptions)
end
else
error(
string.format(
"Invalid type specified to constructMockTitle (expected string, number, table or nil; received %s)",
titleOrOptionsType
),
2
)
end
end
--[[
-- Get a table of protection levels with the levels set by the user. The
-- makeOptionsKey is a function that takes a protection action as an input and
-- gives the option table key for that action. The function returns two values:
-- the table of protection levels (as used by the protectionLevels and
-- cascadingProtection title object properties), and a boolean flag indicating
-- whether any protection levels were set.
--]]
local function getProtectionLevels(options, makeOptionsKey)
local levels = {
edit = {},
move = {},
create = {},
upload = {},
}
local isSet = false
for action in pairs(levels) do
local level = options[makeOptionsKey(action)]
if level then
levels[action][1] = level
isSet = true
end
end
return levels, isSet
end
--[[
-- Set protection levels
--]]
local function setProtectionLevels(title, options)
local protectionLevels, isProtectionSet = getProtectionLevels(
options,
function (action)
return action .. "Protection"
end
)
if isProtectionSet then
rawset(title, "protectionLevels", protectionLevels)
end
end
--[[
-- Set cascading protection
--]]
local function setCascadingProtection(title, options)
local cascadingProtectionLevels, isCascadingProtectionSet = getProtectionLevels(
options,
function (action)
return string.format("cascading%sProtection", capitalize(action))
end
)
local cascadingSourcesExist = options.cascadingProtectionSources and #options.cascadingProtectionSources >= 1
if isCascadingProtectionSet and cascadingSourcesExist then
rawset(
title,
"cascadingProtection",
{
restrictions = cascadingProtectionLevels,
sources = options.cascadingProtectionSources,
}
)
elseif isCascadingProtectionSet then
error("a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty", 2)
elseif cascadingSourcesExist then
error("the cascadingProtectionSources argument was given, but no cascading protection argument was given", 2)
end
end
--[[
-- Set page content, if specified
--]]
local function maybeSetContent(titleObject, content)
if content then
rawset(titleObject, "getContent", function () return content end)
end
end
--[[
-- Set properties in the file table, as well as the fileExists property
--]]
local function setFileProperties(title, options)
if title.file then
for _, property in ipairs{"exists", "width", "height", "pages", "size", "mimeType", "length"} do
local optionName = "file" .. capitalize(property)
maybeSetProperty(title.file, property, options[optionName])
end
end
end
--[[
-- Patch an existing title object with the given options.
--]]
function p.patchTitleObject(title, options)
-- Set simple properties
for _, property in ipairs{"id", "isRedirect", "exists", "contentModel"} do
maybeSetProperty(title, property, options[property])
end
-- Set redirect title
maybeSetProperty(title, "redirectTarget", constructMockTitle(options.redirectTarget))
-- Set complex properties
setProtectionLevels(title, options)
setCascadingProtection(title, options)
maybeSetContent(title, options.content)
setFileProperties(title, options)
return title
end
--[[
-- Construct a new mock title.
--]]
function p.MockTitle(options)
checkType("MockTitle", 1, options, "table")
checkTypeForNamedArgMulti("MockTitle", "page", options.page, {"string", "number"})
local title = mw.title.new(options.page)
return p.patchTitleObject(title, options)
end
--[[
-- Register a mock title.
-- This can be a string, a mock title object or a table of options for
-- MockTitle.
--]]
function p.registerMockTitle(titleOrOptions)
checkType("registerMockTitle", 1, titleOrOptions, "table")
title = constructMockTitle(titleOrOptions)
mockTitleRegistry[title.prefixedText] = title
end
--[[
-- Remove a title from the mock title registry.
-- Returns the title that was removed.
--]]
function p.deregisterMockTitle(titleOrOptions)
checkType("deregisterMockTitle", 1, title, "table")
title = constructMockTitle(titleOrOptions)
registeredTitle = mockTitleRegistry[title.prefixedText]
mockTitleRegistry[title.prefixedText] = nil
return registeredTitle
end
--[[
-- Register multiple mock titles.
--]]
function p.registerMockTitles(...)
checkType("registerMockTitles", 1, titleData, "table")
for _, titleOrOptions in ipairs{...} do
p.registerMockTitle(titleOrOptions)
end
end
--[[
-- Deregister multiple mock titles.
-- Returns a sequence of titles that were removed.
--]]
function p.deregisterMockTitles(...)
checkType("deregisterMockTitles", 1, titleData, "table")
local removedTitles = {}
for _, titleOrOptions in ipairs{...} do
table.insert(removedTitles, p.deregisterMockTitle(titleOrOptions))
end
return removedTitles
end
--[[
-- Clear the mock title registry.
--]]
function p.clearMockTitleRegistry()
mockTitleRegistry = {}
end
--[[
-- Patch the given title function.
-- This replaces the title function with a function that looks in the title
-- registry for the given title.
--]]
local function patchTitleFunc(funcName)
local oldFunc = mw.title[funcName]
mw.title[funcName] = function(...)
local title = oldFunc(...)
if not title then
error(
string.format(
"Could not make title object from invocation %s",
mRepr.invocationRepr{
funcName = "mw.title." .. funcName,
args = {...}
}
),
2
)
end
local mockTitle = mockTitleRegistry[title.prefixedText]
if mockTitle then
return mockTitle
else
return title
end
end
return oldTitleNew
end
--[[
-- Patch mw.title.new.
-- The original mw.title.new is restored after running the given function.
--]]
function p.patchTitleNew(func, ...)
local oldTitleNew = patchTitleFunc("new")
func(...)
mw.title.new = oldTitleNew
end
--[[
-- Patch mw.title.makeTitle.
-- The original mw.title.makeTitle is restored after running the given function.
--]]
function p.patchMakeTitle(func, ...)
local oldMakeTitle = patchTitleFunc("makeTitle")
func(...)
mw.title.makeTitle = oldMakeTitle
end
--[[
-- Patch mw.title.new and mw.title.makeTitle.
-- The original title functions are restored after running the given function.
--]]
function p.patchTitleConstructors(func, ...)
local oldTitleNew = patchTitleFunc("new")
local oldMakeTitle = patchTitleFunc("makeTitle")
func(...)
mw.title.new = oldTitleNew
mw.title.makeTitle = oldMakeTitle
end
return p