Module:Documentation

From Center for Integrated Circuits and Devices Research (CIDR)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Documentation/doc

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