Module:Documentation

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