https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AMock_title%2FsandboxModule:Mock title/sandbox - Revision history2025-06-03T23:53:49ZRevision history for this page on the wikiMediaWiki 1.45.0-wmf.3https://en.wikipedia.org/w/index.php?title=Module:Mock_title/sandbox&diff=1225448887&oldid=prevMr. Stradivarius: copy from Module:Mock title2024-05-24T14:30:10Z<p>copy from <a href="/wiki/Module:Mock_title" title="Module:Mock title">Module:Mock title</a></p>
<p><b>New page</b></p><div>require('strict')<br />
local libraryUtil = require('libraryUtil')<br />
local checkType = libraryUtil.checkType<br />
local checkTypeMulti = libraryUtil.checkTypeMulti<br />
local mRepr = require('Module:Repr')<br />
<br />
local p = {}<br />
local mockTitleRegistry = {}<br />
local mockCurrentTitle = nil<br />
<br />
-- Keep a reference to the original mw.title.new function so that we can use it<br />
-- when mw.title.new is patched with our custom function.<br />
local titleNew = mw.title.new<br />
<br />
--[[<br />
-- Capitalize a string.<br />
--]]<br />
local function capitalize(s)<br />
return s:sub(1, 1):upper() .. s:sub(2, -1)<br />
end<br />
<br />
--[[<br />
-- Check that a named argument is one of multiple types.<br />
--]]<br />
local function checkTypeForNamedArgMulti(name, argName, arg, expectTypes)<br />
local argType = type(arg)<br />
for _, expectedType in ipairs(expectTypes) do<br />
if argType == expectedType then<br />
return<br />
end<br />
end<br />
error(<br />
string.format(<br />
"bad named argument %s to '%s' (%s expected, got %s)",<br />
argName,<br />
name,<br />
mw.text.listToText(expectTypes, ', ', ' or '),<br />
argType<br />
),<br />
3<br />
)<br />
end<br />
<br />
--[[<br />
-- Set a property on an object to the given value, if that value is not nil.<br />
--]]<br />
local function maybeSetProperty(object, property, value)<br />
if value ~= nil then<br />
rawset(object, property, value)<br />
end<br />
end<br />
<br />
--[[<br />
-- Construct a mock title from a string, an ID or an options table. If we were<br />
-- passed a mock title object to start with, return it as-is.<br />
--]]<br />
local function constructMockTitle(titleOrOptions)<br />
if titleOrOptions == nil then<br />
return nil<br />
end<br />
local titleOrOptionsType = type(titleOrOptions)<br />
if titleOrOptionsType == 'string' or titleOrOptionsType == 'number' then<br />
return p.MockTitle{title = titleOrOptions}<br />
elseif titleOrOptionsType == 'table' then<br />
if type(titleOrOptions.getContent) == 'function' then<br />
return titleOrOptions<br />
else<br />
return p.MockTitle(titleOrOptions)<br />
end<br />
else<br />
error(<br />
string.format(<br />
'Invalid type specified to constructMockTitle (expected string, number, table or nil; received %s)',<br />
titleOrOptionsType<br />
),<br />
2<br />
)<br />
end<br />
end<br />
<br />
--[[<br />
-- Get a table of protection levels with the levels set by the user. The<br />
-- makeOptionsKey is a function that takes a protection action as an input and<br />
-- gives the option table key for that action. The function returns two values:<br />
-- the table of protection levels (as used by the protectionLevels and<br />
-- cascadingProtection title object properties), and a boolean flag indicating<br />
-- whether any protection levels were set.<br />
--]]<br />
local function getProtectionLevels(options, makeOptionsKey)<br />
local levels = {<br />
edit = {},<br />
move = {},<br />
create = {},<br />
upload = {},<br />
}<br />
local isSet = false<br />
for action in pairs(levels) do<br />
local level = options[makeOptionsKey(action)]<br />
if level then<br />
levels[action][1] = level<br />
isSet = true<br />
end<br />
end<br />
return levels, isSet<br />
end<br />
<br />
--[[<br />
-- Set protection levels<br />
--]]<br />
local function setProtectionLevels(title, options)<br />
local protectionLevels, isProtectionSet = getProtectionLevels(<br />
options,<br />
function (action)<br />
return action .. 'Protection'<br />
end<br />
)<br />
if isProtectionSet then<br />
rawset(title, 'protectionLevels', protectionLevels)<br />
end<br />
end<br />
<br />
--[[<br />
-- Set cascading protection<br />
--]]<br />
local function setCascadingProtection(title, options)<br />
local cascadingProtectionLevels, isCascadingProtectionSet = getProtectionLevels(<br />
options,<br />
function (action)<br />
return string.format('cascading%sProtection', capitalize(action))<br />
end<br />
)<br />
local cascadingSourcesExist = options.cascadingProtectionSources and #options.cascadingProtectionSources >= 1<br />
if isCascadingProtectionSet and cascadingSourcesExist then<br />
rawset(<br />
title,<br />
'cascadingProtection',<br />
{<br />
restrictions = cascadingProtectionLevels,<br />
sources = options.cascadingProtectionSources,<br />
}<br />
)<br />
elseif isCascadingProtectionSet then<br />
error('a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty', 2)<br />
elseif cascadingSourcesExist then<br />
error('the cascadingProtectionSources argument was given, but no cascading protection argument was given', 2)<br />
end<br />
end<br />
<br />
--[[<br />
-- Set page content, if specified<br />
--]]<br />
local function maybeSetContent(titleObject, content)<br />
if content then<br />
rawset(titleObject, 'getContent', function () return content end)<br />
end<br />
end<br />
<br />
--[[<br />
-- Set properties in the file table, as well as the fileExists property<br />
--]]<br />
local function setFileProperties(title, options)<br />
if title.file then<br />
for _, property in ipairs{'exists', 'width', 'height', 'pages', 'size', 'mimeType', 'length'} do<br />
local optionName = 'file' .. capitalize(property)<br />
maybeSetProperty(title.file, property, options[optionName])<br />
end<br />
end<br />
end<br />
<br />
--[[<br />
-- Changes the associated titles to be patched if applicable<br />
--]]<br />
local function patchAssociatedTitleObjects(title)<br />
for _, property in ipairs{'basePageTitle', 'rootPageTitle', 'talkPageTitle', 'subjectPageTitle'} do<br />
local mockTitle = mockTitleRegistry[title[property] and title[property].prefixedText]<br />
if mockTitle then<br />
rawset(title, property, mockTitle)<br />
end<br />
end<br />
rawset(title, 'subPageTitle', function(text)<br />
return mw.title.makeTitle( title.namespace, title.text .. '/' .. text )<br />
end)<br />
end<br />
<br />
--[[<br />
-- Patch an existing title object with the given options.<br />
--]]<br />
function p.patchTitleObject(title, options)<br />
checkType('patchTitleObject', 1, title, 'table')<br />
checkType('patchTitleObject', 2, options, 'table')<br />
<br />
-- Set simple properties<br />
for _, property in ipairs{'id', 'isRedirect', 'exists', 'contentModel'} do<br />
maybeSetProperty(title, property, options[property])<br />
end<br />
<br />
-- Set redirect title<br />
maybeSetProperty(title, 'redirectTarget', constructMockTitle(options.redirectTarget))<br />
<br />
-- Set complex properties<br />
setProtectionLevels(title, options)<br />
setCascadingProtection(title, options)<br />
maybeSetContent(title, options.content)<br />
setFileProperties(title, options)<br />
<br />
return title<br />
end<br />
<br />
--[[<br />
-- Construct a new mock title.<br />
--]]<br />
function p.MockTitle(options)<br />
checkType('MockTitle', 1, options, 'table')<br />
checkTypeForNamedArgMulti('MockTitle', 'title', options.title, {'string', 'number'})<br />
-- Create the title object with the original mw.title.new so that we don't<br />
-- look in the mock title registry here when mw.title.new is patched.<br />
local title = titleNew(options.title)<br />
return p.patchTitleObject(title, options)<br />
end<br />
<br />
--[[<br />
-- Register a mock title.<br />
-- This can be a mock title object or a table of options for MockTitle.<br />
--]]<br />
function p.registerMockTitle(titleOrOptions)<br />
checkType('registerMockTitle', 1, titleOrOptions, 'table')<br />
local title = constructMockTitle(titleOrOptions)<br />
mockTitleRegistry[title.prefixedText] = title<br />
end<br />
<br />
--[[<br />
-- Remove a title from the mock title registry.<br />
-- Returns the title that was removed.<br />
--]]<br />
function p.deregisterMockTitle(titleOrOptions)<br />
checkTypeMulti('deregisterMockTitle', 1, titleOrOptions, {'number', 'string', 'table'})<br />
local title = constructMockTitle(titleOrOptions)<br />
if not title then<br />
return nil<br />
end<br />
local registeredTitle = mockTitleRegistry[title.prefixedText]<br />
mockTitleRegistry[title.prefixedText] = nil<br />
return registeredTitle<br />
end<br />
<br />
--[[<br />
-- Register multiple mock titles.<br />
--]]<br />
function p.registerMockTitles(...)<br />
for i, titleOrOptions in ipairs{...} do<br />
checkType('registerMockTitles', i, titleOrOptions, 'table')<br />
p.registerMockTitle(titleOrOptions)<br />
end<br />
end<br />
<br />
--[[<br />
-- Deregister multiple mock titles.<br />
-- Returns a sequence of titles that were removed.<br />
--]]<br />
function p.deregisterMockTitles(...)<br />
local removedTitles = {}<br />
for i, titleOrOptions in ipairs{...} do<br />
checkTypeMulti('deregisterMockTitles', i, titleOrOptions, {'number', 'string', 'table'})<br />
table.insert(removedTitles, p.deregisterMockTitle(titleOrOptions))<br />
end<br />
return removedTitles<br />
end<br />
<br />
--[[<br />
-- Clear the mock title registry.<br />
--]]<br />
function p.clearMockTitleRegistry()<br />
mockTitleRegistry = {}<br />
end<br />
<br />
--[[<br />
-- Register a mock title as the current title.<br />
-- This can be a string, a mock title object or a table of options for<br />
-- MockTitle.<br />
--]]<br />
function p.registerMockCurrentTitle(titleOrOptions)<br />
checkType('registerMockCurrentTitle', 1, titleOrOptions, 'table')<br />
local title = constructMockTitle(titleOrOptions)<br />
mockCurrentTitle = title<br />
end<br />
<br />
--[[<br />
-- Deregister the registered current mock title.<br />
--]]<br />
function p.deregisterMockCurrentTitle()<br />
mockCurrentTitle = nil<br />
end<br />
<br />
--[[<br />
-- Clear all registered mock titles.<br />
-- This clears the mock title registry and deregisters the current mock title.<br />
--]]<br />
function p.clearAllMockTitles()<br />
p.clearMockTitleRegistry()<br />
p.deregisterMockCurrentTitle()<br />
end<br />
<br />
--[[<br />
-- Look up a title in the mock title registry.<br />
--]]<br />
local function lookUpTitleInRegistry(title)<br />
return mockTitleRegistry[title.prefixedText]<br />
end<br />
<br />
--[[<br />
-- Look up the registered mock current title.<br />
--]]<br />
local function lookUpMockCurrentTitle()<br />
return mockCurrentTitle<br />
end<br />
<br />
--[[<br />
-- Patch the given title function.<br />
-- This replaces the title function with a function that looks up the title<br />
-- from some source. Exactly how the title is looked up is determined by the<br />
-- lookUpMockTitle argument. This should be a function that takes a title object<br />
-- as input and returns a mock title object as output.<br />
--]]<br />
local function patchTitleFunc(funcName, lookUpMockTitle)<br />
local oldFunc = mw.title[funcName]<br />
mw.title[funcName] = function(...)<br />
local title = oldFunc(...)<br />
if not title then<br />
error(<br />
string.format(<br />
'Could not make title object from invocation %s',<br />
mRepr.invocationRepr{<br />
funcName = 'mw.title.' .. funcName,<br />
args = {...}<br />
}<br />
),<br />
2<br />
)<br />
end<br />
local mockTitle = lookUpMockTitle(title)<br />
-- This type of patching has to be applied to all titles and after all<br />
-- mocks are defined to ensure it works<br />
if mockTitle then<br />
patchAssociatedTitleObjects(mockTitle)<br />
return mockTitle<br />
else<br />
patchAssociatedTitleObjects(title)<br />
return title<br />
end<br />
end<br />
return oldFunc<br />
end<br />
<br />
--[[<br />
-- Handle the patch process.<br />
-- This takes care of setting up before the patch, running the specified<br />
-- function after the patch, tearing down the patch, and returning the results.<br />
-- The setup process is handled using the setup function, which takes no<br />
-- parameters. Teardown is handled by the teardown function, which takes the<br />
-- values returned by the setup function as input. The user-supplied function<br />
-- is passed in as the func argument, which takes the remaining parameters as<br />
-- arguments.<br />
--]]<br />
local function patch(setup, teardown, func, ...)<br />
local setupResults = {setup()}<br />
local results = {pcall(func, ...)}<br />
local success = table.remove(results, 1)<br />
teardown(unpack(setupResults))<br />
if success then<br />
return unpack(results)<br />
else<br />
error(results[1], 3)<br />
end<br />
end<br />
<br />
--[[<br />
-- Patch mw.title.new.<br />
-- The original mw.title.new is restored after running the given function.<br />
--]]<br />
function p.patchTitleNew(func, ...)<br />
checkType('patchTitleNew', 1, func, 'function')<br />
<br />
local function setup()<br />
return patchTitleFunc('new', lookUpTitleInRegistry)<br />
end<br />
<br />
local function teardown(oldTitleNew)<br />
mw.title.new = oldTitleNew<br />
end<br />
<br />
return patch(<br />
setup,<br />
teardown,<br />
func,<br />
...<br />
)<br />
end<br />
<br />
--[[<br />
-- Patch mw.title.makeTitle.<br />
-- The original mw.title.makeTitle is restored after running the given function.<br />
--]]<br />
function p.patchMakeTitle(func, ...)<br />
checkType('patchMakeTitle', 1, func, 'function')<br />
<br />
local function setup()<br />
return patchTitleFunc('makeTitle', lookUpTitleInRegistry)<br />
end<br />
<br />
local function teardown(oldMakeTitle)<br />
mw.title.makeTitle = oldMakeTitle<br />
end<br />
<br />
return patch(<br />
setup,<br />
teardown,<br />
func,<br />
...<br />
)<br />
end<br />
<br />
--[[<br />
-- Patch mw.title.getCurrentTitle.<br />
-- The original mw.title.getCurrentTitle is restored after running the given<br />
-- function.<br />
--]]<br />
function p.patchGetCurrentTitle(func, ...)<br />
checkType('patchGetCurrentTitle', 1, func, 'function')<br />
<br />
local function setup()<br />
return patchTitleFunc('getCurrentTitle', lookUpMockCurrentTitle)<br />
end<br />
<br />
local function teardown(oldGetCurrentTitle)<br />
mw.title.getCurrentTitle = oldGetCurrentTitle<br />
end<br />
<br />
return patch(<br />
setup,<br />
teardown,<br />
func,<br />
...<br />
)<br />
end<br />
<br />
--[[<br />
-- Patch mw.title.new, mw.title.makeTitle and mw.title.getCurrentTitle.<br />
-- The original title functions are restored after running the given function.<br />
--]]<br />
function p.patchTitleConstructors(func, ...)<br />
checkType('patchTitleConstructors', 1, func, 'function')<br />
<br />
local function setup()<br />
local oldTitleNew = patchTitleFunc('new', lookUpTitleInRegistry)<br />
local oldMakeTitle = patchTitleFunc('makeTitle', lookUpTitleInRegistry)<br />
local oldGetCurrentTitle = patchTitleFunc('getCurrentTitle', lookUpMockCurrentTitle)<br />
return oldTitleNew, oldMakeTitle, oldGetCurrentTitle<br />
end<br />
<br />
local function teardown(oldTitleNew, oldMakeTitle, oldGetCurrentTitle)<br />
mw.title.new = oldTitleNew<br />
mw.title.makeTitle = oldMakeTitle<br />
mw.title.getCurrentTitle = oldGetCurrentTitle<br />
end<br />
<br />
return patch(<br />
setup,<br />
teardown,<br />
func,<br />
...<br />
)<br />
end<br />
<br />
return p</div>Mr. Stradivarius