Module:Documentation
From D&D 5e
Documentation for this module may be created at Module:Documentation/doc
-- This module implements {{documentation}}. -- Get required modules. local getArgs = require('Module:Arguments').getArgs local htmlBuilder = require('Module:HtmlBuilder') local messageBox = require('Module:Message box') -- Get the config table. local cfg = mw.loadData('Module:Documentation/config') local p = {} -- Often-used functions. local gsub = mw.ustring.gsub ---------------------------------------------------------------------------- -- Helper functions -- -- These are defined as local functions, but are made available in the p -- table for testing purposes. ---------------------------------------------------------------------------- local function message(cfgKey, expectType, valArray) --[[ -- Gets a message from the cfg table and formats it if appropriate. -- The function raises an error if the value from the cfg table is not -- of the type expectType. -- If the table valArray is present, strings such as $1, $2 etc. in the -- message are substituted with values from the table keys [1], [2] etc. -- For example, if the message cfg.fooMessage had the value 'Foo $2 bar $1.', -- message('fooMessage', 'string', {'baz', 'qux'}) would return "Foo qux bar baz." --]] local msg = cfg[cfgKey] if expectType and type(msg) ~= expectType then error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2) end if not valArray then return msg end local function getMessageVal(match) match = tonumber(match) return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4) end local ret = gsub(msg, '$([1-9][0-9]*)', getMessageVal) return ret end p.message = message local function makeWikilink(page, display) if display then return mw.ustring.format('[[%s|%s]]', page, display) else return mw.ustring.format('[[%s]]', page) end end p.makeWikilink = makeWikilink local function makeCategoryLink(cat, sort) local catns = mw.site.namespaces[14].name return makeWikilink(catns .. ':' .. cat, sort) end p.makeCategoryLink = makeCategoryLink local function makeUrlLink(url, display) return mw.ustring.format('[%s %s]', url, display) end p.makeUrlLink = makeUrlLink local function makeToolbar(...) local ret = {} local lim = select('#', ...) if lim < 1 then return nil end for i = 1, lim do ret[#ret + 1] = select(i, ...) end return '<small style="font-style: normal;">(' .. table.concat(ret, ' | ') .. ')</small>' end p.makeToolbar = makeToolbar local function err(msg) return string.format( '<strong class="error">%s %s</strong>%s', message('errorPrefix', 'string'), msg, makeCategoryLink(message('errorCategory', 'string')) ) end p.err = err ---------------------------------------------------------------------------- -- Argument processing ---------------------------------------------------------------------------- local function makeInvokeFunc(funcName) return function (frame) local args = getArgs(frame, { valueFunc = function (key, value) if type(value) == 'string' then value = value:match('^%s*(.-)%s*$') -- Remove whitespace. if key == 'heading' or value ~= '' then return value else return nil end else return value end end }) return p[funcName](args) end end ---------------------------------------------------------------------------- -- Main function ---------------------------------------------------------------------------- p.main = makeInvokeFunc('_main') function p._main(args) local env = p.getEnvironment(args) local root = htmlBuilder.create() root .wikitext(p.protectionTemplate(env)) .wikitext(p.sandboxNotice(args, env)) -- This div tag is from {{documentation/start box}}, but moving it here -- so that we don't have to worry about unclosed tags. .tag('div') .attr('id', message('mainDivId', 'string')) .addClass(message('mainDivClasses', 'string')) .newline() .wikitext(p._startBox(args, env)) .wikitext(p._content(args, env)) .tag('div') .css('clear', 'both') -- So right or left floating items don't stick out of the doc box. .newline() .done() .done() .wikitext(p._endBox(args, env)) .newline() .wikitext(p.addTrackingCategories(env)) return tostring(root) end ---------------------------------------------------------------------------- -- Environment settings ---------------------------------------------------------------------------- function p.getEnvironment(args) -- Returns a table with information about the environment, including the title to use, the subject namespace, etc. -- This is called from p._main using pcall in case we get any errors from exceeding the expensive function count -- limit, or other perils unknown. -- -- Data includes: -- env.title - the title object of the page we are making documentation for (usually the current title) -- env.subjectSpace - the number of the title's subject namespace. -- env.docspace - the name of the namespace the title puts its documentation in. -- env.templatePage - the name of the template page with no namespace or interwiki prefixes. local env, envFuncs = {}, {} -- Set up the metatable. If a nil value is called, we call that function in the envFuncs table and memoize it -- in the env table so we don't have to call any of the functions more than once. setmetatable(env, { __index = function (t, key) local envFunc = envFuncs[key] if envFunc then local success, val = pcall(envFunc) if success then env[key] = val -- Memoise the value. return val end end return nil end }) function envFuncs.title() -- The title object for the current page, or a test page passed with args.page. local title local titleArg = args.page if titleArg then title = mw.title.new(titleArg) if not title then error(message('titleArgError', 'string', {titleArg})) end else title = mw.title.getCurrentTitle() end return title end function envFuncs.subjectSpace() -- The subject namespace number. return mw.site.namespaces[env.title.namespace].subject.id end function envFuncs.docspace() -- The name of the documentation namespace. local subjectSpace = env.subjectSpace if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then -- Pages in the Article, File, MediaWiki or Category namespaces must have their -- /doc, /sandbox and /testcases pages in talk space. return mw.site.namespaces[subjectSpace].talk.name else return env.title.subjectNsText end end function envFuncs.templatePage() -- The template page with no namespace or interwiki prefixes. local title = env.title local subpage = title.subpageText if subpage == message('sandboxSubpage', 'string') or subpage == message('testcasesSubpage', 'string') then return title.baseText else return title.text end end function envFuncs.templateTitle() -- The template (or module, etc.) title object. local title = env.title local subpage = title.subpageText if subpage == message('sandboxSubpage', 'string') or subpage == message('testcasesSubpage', 'string') then return title.basePageTitle else return title end end function envFuncs.docTitle() -- Title object of the /doc subpage. local title = env.title local docname = args[1] -- User-specified doc page. local docpage if docname then docpage = docname else docpage = env.docpageRoot .. '/' .. message('docSubpage', 'string') end return mw.title.new(docpage) end function envFuncs.docpageRoot() -- The base page of the /doc, /sandbox, and /testcases subpages. -- For some namespaces this is the talk page, rather than the template page. local title = env.title return (env.docspace or title.nsText) .. ':' .. (env.templatePage or title.text) end function envFuncs.sandboxTitle() -- Title object for the /sandbox subpage. return mw.title.new(env.docpageRoot .. '/' .. message('sandboxSubpage', 'string')) end function envFuncs.testcasesTitle() -- Title object for the /testcases subpage. return mw.title.new(env.docpageRoot .. '/' .. message('testcasesSubpage', 'string')) end function envFuncs.printTitle() -- Title object for the /Print subpage. return mw.title.new(env.templatePage .. '/' .. message('printSubpage', 'string')) end return env end ---------------------------------------------------------------------------- -- Auxiliary templates ---------------------------------------------------------------------------- function p.sandboxNotice(args, env) local sandboxNoticeTemplate = message('sandboxNoticeTemplate', 'string') if not (sandboxNoticeTemplate and env.title.subpageText == message('sandboxSubpage', 'string')) then return nil end local frame = mw.getCurrentFrame() local notice = htmlBuilder.create() notice .tag('div') .css('clear', 'both') .done() .wikitext(frame:expandTemplate{title = sandboxNoticeTemplate, args = {[message('sandboxNoticeLivepageParam')] = args.livepage}}) return tostring(notice) end function p.protectionTemplate(env) local title = env.title local protectionTemplate = message('protectionTemplate', 'string') if not (protectionTemplate and title.namespace == 10) then -- Don't display the protection template if we are not in the template namespace. return nil end local frame = mw.getCurrentFrame() local function getProtectionLevel(protectionType, page) -- Gets the protection level for page, or for the current page if page is not specified. local level = frame:callParserFunction('PROTECTIONLEVEL', protectionType, page) if level ~= '' then return level else return nil -- The parser function returns the blank string if there is no match. end end local prefixedTitle = title.prefixedText if getProtectionLevel('move', prefixedTitle) == 'sysop' or getProtectionLevel('edit', prefixedTitle) then -- The page is full-move protected, or full, template, or semi-protected. return frame:expandTemplate{title = protectionTemplate, args = message('protectionTemplateArgs', 'table')} end return nil end ---------------------------------------------------------------------------- -- Start box ---------------------------------------------------------------------------- p.startBox = makeInvokeFunc('_startBox') function p._startBox(args, env) -- Generate [view][edit][history][purge] or [create] links. local links local content = args.content if not content then -- No need to include the links if the documentation is on the template page itself. local linksData = p.makeStartBoxLinksData(args, env) if type(linksData) == 'table' then links = p.renderStartBoxLinks(linksData) else -- linksData is nil or an error message. return linksData end end -- Generate the start box html. local data = p.makeStartBoxData(args, env, links) if type(data) == 'table' then return p.renderStartBox(data) elseif type(data) == 'string' then -- data is an error message. return data else -- User specified no heading. return nil end end function p.makeStartBoxLinksData(args, env) local data = {} -- Get title objects. local title = env.title local docTitle = env.docTitle if not title or not docTitle then return nil end -- View, display, edit, and purge links if /doc exists. data.viewLinkDisplay = message('viewLinkDisplay', 'string') data.editLinkDisplay = message('editLinkDisplay', 'string') data.historyLinkDisplay = message('historyLinkDisplay', 'string') data.purgeLinkDisplay = message('purgeLinkDisplay', 'string') -- Create link if /doc doesn't exist. local preload = args.preload if not preload then if env.subjectSpace == 6 then -- File namespace preload = message('fileDocpagePreload', 'string') else preload = message('docpagePreload', 'string') end end data.preload = preload data.createLinkDisplay = message('createLinkDisplay', 'string') return data end function p.renderStartBoxLinks(data) -- Render the [view][edit][history][purge] or [create] links. local ret local docTitle = data.docTitle local title = data.title if docTitle.exists then local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay) local editLink = makeUrlLink(docTitle:fullUrl{action = 'edit'}, data.editLinkDisplay) local historyLink = makeUrlLink(docTitle:fullUrl{action = 'history'}, data.historyLinkDisplay) local purgeLink = makeUrlLink(title:fullUrl{action = 'purge'}, data.purgeLinkDisplay) ret = '[%s] [%s] [%s] [%s]' ret = ret:gsub('%[', '[') -- Replace square brackets with HTML entities. ret = ret:gsub('%]', ']') ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink) else ret = makeUrlLink(docTitle:fullUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay) end end function p.makeStartBoxData(args, env, links) local subjectSpace = env.subjectSpace if not subjectSpace then return nil end local data = {} -- Heading local heading = args.heading -- Blank values are not removed. if heading == '' then -- Don't display the start box if the heading arg is defined but blank. return nil end if heading then data.heading = heading elseif subjectSpace == 10 then -- Template namespace data.heading = message('documentationIconWikitext', 'string') .. ' ' .. message('templateNamespaceHeading', 'string') elseif subjectSpace == 828 then -- Module namespace data.heading = message('documentationIconWikitext', 'string') .. ' ' .. message('moduleNamespaceHeading', 'string') elseif subjectSpace == 6 then -- File namespace data.heading = message('fileNamespaceHeading', 'string') else data.heading = message('otherNamespacesHeading', 'string') end -- Heading CSS local headingStyle = args['heading-style'] if headingStyle then data.headingStyleText = headingStyle elseif subjectSpace == 10 then -- We are in the template or template talk namespaces. data.headingFontWeight = 'bold' data.headingFontSize = '125%' else data.headingFontSize = '150%' end -- [view][edit][history][purge] or [create] links. if links then data.linksClass = message('startBoxLinkclasses', 'string') data.linksId = message('startBoxLinkId', 'string') data.links = links end return data end function p.renderStartBox(data) -- Renders the start box html. local sbox = htmlBuilder.create('div') sbox .css('padding-bottom', '3px') .css('border-bottom', '1px solid #aaa') .css('margin-bottom', '1ex') .newline() .tag('span') .cssText(data.headingStyleText) .css('font-weight', data.headingFontWeight) .css('font-size', data.headingFontSize) .wikitext(data.heading) local links = data.links if links then sbox.tag('span') .addClass(data.linksClass) .attr('id', data.linksId) .wikitext(links) end return tostring(sbox) end ---------------------------------------------------------------------------- -- Documentation content ---------------------------------------------------------------------------- p.content = makeInvokeFunc('_content') function p._content(args, env) -- Get the /doc title object local docTitle = env.docTitle if not docTitle then return nil end -- Get the documentation content. local content = args.content if not content and docTitle.exists then local frame = mw.getCurrentFrame() content = frame:preprocess('{{ ' .. docTitle.prefixedText .. ' }}') end -- The line breaks below are necessary so that "=== Headings ===" at the start and end -- of docs are interpreted correctly. return '\n' .. (content or '') .. '\n' end ---------------------------------------------------------------------------- -- End box ---------------------------------------------------------------------------- p.endBox = makeInvokeFunc('_endBox') function p._endBox(args, env) -- This function generates the end box (also known as the link box). -- Get environment data. local subjectSpace = env.subjectSpace local docTitle = env.docTitle if not subjectSpace or not docTitle then return nil end -- Check whether we should output the end box at all. Add the end -- box by default if the documentation exists or if we are in the -- user, module or template namespaces. if linkBox == 'off' or not ( docTitle.exists or subjectSpace == 2 or subjectSpace == 828 or subjectSpace == 10 ) then return nil end -- Assemble the arguments for {{fmbox}}. local fmargs = {} fmargs.id = message('fmboxId', 'string') -- Sets 'documentation-meta-data' fmargs.image = message('fmboxImageNone', 'string') -- Sets 'none' fmargs.style = message('fmboxStyle', 'string') -- Sets 'background-color: #ecfcf4' fmargs.textstyle = message('fmboxTextstyle', 'string') -- 'font-style: italic;' -- Assemble the fmbox text field. local text = '' if linkBox then -- Use custom link box content if it is defined. text = text .. linkBox else text = text .. (p.makeDocPageBlurb(args, env) or '') -- Add links to /sandbox and /testcases when appropriate. if subjectSpace == 2 or subjectSpace == 828 or subjectSpace == 10 then -- We are in the user, module or template namespaces. text = text .. p.makeEndBoxExperimentBlurb(args, env) text = text .. '<br />' -- Show the categories text, but not if "content" fed or "docname fed" -- since then it is unclear where to add the categories. if not content and not docnameFed then text = text .. (p.makeCategoriesBlurb(args, env) or '') end -- Show the "subpages" link. if subjectSpace ~= 6 then -- Don't show the link in file space. text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') end -- Show the "print" link if it exists. local printBlurb = p.makePrintBlurb(args, env) if printBlurb then text = text .. '<br />' .. printBlurb end end end fmargs.text = text -- Return the fmbox output. return messageBox.main('fmbox', fmargs) end function p.makePrintBlurb(args, env) -- Get the /Print title object local printTitle = env.printTitle if not printTitle then return nil end -- Make the print blurb. local ret if printTitle.exists then local printLink = makeWikilink(printTitle.prefixedText, message('printLinkDisplay', 'string')) ret = message('printBlurb', 'string', {printLink}) local displayPrintCategory = message('displayPrintCategory', 'boolean') if displayPrintCategory then ret = ret .. makeCategoryLink(message('printCategory', 'string')) end end return ret end function p.makeSubpagesBlurb(args, env) -- Get the template title object local templateTitle = env.templateTitle if not templateTitle then return nil end -- Make the subpages blurb. local pagetype if subjectSpace == 10 then pagetype = message('templatePagetype', 'string') elseif subjectSpace == 828 then pagetype = message('modulePagetype', 'string') else pagetype = message('defaultPagetype', 'string') end return makeWikilink( 'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/', message('subpagesLinkDisplay', 'string', {pagetype}) ) end function p.makeCategoriesBlurb(args, env) -- Get the title object. local docTitle = env.docTitle if not docTitle then return nil end -- Make the blurb. local docPathLink = makeWikilink(docTitle.prefixedText, message('docLinkDisplay', 'string')) return message('addCategoriesBlurb', 'string', {docPathLink}) end function p.makeDocPageBlurb(args, env) -- Get the title object. local docTitle = env.docTitle if not docTitle then return nil end -- Make the blurb. local ret if docTitle.exists then -- /doc exists; link to it. local docLink = makeWikilink(docTitle.prefixedText) local editUrl = docTitle:fullUrl{action = 'edit'} local editDisplay = message('editLinkDisplay', 'string') local editLink = makeUrlLink(editUrl, editDisplay) local historyUrl = docTitle:fullUrl{action = 'history'} local historyDisplay = message('historyLinkDisplay', 'string') local historyLink = makeUrlLink(historyUrl, historyDisplay) ret = message('transcludedFromBlurb', 'string', {docLink}) .. ' ' .. makeToolbar(editLink, historyLink) .. '<br />' elseif env.subjectSpace == 828 then -- /doc does not exist; ask to create it. local createUrl = docTitle:fullUrl{action = 'edit', preload = message('modulePreload', 'string')} local createDisplay = message('createLinkDisplay', 'string') local createLink = makeUrlLink(createUrl, createDisplay) ret = message('createModuleDocBlurb', 'string', {createLink}) .. '<br />' end return ret end function p.makeEndBoxExperimentBlurb(args, env) -- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages." local subjectSpace = env.subjectSpace local templatePage = env.templatePage -- Get title objects. local templateTitle = env.templateTitle local sandboxTitle = env.sandboxTitle local testcasesTitle = env.testcasesTitle if not templateTitle or not sandboxTitle or not testcasesTitle then return nil end -- Make links. local sandboxLinks, testcasesLinks if sandboxTitle.exists then local sandboxPage = sandboxTitle.prefixedText local sandboxDisplay = message('sandboxLinkDisplay', 'string') local sandboxLink = makeWikilink(sandboxPage, sandboxDisplay) local sandboxEditUrl = sandboxTitle:fullUrl{action = 'edit'} local sandboxEditDisplay = message('sandboxEditLinkDisplay', 'string') local sandboxEditLink = makeUrlLink(sandboxEditUrl, sandboxEditDisplay) local compareUrl = mw.uri.fullUrl('Special:ComparePages', {page1 = templateTitle.prefixedText, page2 = sandboxPage}) compareUrl = tostring(compareUrl) local compareDisplay = message('compareLinkDisplay', 'string') local compareLink = makeUrlLink(compareUrl, compareDisplay) sandboxLinks = sandboxLink .. ' ' .. makeToolbar(sandboxEditLink, compareLink) else local sandboxPreload if subjectSpace == 828 then sandboxPreload = message('moduleSandboxPreload', 'string') else sandboxPreload = message('templateSandboxPreload', 'string') end local sandboxCreateUrl = sandboxTitle:fullUrl{action = 'edit', preload = sandboxPreload} local sandboxCreateDisplay = message('sandboxCreateLinkDisplay', 'string') local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay) local mirrorSummary = message('mirrorEditSummary', 'string', {makeWikilink(templatePage)}) local mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = templatePage, summary = mirrorSummary} local mirrorDisplay = message('mirrorLinkDisplay', 'string') local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay) sandboxLinks = message('sandboxLinkDisplay', 'string') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink) end if testcasesTitle.exists then local testcasesPage = testcasesTitle.prefixedText local testcasesDisplay = message('testcasesLinkDisplay', 'string') local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay) local testcasesEditUrl = testcasesTitle:fullUrl{action = 'edit'} local testcasesEditDisplay = message('testcasesEditLinkDisplay', 'string') local testcasesEditLink = makeUrlLink(testcasesEditUrl, testcasesEditDisplay) testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink) else local testcasesPreload if subjectSpace == 828 then testcasesPreload = message('moduleTestcasesPreload', 'string') else testcasesPreload = message('templateTestcasesPreload', 'string') end local testcasesCreateUrl = testcasesTitle:fullUrl{action = 'edit', preload = testcasesPreload} local testcasesCreateDisplay = message('testcasesCreateLinkDisplay', 'string') local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay) testcasesLinks = message('testcasesLinkDisplay', 'string') .. ' ' .. makeToolbar(testcasesCreateLink) end local messageName if subjectSpace == 828 then messageName = 'experimentBlurbModule' else messageName = 'experimentBlurbTemplate' end return message(messageName, 'string', {sandboxLinks, testcasesLinks}) end ---------------------------------------------------------------------------- -- Tracking categories ---------------------------------------------------------------------------- function p.addTrackingCategories(env) -- Check if {{documentation}} is transcluded on a /doc or /testcases page. local title = env.title local ret = '' local subpage = title.subpageText if message('displayStrangeUsageCategory', 'boolean') and (subpage == message('docSubpage', 'string') or subpage == message('testcasesSubpage', 'string')) then local sort = (title.namespace == 0 and message('strangeUsageCategoryMainspaceSort', 'string') or '') .. title.prefixedText -- Sort on namespace. ret = ret .. makeCategoryLink(message('strangeUsageCategory', 'string'), sort) end return ret end return p