Module:Documentation

From WikiProjectMed
Jump to navigation Jump to search

This module displays a blue box containing documentation for templates, Lua modules, or other pages. The {{documentation}} template invokes it.

Normal usage

For most uses, you should use the {{documentation}} template; please see that template's page for its usage instructions and parameters.

Use in other modules

To use this module from another Lua module, first load it with require:

local documentation = require('Module:Documentation').main

Then you can simply call it using a table of arguments.

documentation{content = 'Some documentation', ['link box'] = 'My custom link box'}

Please refer to the template documentation for usage instructions and a list of parameters.

Porting to other wikis

The module has a configuration file at Module:Documentation/config which is intended to allow easy translation and porting to other wikis. Please see the code comments in the config page for instructions. If you have any questions, or you need a feature which is not currently implemented, please leave a message at Template talk:Documentation to get the attention of a developer.


   1 -- This module implements {{documentation}}.
   2 
   3 -- Get required modules.
   4 local getArgs = require('Module:Arguments').getArgs
   5 
   6 -- Get the config table.
   7 local cfg = mw.loadData('Module:Documentation/config')
   8 
   9 local p = {}
  10 
  11 -- Often-used functions.
  12 local ugsub = mw.ustring.gsub
  13 
  14 ----------------------------------------------------------------------------
  15 -- Helper functions
  16 --
  17 -- These are defined as local functions, but are made available in the p
  18 -- table for testing purposes.
  19 ----------------------------------------------------------------------------
  20 
  21 local function message(cfgKey, valArray, expectType)
  22 	--[[
  23 	-- Gets a message from the cfg table and formats it if appropriate.
  24 	-- The function raises an error if the value from the cfg table is not
  25 	-- of the type expectType. The default type for expectType is 'string'.
  26 	-- If the table valArray is present, strings such as $1, $2 etc. in the
  27 	-- message are substituted with values from the table keys [1], [2] etc.
  28 	-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
  29 	-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
  30 	--]]
  31 	local msg = cfg[cfgKey]
  32 	expectType = expectType or 'string'
  33 	if type(msg) ~= expectType then
  34 		error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
  35 	end
  36 	if not valArray then
  37 		return msg
  38 	end
  39 
  40 	local function getMessageVal(match)
  41 		match = tonumber(match)
  42 		return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
  43 	end
  44 
  45 	return ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
  46 end
  47 
  48 p.message = message
  49 
  50 local function makeWikilink(page, display)
  51 	if display then
  52 		return mw.ustring.format('[[%s|%s]]', page, display)
  53 	else
  54 		return mw.ustring.format('[[%s]]', page)
  55 	end
  56 end
  57 
  58 p.makeWikilink = makeWikilink
  59 
  60 local function makeCategoryLink(cat, sort)
  61 	local catns = mw.site.namespaces[14].name
  62 	return makeWikilink(catns .. ':' .. cat, sort)
  63 end
  64 
  65 p.makeCategoryLink = makeCategoryLink
  66 
  67 local function makeUrlLink(url, display)
  68 	return mw.ustring.format('[%s %s]', url, display)
  69 end
  70 
  71 p.makeUrlLink = makeUrlLink
  72 
  73 local function makeToolbar(...)
  74 	local ret = {}
  75 	local lim = select('#', ...)
  76 	if lim < 1 then
  77 		return nil
  78 	end
  79 	for i = 1, lim do
  80 		ret[#ret + 1] = select(i, ...)
  81 	end
  82 	-- 'documentation-toolbar'
  83 	return '<span class="' .. message('toolbar-class') .. '">('
  84 		.. table.concat(ret, ' &#124; ') .. ')</span>'
  85 end	
  86 
  87 p.makeToolbar = makeToolbar
  88 
  89 ----------------------------------------------------------------------------
  90 -- Argument processing
  91 ----------------------------------------------------------------------------
  92 
  93 local function makeInvokeFunc(funcName)
  94 	return function (frame)
  95 		local args = getArgs(frame, {
  96 			valueFunc = function (key, value)
  97 				if type(value) == 'string' then
  98 					value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
  99 					if key == 'heading' or value ~= '' then
 100 						return value
 101 					else
 102 						return nil
 103 					end
 104 				else
 105 					return value
 106 				end
 107 			end
 108 		})
 109 		return p[funcName](args)
 110 	end
 111 end
 112 
 113 ----------------------------------------------------------------------------
 114 -- Entry points
 115 ----------------------------------------------------------------------------
 116 
 117 function p.nonexistent(frame)
 118 	if mw.title.getCurrentTitle().subpageText == 'testcases' then
 119 		return frame:expandTemplate{title = 'module test cases notice'}
 120 	else
 121 		return p.main(frame)
 122 	end
 123 end
 124 
 125 p.main = makeInvokeFunc('_main')
 126 
 127 function p._main(args)
 128 	--[[
 129 	-- This function defines logic flow for the module.
 130 	-- @args - table of arguments passed by the user
 131 	--]]
 132 	local env = p.getEnvironment(args)
 133 	local root = mw.html.create()
 134 	root
 135 		:wikitext(p._getModuleWikitext(args, env))
 136 		:wikitext(p.protectionTemplate(env))
 137 		:wikitext(p.sandboxNotice(args, env))
 138 		:tag('div')
 139 			-- 'documentation-container'
 140 			:addClass(message('container'))
 141 			:newline()
 142 			:tag('div')
 143 				-- 'documentation'
 144 				:addClass(message('main-div-classes'))
 145 				:newline()
 146 				:wikitext(p._startBox(args, env))
 147 				:wikitext(p._content(args, env))
 148 				:tag('div')
 149 					-- 'documentation-clear'
 150 					:addClass(message('clear'))
 151 					:done()
 152 				:newline()
 153 				:done()
 154 			:wikitext(p._endBox(args, env))
 155 			:done()
 156 		:wikitext(p.addTrackingCategories(env))
 157 	-- 'Module:Documentation/styles.css'
 158 	return mw.getCurrentFrame():extensionTag (
 159 		'templatestyles', '', {src=cfg['templatestyles']
 160 	}) .. tostring(root)
 161 end
 162 
 163 ----------------------------------------------------------------------------
 164 -- Environment settings
 165 ----------------------------------------------------------------------------
 166 
 167 function p.getEnvironment(args)
 168 	--[[
 169 	-- Returns a table with information about the environment, including title
 170 	-- objects and other namespace- or path-related data.
 171 	-- @args - table of arguments passed by the user
 172 	--
 173 	-- Title objects include:
 174 	-- env.title - the page we are making documentation for (usually the current title)
 175 	-- env.templateTitle - the template (or module, file, etc.)
 176 	-- env.docTitle - the /doc subpage.
 177 	-- env.sandboxTitle - the /sandbox subpage.
 178 	-- env.testcasesTitle - the /testcases subpage.
 179 	-- env.printTitle - the print version of the template, located at the /Print subpage.
 180 	--
 181 	-- Data includes:
 182 	-- env.protectionLevels - the protection levels table of the title object.
 183 	-- env.subjectSpace - the number of the title's subject namespace.
 184 	-- env.docSpace - the number of the namespace the title puts its documentation in.
 185 	-- env.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
 186 	-- env.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.
 187 	-- 
 188 	-- All table lookups are passed through pcall so that errors are caught. If an error occurs, the value
 189 	-- returned will be nil.
 190 	--]]
 191 	
 192 	local env, envFuncs = {}, {}
 193 
 194 	-- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
 195 	-- returned by that function is memoized in the env table so that we don't call any of the functions
 196 	-- more than once. (Nils won't be memoized.)
 197 	setmetatable(env, {
 198 		__index = function (t, key)
 199 			local envFunc = envFuncs[key]
 200 			if envFunc then
 201 				local success, val = pcall(envFunc)
 202 				if success then
 203 					env[key] = val -- Memoise the value.
 204 					return val
 205 				end
 206 			end
 207 			return nil
 208 		end
 209 	})	
 210 
 211 	function envFuncs.title()
 212 		-- The title object for the current page, or a test page passed with args.page.
 213 		local title
 214 		local titleArg = args.page
 215 		if titleArg then
 216 			title = mw.title.new(titleArg)
 217 		else
 218 			title = mw.title.getCurrentTitle()
 219 		end
 220 		return title
 221 	end
 222 
 223 	function envFuncs.templateTitle()
 224 		--[[
 225 		-- The template (or module, etc.) title object.
 226 		-- Messages:
 227 		-- 'sandbox-subpage' --> 'sandbox'
 228 		-- 'testcases-subpage' --> 'testcases'
 229 		--]]
 230 		local subjectSpace = env.subjectSpace
 231 		local title = env.title
 232 		local subpage = title.subpageText
 233 		if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') then
 234 			return mw.title.makeTitle(subjectSpace, title.baseText)
 235 		else
 236 			return mw.title.makeTitle(subjectSpace, title.text)
 237 		end
 238 	end
 239 
 240 	function envFuncs.docTitle()
 241 		--[[
 242 		-- Title object of the /doc subpage.
 243 		-- Messages:
 244 		-- 'doc-subpage' --> 'doc'
 245 		--]]
 246 		local title = env.title
 247 		local docname = args[1] -- User-specified doc page.
 248 		local docpage
 249 		if docname then
 250 			docpage = docname
 251 		else
 252 			docpage = env.docpageBase .. '/' .. message('doc-subpage')
 253 		end
 254 		return mw.title.new(docpage)
 255 	end
 256 	
 257 	function envFuncs.sandboxTitle()
 258 		--[[
 259 		-- Title object for the /sandbox subpage.
 260 		-- Messages:
 261 		-- 'sandbox-subpage' --> 'sandbox'
 262 		--]]
 263 		return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
 264 	end
 265 	
 266 	function envFuncs.testcasesTitle()
 267 		--[[
 268 		-- Title object for the /testcases subpage.
 269 		-- Messages:
 270 		-- 'testcases-subpage' --> 'testcases'
 271 		--]]
 272 		return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
 273 	end
 274 	
 275 	function envFuncs.printTitle()
 276 		--[[
 277 		-- Title object for the /Print subpage.
 278 		-- Messages:
 279 		-- 'print-subpage' --> 'Print'
 280 		--]]
 281 		return env.templateTitle:subPageTitle(message('print-subpage'))
 282 	end
 283 
 284 	function envFuncs.protectionLevels()
 285 		-- The protection levels table of the title object.
 286 		return env.title.protectionLevels
 287 	end
 288 
 289 	function envFuncs.subjectSpace()
 290 		-- The subject namespace number.
 291 		return mw.site.namespaces[env.title.namespace].subject.id
 292 	end
 293 
 294 	function envFuncs.docSpace()
 295 		-- The documentation namespace number. For most namespaces this is the
 296 		-- same as the subject namespace. However, pages in the Article, File,
 297 		-- MediaWiki or Category namespaces must have their /doc, /sandbox and
 298 		-- /testcases pages in talk space.
 299 		local subjectSpace = env.subjectSpace
 300 		if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then
 301 			return subjectSpace + 1
 302 		else
 303 			return subjectSpace
 304 		end
 305 	end
 306 
 307 	function envFuncs.docpageBase()
 308 		-- The base page of the /doc, /sandbox, and /testcases subpages.
 309 		-- For some namespaces this is the talk page, rather than the template page.
 310 		local templateTitle = env.templateTitle
 311 		local docSpace = env.docSpace
 312 		local docSpaceText = mw.site.namespaces[docSpace].name
 313 		-- Assemble the link. docSpace is never the main namespace, so we can hardcode the colon.
 314 		return docSpaceText .. ':' .. templateTitle.text
 315 	end
 316 	
 317 	function envFuncs.compareUrl()
 318 		-- Diff link between the sandbox and the main template using [[Special:ComparePages]].
 319 		local templateTitle = env.templateTitle
 320 		local sandboxTitle = env.sandboxTitle
 321 		if templateTitle.exists and sandboxTitle.exists then
 322 			local compareUrl = mw.uri.fullUrl(
 323 				'Special:ComparePages',
 324 				{ page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
 325 			)
 326 			return tostring(compareUrl)
 327 		else
 328 			return nil
 329 		end
 330 	end		
 331 
 332 	return env
 333 end	
 334 
 335 ----------------------------------------------------------------------------
 336 -- Auxiliary templates
 337 ----------------------------------------------------------------------------
 338 
 339 p.getModuleWikitext = makeInvokeFunc('_getModuleWikitext')
 340 
 341 function p._getModuleWikitext(args, env)
 342 	local currentTitle = mw.title.getCurrentTitle()
 343 	if currentTitle.contentModel ~= 'Scribunto' then return end
 344 	pcall(require, currentTitle.prefixedText) -- if it fails, we don't care
 345 	local moduleWikitext =  package.loaded["Module:Module wikitext"]
 346 	if moduleWikitext then
 347 		return moduleWikitext.main()
 348 	end
 349 end
 350 
 351 function p.sandboxNotice(args, env)
 352 	--[=[
 353 	-- Generates a sandbox notice for display above sandbox pages.
 354 	-- @args - a table of arguments passed by the user
 355 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 356 	-- 
 357 	-- Messages:
 358 	-- 'sandbox-notice-image' --> '[[Image:Sandbox.svg|50px|alt=|link=]]'
 359 	-- 'sandbox-notice-blurb' --> 'This is the $1 for $2.'
 360 	-- 'sandbox-notice-diff-blurb' --> 'This is the $1 for $2 ($3).'
 361 	-- 'sandbox-notice-pagetype-template' --> '[[Wikipedia:Template test cases|template sandbox]] page'
 362 	-- 'sandbox-notice-pagetype-module' --> '[[Wikipedia:Template test cases|module sandbox]] page'
 363 	-- 'sandbox-notice-pagetype-other' --> 'sandbox page'
 364 	-- 'sandbox-notice-compare-link-display' --> 'diff'
 365 	-- 'sandbox-notice-testcases-blurb' --> 'See also the companion subpage for $1.'
 366 	-- 'sandbox-notice-testcases-link-display' --> 'test cases'
 367 	-- 'sandbox-category' --> 'Template sandboxes'
 368 	--]=]
 369 	local title = env.title
 370 	local sandboxTitle = env.sandboxTitle
 371 	local templateTitle = env.templateTitle
 372 	local subjectSpace = env.subjectSpace
 373 	if not (subjectSpace and title and sandboxTitle and templateTitle
 374 		and mw.title.equals(title, sandboxTitle)) then
 375 		return nil
 376 	end
 377 	-- Build the table of arguments to pass to {{ombox}}. We need just two fields, "image" and "text".
 378 	local omargs = {}
 379 	omargs.image = message('sandbox-notice-image')
 380 	-- Get the text. We start with the opening blurb, which is something like
 381 	-- "This is the template sandbox for [[Template:Foo]] (diff)."
 382 	local text = ''
 383 	local pagetype
 384 	if subjectSpace == 10 then
 385 		pagetype = message('sandbox-notice-pagetype-template')
 386 	elseif subjectSpace == 828 then
 387 		pagetype = message('sandbox-notice-pagetype-module')
 388 	else
 389 		pagetype = message('sandbox-notice-pagetype-other')
 390 	end
 391 	local templateLink = makeWikilink(templateTitle.prefixedText)
 392 	local compareUrl = env.compareUrl
 393 	if compareUrl then
 394 		local compareDisplay = message('sandbox-notice-compare-link-display')
 395 		local compareLink = makeUrlLink(compareUrl, compareDisplay)
 396 		text = text .. message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
 397 	else
 398 		text = text .. message('sandbox-notice-blurb', {pagetype, templateLink})
 399 	end
 400 	-- Get the test cases page blurb if the page exists. This is something like
 401 	-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
 402 	local testcasesTitle = env.testcasesTitle
 403 	if testcasesTitle and testcasesTitle.exists then
 404 		if testcasesTitle.contentModel == "Scribunto" then
 405 			local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
 406 			local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
 407 			local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
 408 			local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
 409 			text = text .. '<br />' .. message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
 410 		else
 411 			local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
 412 			local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
 413 			text = text .. '<br />' .. message('sandbox-notice-testcases-blurb', {testcasesLink})
 414 		end
 415 	end
 416 	-- Add the sandbox to the sandbox category.
 417 	omargs.text = text .. makeCategoryLink(message('sandbox-category'))
 418 
 419 	-- 'documentation-clear'
 420 	return '<div class="' .. message('clear') .. '"></div>'
 421 		.. require('Module:Message box').main('ombox', omargs)
 422 end
 423 
 424 function p.protectionTemplate(env)
 425 	-- Generates the padlock icon in the top right.
 426 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 427 	-- Messages:
 428 	-- 'protection-template' --> 'pp-template'
 429 	-- 'protection-template-args' --> {docusage = 'yes'}
 430 	local protectionLevels = env.protectionLevels
 431 	if not protectionLevels then
 432 		return nil
 433 	end
 434 	local editProt = protectionLevels.edit and protectionLevels.edit[1]
 435 	local moveProt = protectionLevels.move and protectionLevels.move[1]
 436 	if editProt then
 437 		-- The page is edit-protected.
 438 		return require('Module:Protection banner')._main{
 439 			message('protection-reason-edit'), small = true
 440 		}
 441 	elseif moveProt and moveProt ~= 'autoconfirmed' then
 442 		-- The page is move-protected but not edit-protected. Exclude move
 443 		-- protection with the level "autoconfirmed", as this is equivalent to
 444 		-- no move protection at all.
 445 		return require('Module:Protection banner')._main{
 446 			action = 'move', small = true
 447 		}
 448 	else
 449 		return nil
 450 	end
 451 end
 452 
 453 ----------------------------------------------------------------------------
 454 -- Start box
 455 ----------------------------------------------------------------------------
 456 
 457 p.startBox = makeInvokeFunc('_startBox')
 458 
 459 function p._startBox(args, env)
 460 	--[[
 461 	-- This function generates the start box.
 462 	-- @args - a table of arguments passed by the user
 463 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 464 	-- 
 465 	-- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
 466 	-- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
 467 	-- which generate the box HTML.
 468 	--]]
 469 	env = env or p.getEnvironment(args)
 470 	local links
 471 	local content = args.content
 472 	if not content or args[1] then
 473 		-- No need to include the links if the documentation is on the template page itself.
 474 		local linksData = p.makeStartBoxLinksData(args, env)
 475 		if linksData then
 476 			links = p.renderStartBoxLinks(linksData)
 477 		end
 478 	end
 479 	-- Generate the start box html.
 480 	local data = p.makeStartBoxData(args, env, links)
 481 	if data then
 482 		return p.renderStartBox(data)
 483 	else
 484 		-- User specified no heading.
 485 		return nil
 486 	end
 487 end
 488 
 489 function p.makeStartBoxLinksData(args, env)
 490 	--[[
 491 	-- Does initial processing of data to make the [view] [edit] [history] [purge] links.
 492 	-- @args - a table of arguments passed by the user
 493 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 494 	-- 
 495 	-- Messages:
 496 	-- 'view-link-display' --> 'view'
 497 	-- 'edit-link-display' --> 'edit'
 498 	-- 'history-link-display' --> 'history'
 499 	-- 'purge-link-display' --> 'purge'
 500 	-- 'file-docpage-preload' --> 'Template:Documentation/preload-filespace'
 501 	-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
 502 	-- 'docpage-preload' --> 'Template:Documentation/preload'
 503 	-- 'create-link-display' --> 'create'
 504 	--]]
 505 	local subjectSpace = env.subjectSpace
 506 	local title = env.title
 507 	local docTitle = env.docTitle
 508 	if not title or not docTitle then
 509 		return nil
 510 	end
 511 	if docTitle.isRedirect then 
 512 		docTitle = docTitle.redirectTarget
 513 	end
 514 
 515 	local data = {}
 516 	data.title = title
 517 	data.docTitle = docTitle
 518 	-- View, display, edit, and purge links if /doc exists.
 519 	data.viewLinkDisplay = message('view-link-display')
 520 	data.editLinkDisplay = message('edit-link-display')
 521 	data.historyLinkDisplay = message('history-link-display')
 522 	data.purgeLinkDisplay = message('purge-link-display')
 523 	-- Create link if /doc doesn't exist.
 524 	local preload = args.preload
 525 	if not preload then
 526 		if subjectSpace == 6 then -- File namespace
 527 			preload = message('file-docpage-preload')
 528 		elseif subjectSpace == 828 then -- Module namespace
 529 			preload = message('module-preload')
 530 		else
 531 			preload = message('docpage-preload')
 532 		end
 533 	end
 534 	data.preload = preload
 535 	data.createLinkDisplay = message('create-link-display')
 536 	return data
 537 end
 538 
 539 function p.renderStartBoxLinks(data)
 540 	--[[
 541 	-- Generates the [view][edit][history][purge] or [create] links from the data table.
 542 	-- @data - a table of data generated by p.makeStartBoxLinksData
 543 	--]]
 544 	
 545 	local function escapeBrackets(s)
 546 		-- Escapes square brackets with HTML entities.
 547 		s = s:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
 548 		s = s:gsub('%]', '&#93;')
 549 		return s
 550 	end
 551 
 552 	local ret
 553 	local docTitle = data.docTitle
 554 	local title = data.title
 555 	if docTitle.exists then
 556 		local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
 557 		local editLink = makeUrlLink(docTitle:fullUrl{action = 'edit'}, data.editLinkDisplay)
 558 		local historyLink = makeUrlLink(docTitle:fullUrl{action = 'history'}, data.historyLinkDisplay)
 559 		local purgeLink = makeUrlLink(title:fullUrl{action = 'purge'}, data.purgeLinkDisplay)
 560 		ret = '[%s] [%s] [%s] [%s]'
 561 		ret = escapeBrackets(ret)
 562 		ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink)
 563 	else
 564 		local createLink = makeUrlLink(docTitle:fullUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay)
 565 		ret = '[%s]'
 566 		ret = escapeBrackets(ret)
 567 		ret = mw.ustring.format(ret, createLink)
 568 	end
 569 	return ret
 570 end
 571 
 572 function p.makeStartBoxData(args, env, links)
 573 	--[=[
 574 	-- Does initial processing of data to pass to the start-box render function, p.renderStartBox.
 575 	-- @args - a table of arguments passed by the user
 576 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 577 	-- @links - a string containing the [view][edit][history][purge] links - could be nil if there's an error.
 578 	--
 579 	-- Messages:
 580 	-- 'documentation-icon-wikitext' --> '[[File:Test Template Info-Icon - Version (2).svg|50px|link=|alt=]]'
 581 	-- 'template-namespace-heading' --> 'Template documentation'
 582 	-- 'module-namespace-heading' --> 'Module documentation'
 583 	-- 'file-namespace-heading' --> 'Summary'
 584 	-- 'other-namespaces-heading' --> 'Documentation'
 585 	-- 'testcases-create-link-display' --> 'create'
 586 	--]=]
 587 	local subjectSpace = env.subjectSpace
 588 	if not subjectSpace then
 589 		-- Default to an "other namespaces" namespace, so that we get at least some output
 590 		-- if an error occurs.
 591 		subjectSpace = 2
 592 	end
 593 	local data = {}
 594 	
 595 	-- Heading
 596 	local heading = args.heading -- Blank values are not removed.
 597 	if heading == '' then
 598 		-- Don't display the start box if the heading arg is defined but blank.
 599 		return nil
 600 	end
 601 	if heading then
 602 		data.heading = heading
 603 	elseif subjectSpace == 10 then -- Template namespace
 604 		data.heading = message('documentation-icon-wikitext') .. ' ' .. message('template-namespace-heading')
 605 	elseif subjectSpace == 828 then -- Module namespace
 606 		data.heading = message('documentation-icon-wikitext') .. ' ' .. message('module-namespace-heading')
 607 	elseif subjectSpace == 6 then -- File namespace
 608 		data.heading = message('file-namespace-heading')
 609 	else
 610 		data.heading = message('other-namespaces-heading')
 611 	end
 612 	
 613 	-- Heading CSS
 614 	local headingStyle = args['heading-style']
 615 	if headingStyle then
 616 		data.headingStyleText = headingStyle
 617 	else
 618 		-- 'documentation-heading'
 619 		data.headingClass = message('main-div-heading-class')
 620 	end
 621 	
 622 	-- Data for the [view][edit][history][purge] or [create] links.
 623 	if links then
 624 		-- 'mw-editsection-like plainlinks'
 625 		data.linksClass = message('start-box-link-classes')
 626 		data.links = links
 627 	end
 628 	
 629 	return data
 630 end
 631 
 632 function p.renderStartBox(data)
 633 	-- Renders the start box html.
 634 	-- @data - a table of data generated by p.makeStartBoxData.
 635 	local sbox = mw.html.create('div')
 636 	sbox
 637 		-- 'documentation-startbox'
 638 		:addClass(message('start-box-class'))
 639 		:newline()
 640 		:tag('span')
 641 			:addClass(data.headingClass)
 642 			:cssText(data.headingStyleText)
 643 			:wikitext(data.heading)
 644 	local links = data.links
 645 	if links then
 646 		sbox:tag('span')
 647 			:addClass(data.linksClass)
 648 			:attr('id', data.linksId)
 649 			:wikitext(links)
 650 	end
 651 	return tostring(sbox)
 652 end
 653 
 654 ----------------------------------------------------------------------------
 655 -- Documentation content
 656 ----------------------------------------------------------------------------
 657 
 658 p.content = makeInvokeFunc('_content')
 659 
 660 function p._content(args, env)
 661 	-- Displays the documentation contents
 662 	-- @args - a table of arguments passed by the user
 663 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 664 	env = env or p.getEnvironment(args)
 665 	local docTitle = env.docTitle
 666 	local content = args.content
 667 	if not content and docTitle and docTitle.exists then
 668 		content = args._content or mw.getCurrentFrame():expandTemplate{title = docTitle.prefixedText}
 669 	end
 670 	-- The line breaks below are necessary so that "=== Headings ===" at the start and end
 671 	-- of docs are interpreted correctly.
 672 	return '\n' .. (content or '') .. '\n' 
 673 end
 674 
 675 p.contentTitle = makeInvokeFunc('_contentTitle')
 676 
 677 function p._contentTitle(args, env)
 678 	env = env or p.getEnvironment(args)
 679 	local docTitle = env.docTitle
 680 	if not args.content and docTitle and docTitle.exists then
 681 		return docTitle.prefixedText
 682 	else
 683 		return ''
 684 	end
 685 end
 686 
 687 ----------------------------------------------------------------------------
 688 -- End box
 689 ----------------------------------------------------------------------------
 690 
 691 p.endBox = makeInvokeFunc('_endBox')
 692 
 693 function p._endBox(args, env)
 694 	--[=[
 695 	-- This function generates the end box (also known as the link box).
 696 	-- @args - a table of arguments passed by the user
 697 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 698 	-- 
 699 	--]=]
 700 	
 701 	-- Get environment data.
 702 	env = env or p.getEnvironment(args)
 703 	local subjectSpace = env.subjectSpace
 704 	local docTitle = env.docTitle
 705 	if not subjectSpace or not docTitle then
 706 		return nil
 707 	end
 708 		
 709 	-- Check whether we should output the end box at all. Add the end
 710 	-- box by default if the documentation exists or if we are in the
 711 	-- user, module or template namespaces.
 712 	local linkBox = args['link box']
 713 	if linkBox == 'off'
 714 		or not (
 715 			docTitle.exists
 716 			or subjectSpace == 2
 717 			or subjectSpace == 828
 718 			or subjectSpace == 10
 719 		)
 720 	then
 721 		return nil
 722 	end
 723 
 724 	-- Assemble the link box.
 725 	local text = ''
 726 	if linkBox then
 727 		text = text .. linkBox
 728 	else
 729 		text = text .. (p.makeDocPageBlurb(args, env) or '') -- "This documentation is transcluded from [[Foo]]." 
 730 		if subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828 then
 731 			-- We are in the user, template or module namespaces.
 732 			-- Add sandbox and testcases links.
 733 			-- "Editors can experiment in this template's sandbox and testcases pages."
 734 			text = text .. (p.makeExperimentBlurb(args, env) or '') .. '<br />'
 735 			if not args.content and not args[1] then
 736 				-- "Please add categories to the /doc subpage."
 737 				-- Don't show this message with inline docs or with an explicitly specified doc page,
 738 				-- as then it is unclear where to add the categories.
 739 				text = text .. (p.makeCategoriesBlurb(args, env) or '')
 740 			end
 741 			text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') --"Subpages of this template"
 742 			local printBlurb = p.makePrintBlurb(args, env) -- Two-line blurb about print versions of templates.
 743 			if printBlurb then
 744 				text = text .. '<br />' .. printBlurb
 745 			end
 746 		end
 747 	end
 748 	
 749 	local box = mw.html.create('div')
 750 	-- 'documentation-metadata'
 751 	box:attr('role', 'note')
 752 		:addClass(message('end-box-class'))
 753 		-- 'plainlinks'
 754 		:addClass(message('end-box-plainlinks'))
 755 		:wikitext(text)
 756 		:done()
 757 
 758 	return '\n' .. tostring(box)
 759 end
 760 
 761 function p.makeDocPageBlurb(args, env)
 762 	--[=[
 763 	-- Makes the blurb "This documentation is transcluded from [[Template:Foo]] (edit, history)".
 764 	-- @args - a table of arguments passed by the user
 765 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 766 	-- 
 767 	-- Messages:
 768 	-- 'edit-link-display' --> 'edit'
 769 	-- 'history-link-display' --> 'history'
 770 	-- 'transcluded-from-blurb' --> 
 771 	-- 'The above [[Wikipedia:Template documentation|documentation]] 
 772 	-- is [[Help:Transclusion|transcluded]] from $1.'
 773 	-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
 774 	-- 'create-link-display' --> 'create'
 775 	-- 'create-module-doc-blurb' -->
 776 	-- 'You might want to $1 a documentation page for this [[Wikipedia:Lua|Scribunto module]].'
 777 	--]=]
 778 	local docTitle = env.docTitle
 779 	if not docTitle then
 780 		return nil
 781 	end
 782 	local ret
 783 	if docTitle.exists then
 784 		-- /doc exists; link to it.
 785 		local docLink = makeWikilink(docTitle.prefixedText)
 786 		local editUrl = docTitle:fullUrl{action = 'edit'}
 787 		local editDisplay = message('edit-link-display')
 788 		local editLink = makeUrlLink(editUrl, editDisplay)
 789 		local historyUrl = docTitle:fullUrl{action = 'history'}
 790 		local historyDisplay = message('history-link-display')
 791 		local historyLink = makeUrlLink(historyUrl, historyDisplay)
 792 		ret = message('transcluded-from-blurb', {docLink})
 793 			.. ' '
 794 			.. makeToolbar(editLink, historyLink)
 795 			.. '<br />'
 796 	elseif env.subjectSpace == 828 then
 797 		-- /doc does not exist; ask to create it.
 798 		local createUrl = docTitle:fullUrl{action = 'edit', preload = message('module-preload')}
 799 		local createDisplay = message('create-link-display')
 800 		local createLink = makeUrlLink(createUrl, createDisplay)
 801 		ret = message('create-module-doc-blurb', {createLink})
 802 			.. '<br />'
 803 	end
 804 	return ret
 805 end
 806 
 807 function p.makeExperimentBlurb(args, env)
 808 	--[[
 809 	-- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
 810 	-- @args - a table of arguments passed by the user
 811 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 812 	-- 
 813 	-- Messages:
 814 	-- 'sandbox-link-display' --> 'sandbox'
 815 	-- 'sandbox-edit-link-display' --> 'edit'
 816 	-- 'compare-link-display' --> 'diff'
 817 	-- 'module-sandbox-preload' --> 'Template:Documentation/preload-module-sandbox'
 818 	-- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
 819 	-- 'sandbox-create-link-display' --> 'create'
 820 	-- 'mirror-edit-summary' --> 'Create sandbox version of $1'
 821 	-- 'mirror-link-display' --> 'mirror'
 822 	-- 'mirror-link-preload' --> 'Template:Documentation/mirror'
 823 	-- 'sandbox-link-display' --> 'sandbox'
 824 	-- 'testcases-link-display' --> 'testcases'
 825 	-- 'testcases-edit-link-display'--> 'edit'
 826 	-- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
 827 	-- 'testcases-create-link-display' --> 'create'
 828 	-- 'testcases-link-display' --> 'testcases'
 829 	-- 'testcases-edit-link-display' --> 'edit'
 830 	-- 'module-testcases-preload' --> 'Template:Documentation/preload-module-testcases'
 831 	-- 'template-testcases-preload' --> 'Template:Documentation/preload-testcases'
 832 	-- 'experiment-blurb-module' --> 'Editors can experiment in this module's $1 and $2 pages.'
 833 	-- 'experiment-blurb-template' --> 'Editors can experiment in this template's $1 and $2 pages.'
 834 	--]]
 835 	local subjectSpace = env.subjectSpace
 836 	local templateTitle = env.templateTitle
 837 	local sandboxTitle = env.sandboxTitle
 838 	local testcasesTitle = env.testcasesTitle
 839 	local templatePage = templateTitle.prefixedText
 840 	if not subjectSpace or not templateTitle or not sandboxTitle or not testcasesTitle then
 841 		return nil
 842 	end
 843 	-- Make links.
 844 	local sandboxLinks, testcasesLinks
 845 	if sandboxTitle.exists then
 846 		local sandboxPage = sandboxTitle.prefixedText
 847 		local sandboxDisplay = message('sandbox-link-display')
 848 		local sandboxLink = makeWikilink(sandboxPage, sandboxDisplay)
 849 		local sandboxEditUrl = sandboxTitle:fullUrl{action = 'edit'}
 850 		local sandboxEditDisplay = message('sandbox-edit-link-display')
 851 		local sandboxEditLink = makeUrlLink(sandboxEditUrl, sandboxEditDisplay)
 852 		local compareUrl = env.compareUrl
 853 		local compareLink
 854 		if compareUrl then
 855 			local compareDisplay = message('compare-link-display')
 856 			compareLink = makeUrlLink(compareUrl, compareDisplay)
 857 		end
 858 		sandboxLinks = sandboxLink .. ' ' .. makeToolbar(sandboxEditLink, compareLink)
 859 	else
 860 		local sandboxPreload
 861 		if subjectSpace == 828 then
 862 			sandboxPreload = message('module-sandbox-preload')
 863 		else
 864 			sandboxPreload = message('template-sandbox-preload')
 865 		end
 866 		local sandboxCreateUrl = sandboxTitle:fullUrl{action = 'edit', preload = sandboxPreload}
 867 		local sandboxCreateDisplay = message('sandbox-create-link-display')
 868 		local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay)
 869 		local mirrorSummary = message('mirror-edit-summary', {makeWikilink(templatePage)})
 870 		local mirrorPreload = message('mirror-link-preload')
 871 		local mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = mirrorPreload, summary = mirrorSummary}
 872 		if subjectSpace == 828 then
 873 			mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = templateTitle.prefixedText, summary = mirrorSummary}
 874 		end
 875 		local mirrorDisplay = message('mirror-link-display')
 876 		local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay)
 877 		sandboxLinks = message('sandbox-link-display') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink)
 878 	end
 879 	if testcasesTitle.exists then
 880 		local testcasesPage = testcasesTitle.prefixedText
 881 		local testcasesDisplay = message('testcases-link-display')
 882 		local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay)
 883 		local testcasesEditUrl = testcasesTitle:fullUrl{action = 'edit'}
 884 		local testcasesEditDisplay = message('testcases-edit-link-display')
 885 		local testcasesEditLink = makeUrlLink(testcasesEditUrl, testcasesEditDisplay)
 886 		-- for Modules, add testcases run link if exists
 887 		if testcasesTitle.contentModel == "Scribunto"  and testcasesTitle.talkPageTitle and testcasesTitle.talkPageTitle.exists then
 888 			local testcasesRunLinkDisplay = message('testcases-run-link-display')
 889 			local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
 890 			testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink, testcasesRunLink)
 891 		else
 892 			testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink)
 893 		end
 894 	else
 895 		local testcasesPreload
 896 		if subjectSpace == 828 then
 897 			testcasesPreload = message('module-testcases-preload')
 898 		else
 899 			testcasesPreload = message('template-testcases-preload')
 900 		end
 901 		local testcasesCreateUrl = testcasesTitle:fullUrl{action = 'edit', preload = testcasesPreload}
 902 		local testcasesCreateDisplay = message('testcases-create-link-display')
 903 		local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay)
 904 		testcasesLinks = message('testcases-link-display') .. ' ' .. makeToolbar(testcasesCreateLink)
 905 	end
 906 	local messageName
 907 	if subjectSpace == 828 then
 908 		messageName = 'experiment-blurb-module'
 909 	else
 910 		messageName = 'experiment-blurb-template'
 911 	end
 912 	return message(messageName, {sandboxLinks, testcasesLinks})
 913 end
 914 
 915 function p.makeCategoriesBlurb(args, env)
 916 	--[[
 917 	-- Generates the text "Please add categories to the /doc subpage."
 918 	-- @args - a table of arguments passed by the user
 919 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 920 	-- Messages:
 921 	-- 'doc-link-display' --> '/doc'
 922 	-- 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
 923 	--]]
 924 	local docTitle = env.docTitle
 925 	if not docTitle then
 926 		return nil
 927 	end
 928 	local docPathLink = makeWikilink(docTitle.prefixedText, message('doc-link-display'))
 929 	return message('add-categories-blurb', {docPathLink})
 930 end
 931 
 932 function p.makeSubpagesBlurb(args, env)
 933 	--[[
 934 	-- Generates the "Subpages of this template" link.
 935 	-- @args - a table of arguments passed by the user
 936 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 937 	
 938 	-- Messages:
 939 	-- 'template-pagetype' --> 'template'
 940 	-- 'module-pagetype' --> 'module'
 941 	-- 'default-pagetype' --> 'page'
 942 	-- 'subpages-link-display' --> 'Subpages of this $1'
 943 	--]]
 944 	local subjectSpace = env.subjectSpace
 945 	local templateTitle = env.templateTitle
 946 	if not subjectSpace or not templateTitle then
 947 		return nil
 948 	end
 949 	local pagetype
 950 	if subjectSpace == 10 then
 951 		pagetype = message('template-pagetype')
 952 	elseif subjectSpace == 828 then
 953 		pagetype = message('module-pagetype')
 954 	else
 955 		pagetype = message('default-pagetype')
 956 	end
 957 	local subpagesLink = makeWikilink(
 958 		'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/',
 959 		message('subpages-link-display', {pagetype})
 960 	)
 961 	return message('subpages-blurb', {subpagesLink})
 962 end
 963 
 964 function p.makePrintBlurb(args, env)
 965 	--[=[
 966 	-- Generates the blurb displayed when there is a print version of the template available.
 967 	-- @args - a table of arguments passed by the user
 968 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
 969 	--
 970 	-- Messages:
 971 	-- 'print-link-display' --> '/Print'
 972 	-- 'print-blurb' --> 'A [[Help:Books/for experts#Improving the book layout|print version]]'
 973 	--		.. ' of this template exists at $1.'
 974 	--		.. ' If you make a change to this template, please update the print version as well.'
 975 	-- 'display-print-category' --> true
 976 	-- 'print-category' --> 'Templates with print versions'
 977 	--]=]
 978 	local printTitle = env.printTitle
 979 	if not printTitle then
 980 		return nil
 981 	end
 982 	local ret
 983 	if printTitle.exists then
 984 		local printLink = makeWikilink(printTitle.prefixedText, message('print-link-display'))
 985 		ret = message('print-blurb', {printLink})
 986 		local displayPrintCategory = message('display-print-category', nil, 'boolean')
 987 		if displayPrintCategory then
 988 			ret = ret .. makeCategoryLink(message('print-category'))
 989 		end
 990 	end
 991 	return ret
 992 end
 993 
 994 ----------------------------------------------------------------------------
 995 -- Tracking categories
 996 ----------------------------------------------------------------------------
 997 
 998 function p.addTrackingCategories(env)
 999 	--[[
1000 	-- Check if {{documentation}} is transcluded on a /doc or /testcases page.
1001 	-- @env - environment table containing title objects, etc., generated with p.getEnvironment
1002 	
1003 	-- Messages:
1004 	-- 'display-strange-usage-category' --> true
1005 	-- 'doc-subpage' --> 'doc'
1006 	-- 'testcases-subpage' --> 'testcases'
1007 	-- 'strange-usage-category' --> 'Wikipedia pages with strange ((documentation)) usage'
1008 	-- 
1009 	-- /testcases pages in the module namespace are not categorised, as they may have
1010 	-- {{documentation}} transcluded automatically.
1011 	--]]
1012 	local title = env.title
1013 	local subjectSpace = env.subjectSpace
1014 	if not title or not subjectSpace then
1015 		return nil
1016 	end
1017 	local subpage = title.subpageText
1018 	local ret = ''
1019 	if message('display-strange-usage-category', nil, 'boolean')
1020 		and (
1021 			subpage == message('doc-subpage')
1022 			or subjectSpace ~= 828 and subpage == message('testcases-subpage')
1023 		)
1024 	then
1025 		ret = ret .. makeCategoryLink(message('strange-usage-category'))
1026 	end
1027 	return ret
1028 end
1029 
1030 return p