Module:Documentation

-- This module implements.

-- 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', 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 '(' .. table.concat(ret, ' &#124; ') .. ') ' end

p.makeToolbar = makeToolbar

local function err(msg) return string.format(		' %s %s %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, 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 function envFuncs.compareLink -- Diff link between the sandbox and the main template using Special:ComparePages. local templateTitle = env.templateTitle local sandboxTitle = env.sandboxTitle local compareUrl = mw.uri.fullUrl(			'Special:ComparePages',			{page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}		) compareUrl = tostring(compareUrl) local compareDisplay = message('compareLinkDisplay', 'string') return makeUrlLink(compareUrl, compareDisplay) 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 data.title = title data.docTitle = docTitle -- 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('%[', '&#91;') -- Replace square brackets with HTML entities. ret = ret:gsub('%]', '&#93;') 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('') 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. 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 .. ' '			-- 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 .. ' ' .. 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) .. ' '	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}) .. ' '	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 compareLink = env.compareLink 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 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