https://en.wikipedia.org/w/index.php?action=history&feed=atom&title=Module%3AMock_title%2Fsandbox Module:Mock title/sandbox - Revision history 2025-06-03T23:53:49Z Revision history for this page on the wiki MediaWiki 1.45.0-wmf.3 https://en.wikipedia.org/w/index.php?title=Module:Mock_title/sandbox&diff=1225448887&oldid=prev Mr. Stradivarius: copy from Module:Mock title 2024-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(&#039;strict&#039;)<br /> local libraryUtil = require(&#039;libraryUtil&#039;)<br /> local checkType = libraryUtil.checkType<br /> local checkTypeMulti = libraryUtil.checkTypeMulti<br /> local mRepr = require(&#039;Module:Repr&#039;)<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 /> &quot;bad named argument %s to &#039;%s&#039; (%s expected, got %s)&quot;,<br /> argName,<br /> name,<br /> mw.text.listToText(expectTypes, &#039;, &#039;, &#039; or &#039;),<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 == &#039;string&#039; or titleOrOptionsType == &#039;number&#039; then<br /> return p.MockTitle{title = titleOrOptions}<br /> elseif titleOrOptionsType == &#039;table&#039; then<br /> if type(titleOrOptions.getContent) == &#039;function&#039; then<br /> return titleOrOptions<br /> else<br /> return p.MockTitle(titleOrOptions)<br /> end<br /> else<br /> error(<br /> string.format(<br /> &#039;Invalid type specified to constructMockTitle (expected string, number, table or nil; received %s)&#039;,<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 .. &#039;Protection&#039;<br /> end<br /> )<br /> if isProtectionSet then<br /> rawset(title, &#039;protectionLevels&#039;, 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(&#039;cascading%sProtection&#039;, capitalize(action))<br /> end<br /> )<br /> local cascadingSourcesExist = options.cascadingProtectionSources and #options.cascadingProtectionSources &gt;= 1<br /> if isCascadingProtectionSet and cascadingSourcesExist then<br /> rawset(<br /> title,<br /> &#039;cascadingProtection&#039;,<br /> {<br /> restrictions = cascadingProtectionLevels,<br /> sources = options.cascadingProtectionSources,<br /> }<br /> )<br /> elseif isCascadingProtectionSet then<br /> error(&#039;a cascading protection argument was given but the cascadingProtectionSources argument was missing or empty&#039;, 2)<br /> elseif cascadingSourcesExist then<br /> error(&#039;the cascadingProtectionSources argument was given, but no cascading protection argument was given&#039;, 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, &#039;getContent&#039;, 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{&#039;exists&#039;, &#039;width&#039;, &#039;height&#039;, &#039;pages&#039;, &#039;size&#039;, &#039;mimeType&#039;, &#039;length&#039;} do<br /> local optionName = &#039;file&#039; .. 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{&#039;basePageTitle&#039;, &#039;rootPageTitle&#039;, &#039;talkPageTitle&#039;, &#039;subjectPageTitle&#039;} 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, &#039;subPageTitle&#039;, function(text)<br /> return mw.title.makeTitle( title.namespace, title.text .. &#039;/&#039; .. 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(&#039;patchTitleObject&#039;, 1, title, &#039;table&#039;)<br /> checkType(&#039;patchTitleObject&#039;, 2, options, &#039;table&#039;)<br /> <br /> -- Set simple properties<br /> for _, property in ipairs{&#039;id&#039;, &#039;isRedirect&#039;, &#039;exists&#039;, &#039;contentModel&#039;} do<br /> maybeSetProperty(title, property, options[property])<br /> end<br /> <br /> -- Set redirect title<br /> maybeSetProperty(title, &#039;redirectTarget&#039;, 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(&#039;MockTitle&#039;, 1, options, &#039;table&#039;)<br /> checkTypeForNamedArgMulti(&#039;MockTitle&#039;, &#039;title&#039;, options.title, {&#039;string&#039;, &#039;number&#039;})<br /> -- Create the title object with the original mw.title.new so that we don&#039;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(&#039;registerMockTitle&#039;, 1, titleOrOptions, &#039;table&#039;)<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(&#039;deregisterMockTitle&#039;, 1, titleOrOptions, {&#039;number&#039;, &#039;string&#039;, &#039;table&#039;})<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(&#039;registerMockTitles&#039;, i, titleOrOptions, &#039;table&#039;)<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(&#039;deregisterMockTitles&#039;, i, titleOrOptions, {&#039;number&#039;, &#039;string&#039;, &#039;table&#039;})<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(&#039;registerMockCurrentTitle&#039;, 1, titleOrOptions, &#039;table&#039;)<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 /> &#039;Could not make title object from invocation %s&#039;,<br /> mRepr.invocationRepr{<br /> funcName = &#039;mw.title.&#039; .. 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(&#039;patchTitleNew&#039;, 1, func, &#039;function&#039;)<br /> <br /> local function setup()<br /> return patchTitleFunc(&#039;new&#039;, 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(&#039;patchMakeTitle&#039;, 1, func, &#039;function&#039;)<br /> <br /> local function setup()<br /> return patchTitleFunc(&#039;makeTitle&#039;, 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(&#039;patchGetCurrentTitle&#039;, 1, func, &#039;function&#039;)<br /> <br /> local function setup()<br /> return patchTitleFunc(&#039;getCurrentTitle&#039;, 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(&#039;patchTitleConstructors&#039;, 1, func, &#039;function&#039;)<br /> <br /> local function setup()<br /> local oldTitleNew = patchTitleFunc(&#039;new&#039;, lookUpTitleInRegistry)<br /> local oldMakeTitle = patchTitleFunc(&#039;makeTitle&#039;, lookUpTitleInRegistry)<br /> local oldGetCurrentTitle = patchTitleFunc(&#039;getCurrentTitle&#039;, 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