Module:Documentation: Difference between revisions

From Path of Exile Wiki
Jump to navigation Jump to search
en>Mr. Stradivarius
(don't use mw.uri.encode - title:fullUrl already encodes the strings for us)
(Clear floats)
 
(161 intermediate revisions by 22 users not shown)
Line 3: Line 3:
-- Get required modules.
-- Get required modules.
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local htmlBuilder = require('Module:HtmlBuilder')
 
local messageBox = require('Module:Message box')
-- Get the config table.
local cfg = mw.loadData('Module:Documentation/config')


local p = {}
local p = {}


-- Constants.
-- Often-used functions.
local currentTitle = mw.title.getCurrentTitle()
local ugsub = mw.ustring.gsub
local subjectSpace = mw.site.namespaces[currentTitle.namespace].subject.id


----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Helper functions
-- Helper functions
--
-- These are defined as local functions, but are made available in the p
-- table for testing purposes.
----------------------------------------------------------------------------
----------------------------------------------------------------------------
local function message(cfgKey, valArray, expectType)
    --[[
    -- Gets a message from the cfg table and formats it if appropriate.
    -- The function raises an error if the value from the cfg table is not
    -- of the type expectType. The default type for expectType is 'string'.
    -- If the table valArray is present, strings such as $1, $2 etc. in the
    -- message are substituted with values from the table keys [1], [2] etc.
    -- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
    -- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
    --]]
    local msg = cfg[cfgKey]
    expectType = expectType or 'string'
    if type(msg) ~= expectType then
        error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
    end
    if not valArray then
        return msg
    end
    local function getMessageVal(match)
        match = tonumber(match)
        return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
    end
    local ret = ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
    return ret
end
p.message = message


local function makeWikilink(page, display)
local function makeWikilink(page, display)
if display then
    if display then
return mw.ustring.format('[[%s|%s]]', page, display)
        return mw.ustring.format('[[%s|%s]]', page, display)
else
    else
return mw.ustring.format('[[%s]]', page)
        return mw.ustring.format('[[%s]]', page)
end
    end
end
end
p.makeWikilink = makeWikilink
local function makeCategoryLink(cat, sort)
    local catns = mw.site.namespaces[14].name
    return makeWikilink(catns .. ':' .. cat, sort)
end
p.makeCategoryLink = makeCategoryLink


local function makeUrlLink(url, display)
local function makeUrlLink(url, display)
return mw.ustring.format('[%s %s]', url, display)
    return mw.ustring.format('[%s %s]', url, display)
end
end
p.makeUrlLink = makeUrlLink
local function makeToolbar(...)
    local ret = {}
    local lim = select('#', ...)
    if lim < 1 then
        return nil
    end
    for i = 1, lim do
        ret[#ret + 1] = select(i, ...)
    end
    local toolbar = mw.html.create('span')
    toolbar
        :addClass(message('end-box-toolbar-classes'))
        :wikitext('(' .. table.concat(ret, ' &#124; ') .. ')')
    return tostring(toolbar)
end
p.makeToolbar = makeToolbar


----------------------------------------------------------------------------
----------------------------------------------------------------------------
-- Main functions
-- Argument processing
----------------------------------------------------------------------------
----------------------------------------------------------------------------


function p.main(frame)
local function makeInvokeFunc(funcName)
local args = getArgs(frame, {
    return function (frame)
valueFunc = function (key, value)
        local args = getArgs(frame, {
if type(value) == 'string' then
            valueFunc = function (key, value)
value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
                if type(value) == 'string' then
if key == 'heading' or value ~= '' then
                    value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
return value
                    if key == 'heading' or value ~= '' then
else
                        return value
return nil
                    else
end
                        return nil
else
                    end
return value
                else
end
                    return value
end
                end
})
            end
return p._main(args)
        })
        return p[funcName](args)
    end
end
end
----------------------------------------------------------------------------
-- Main function
----------------------------------------------------------------------------
p.main = makeInvokeFunc('_main')


function p._main(args)
function p._main(args)
local root = htmlBuilder.create()
    --[[
root
    -- This function defines logic flow for the module.
.wikitext(p.protectionTemplate())
    -- @args - table of arguments passed by the user
.wikitext(p.sandboxNotice(args))
    --
-- This div tag is from {{documentation/start box}}, but moving it here so that we don't have to worry about unclosed tags.
    -- Messages:
.tag('div')
    -- 'main-div-id' --> 'template-documentation'
.attr('id', 'template-documentation')
    -- 'main-div-classes' --> 'template-documentation iezoomfix'
.addClass('template-documentation iezoomfix')
    --]]
.wikitext(p.startBox(args))
    local env = p.getEnvironment(args)
.wikitext(p.content(args))
    local root = mw.html.create()
.tag('div')
    root
.css('clear', 'both') -- So right or left floating items don't stick out of the doc box.
        :tag('div')
.done()
            :cssText('clear: both;')
.done()
            :done()
.wikitext(p.endBox(args))
        :wikitext(p.sandboxNotice(args, env))
.wikitext(p.addTrackingCategories())
        -- This div tag is from {{documentation/start box}}, but moving it here
return tostring(root)
        -- so that we don't have to worry about unclosed tags.
        :tag('div')
            :attr('id', message('main-div-id'))
            :addClass(message('main-div-classes'))
            :addClass('clearfix')
            :newline()
            :wikitext(p._startBox(args, env))
            :wikitext(p._content(args, env))
            :done()
        :wikitext(p._endBox(args, env))
        :wikitext(p.addTrackingCategories(env))
    return tostring(root)
end
end


function p.sandboxNotice(args)
----------------------------------------------------------------------------
if currentTitle.subpageText == 'sandbox' then
-- Environment settings
local frame = mw.getCurrentFrame()
----------------------------------------------------------------------------
local root = htmlBuilder.create()
 
root
function p.getEnvironment(args)
.tag('div')
    --[[
.css('clear', 'both')
    -- Returns a table with information about the environment, including title objects and other namespace- or
.done()
    -- path-related data.
.wikitext(frame:expandTemplate{title = 'template sandbox notice', args = {args.livepage}})
    -- @args - table of arguments passed by the user
return tostring(root)
    --
else
    -- Title objects include:
return nil
    -- env.title - the page we are making documentation for (usually the current title)
end
    -- env.templateTitle - the template (or module, file, etc.)
    -- env.docTitle - the /doc subpage.
    -- env.sandboxTitle - the /sandbox subpage.
    -- env.testcasesTitle - the /testcases subpage.
    --
    -- Data includes:
    -- env.subjectSpace - the number of the title's subject namespace.
    -- env.docSpace - the number of the namespace the title puts its documentation in.
    -- env.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
    -- env.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.
    --
    -- All table lookups are passed through pcall so that errors are caught. If an error occurs, the value
    -- returned will be nil.
    --]]
   
    local env, envFuncs = {}, {}
 
    -- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
    -- returned by that function is memoized in the env table so that we don't call any of the functions
    -- more than once. (Nils won't be memoized.)
    setmetatable(env, {
        __index = function (t, key)
            local envFunc = envFuncs[key]
            if envFunc then
                local success, val = pcall(envFunc)
                if success then
                    env[key] = val -- Memoise the value.
                    return val
                end
            end
            return nil
        end
    }) 
 
    function envFuncs.title()
        -- The title object for the current page, or a test page passed with args.page.
        local title
        local titleArg = args.page
        if titleArg then
            title = mw.title.new(titleArg)
        else
            title = mw.title.getCurrentTitle()
        end
        return title
    end
 
    function envFuncs.templateTitle()
        --[[
        -- The template (or module, etc.) title object.
        -- Messages:
        -- 'sandbox-subpage' --> 'sandbox'
        -- 'testcases-subpage' --> 'testcases'
        --]]
        local subjectSpace = env.subjectSpace
        local title = env.title
        local subpage = title.subpageText
        if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') then
            return mw.title.makeTitle(subjectSpace, title.baseText)
        else
            return mw.title.makeTitle(subjectSpace, title.text)
        end
    end
 
    function envFuncs.docTitle()
        --[[
        -- Title object of the /doc subpage.
        -- Messages:
        -- 'doc-subpage' --> 'doc'
        --]]
        local title = env.title
        local docname = args[1] -- User-specified doc page.
        local docpage
        if docname then
            docpage = docname
        else
            docpage = env.docpageBase .. '/' .. message('doc-subpage')
        end
        return mw.title.new(docpage)
    end
   
    function envFuncs.sandboxTitle()
        --[[
        -- Title object for the /sandbox subpage.
        -- Messages:
        -- 'sandbox-subpage' --> 'sandbox'
        --]]
        return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
    end
   
    function envFuncs.testcasesTitle()
        --[[
        -- Title object for the /testcases subpage.
        -- Messages:
        -- 'testcases-subpage' --> 'testcases'
        --]]
        return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
    end
 
    function envFuncs.subjectSpace()
        -- The subject namespace number.
        return mw.site.namespaces[env.title.namespace].subject.id
    end
 
    function envFuncs.docSpace()
        -- The documentation namespace number. For most namespaces this is the same as the
        -- subject namespace. However, pages in the Article, File, MediaWiki, Category or Widget
        -- namespaces must have their /doc, /sandbox and /testcases pages in talk space.
        local subjectSpace = env.subjectSpace
        if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 or subjectSpace == 274 then
            return subjectSpace + 1
        else
            return subjectSpace
        end
    end
 
    function envFuncs.docpageBase()
        -- The base page of the /doc, /sandbox, and /testcases subpages.
        -- For some namespaces this is the talk page, rather than the template page.
        local templateTitle = env.templateTitle
        local docSpace = env.docSpace
        local docSpaceText = mw.site.namespaces[docSpace].name
        -- Assemble the link. docSpace is never the main namespace, so we can hardcode the colon.
        return docSpaceText .. ':' .. templateTitle.text
    end
   
    function envFuncs.compareUrl()
        -- Diff link between the sandbox and the main template using [[Special:ComparePages]].
        local templateTitle = env.templateTitle
        local sandboxTitle = env.sandboxTitle
        if templateTitle.exists and sandboxTitle.exists then
            local compareUrl = mw.uri.fullUrl(
                'Special:ComparePages',
                {page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
            )
            return tostring(compareUrl)
        else
            return nil
        end
    end   
 
    return env
end
 
----------------------------------------------------------------------------
-- Auxiliary templates
----------------------------------------------------------------------------
 
function p.sandboxNotice(args, env)
    --[=[
    -- Generates a sandbox notice for display above sandbox pages.
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    --
    -- Messages:
    -- 'sandbox-notice-image' --> '[[File:Sandbox.svg|50px|alt=|link=]]'
    -- 'sandbox-notice-blurb' --> 'This is the $1 for $2.'
    -- 'sandbox-notice-diff-blurb' --> 'This is the $1 for $2 ($3).'
    -- 'sandbox-notice-pagetype-template' --> '[[Wikipedia:Template test cases|template sandbox]] page'
    -- 'sandbox-notice-pagetype-module' --> '[[Wikipedia:Template test cases|module sandbox]] page'
    -- 'sandbox-notice-pagetype-other' --> 'sandbox page'
    -- 'sandbox-notice-compare-link-display' --> 'diff'
    -- 'sandbox-notice-testcases-blurb' --> 'See also the companion subpage for $1.'
    -- 'sandbox-notice-testcases-link-display' --> 'test cases'
    -- 'sandbox-category' --> 'Template sandboxes'
    --]=]
    local title = env.title
    local sandboxTitle = env.sandboxTitle
    local templateTitle = env.templateTitle
    local subjectSpace = env.subjectSpace
    if not (subjectSpace and title and sandboxTitle and templateTitle and mw.title.equals(title, sandboxTitle)) then
        return nil
    end
    -- Build the table of arguments to pass to {{ombox}}. We need just two fields, "image" and "text".
    local mboxargs = {
        type = 'content',
        image = message('sandbox-notice-image'),
    }
    -- Get the text. We start with the opening blurb, which is something like
    -- "This is the template sandbox for [[Template:Foo]] (diff)."
    local pagetype
    if subjectSpace == 10 then
        pagetype = message('sandbox-notice-pagetype-template')
    elseif subjectSpace == 828 then
        pagetype = message('sandbox-notice-pagetype-module')
    else
        pagetype = message('sandbox-notice-pagetype-other')
    end
    local templateLink = makeWikilink(templateTitle.prefixedText)
    local compareUrl = env.compareUrl
    if compareUrl then
        local compareDisplay = message('sandbox-notice-compare-link-display')
        local compareLink = mw.html.create('span')
            :addClass('plainlinks')
            :wikitext(makeUrlLink(compareUrl, compareDisplay))
        mboxargs.title = message('sandbox-notice-diff-blurb', {pagetype, templateLink, tostring(compareLink)})
    else
        mboxargs.title = message('sandbox-notice-blurb', {pagetype, templateLink})
    end
    -- Get the test cases page blurb if the page exists. This is something like
    -- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
    local testcasesTitle = env.testcasesTitle
    if testcasesTitle and testcasesTitle.exists then
        if testcasesTitle.contentModel == "Scribunto" then
            local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
            local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
            local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
            local testcasesRunLink = makeWikilink(testcasesTitle.prefixedText, testcasesRunLinkDisplay)
            mboxargs.text = message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
        else
            local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
            local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
            mboxargs.text = message('sandbox-notice-testcases-blurb', {testcasesLink})
        end
    end
    local ret = {
        mw.getCurrentFrame():expandTemplate{title = message('template-message-box'), args = mboxargs},
        makeCategoryLink(message('sandbox-category')) -- Add the sandbox to the sandbox category.
    }
    return table.concat(ret)
end
end


function p.protectionTemplate()
----------------------------------------------------------------------------
if currentTitle.namespace == 10 then -- We are in the template namespace.
-- Start box
local frame = mw.getCurrentFrame()
----------------------------------------------------------------------------


local function getProtectionLevel(protectionType)
p.startBox = makeInvokeFunc('_startBox')
-- Gets the protection level for the current page.
local level = frame:callParserFunction('PROTECTIONLEVEL', protectionType)
if level ~= '' then
return level
else
return nil -- The parser function returns the blank string if there is no match.
end
end


if getProtectionLevel('move') == 'sysop' or getProtectionLevel('edit') then
function p._startBox(args, env)
-- The page is full-move protected, or full, template, or semi-protected.
    --[[
return frame:expandTemplate{title = 'pp-template', args = {docusage = 'yes'}}
    -- This function generates the start box.
end
    -- @args - a table of arguments passed by the user
end
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
return nil
    --
    -- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
    -- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
    -- which generate the box HTML.
    --]]
    env = env or p.getEnvironment(args)
    local links
    local docTitle = env.docTitle
    if not args.content or docTitle.exists then
        -- No need to include the links if the documentation is on the template page itself.
        local linksData = p.makeStartBoxLinksData(args, env)
        if linksData then
            links = p.renderStartBoxLinks(linksData)
        end
    end
    -- Generate the start box html.
    local data = p.makeStartBoxData(args, env, links)
    if data then
        return p.renderStartBox(data)
    else
        -- User specified no heading.
        return nil
    end
end
end


function p.startBox(args)
function p.makeStartBoxLinksData(args, env)
-- Arg processing from {{documentation}}.
    --[[
local preload = args.preload -- Allow custom preloads.
    -- Does initial processing of data to make the [view] [edit] [history] [purge] links.
local heading = args.heading -- Blank values are not removed.
    -- @args - a table of arguments passed by the user
local headingStyle = args['heading-style']
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
local content = args.content
    --
local docspace = p.docspace()
    -- Messages:
local docname = args[1] -- Other docname, if fed.
    -- 'view-link-display' --> 'view'
local templatePage = p.templatePage()
    -- 'edit-link-display' --> 'edit'
    -- 'history-link-display' --> 'history'
    -- 'purge-link-display' --> 'purge'
    -- 'module-preload' --> 'Template:Documentation/preload-module-doc'
    -- 'docpage-preload' --> 'Template:Documentation/preload'
    -- 'create-link-display' --> 'create'
    --]]
    local subjectSpace = env.subjectSpace
    local title = env.title
    local docTitle = env.docTitle
    if not title or not docTitle then
        return nil
    end
    if docTitle.isRedirect then
        docTitle = docTitle.redirectTarget
    end


-- Arg processing from {{documentation/start box2}}.
    local data = {}
local docpage
    data.title = title
if docname then
    data.docTitle = docTitle
docpage = docname
    -- View, display, edit, and purge links if /doc exists.
else
    data.viewLinkDisplay = message('view-link-display')
local namespace = docspace or currentTitle.nsText
    data.editLinkDisplay = message('edit-link-display')
local pagename = templatePage or currentTitle.text
    data.historyLinkDisplay = message('history-link-display')
docpage = namespace .. ':' .. pagename .. '/doc'
    data.purgeLinkDisplay = message('purge-link-display')
end
    -- Create link if /doc doesn't exist.
local docTitle = mw.title.new(docpage)
    local preload = args.preload
local docExist = docTitle.exists
    if not preload then
        if subjectSpace == 828 then -- Module namespace
-- Output from {{documentation/start box}}.
            preload = message('module-preload')
        else
            preload = message('docpage-preload')
        end
    end
    data.preload = preload
    data.createLinkDisplay = message('create-link-display')
    return data
end


-- First, check the heading parameter.
function p.renderStartBoxLinks(data)
if heading == '' then
    --[[
-- Heading is defined but blank, so do nothing.
    -- Generates the [view][edit][history][purge] or [create][purge] links from the data table.
return nil
    -- @data - a table of data generated by p.makeStartBoxLinksData
end
    --]]
   
    local function escapeBrackets(s)
        -- Escapes square brackets with HTML entities.
        s = s:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
        s = s:gsub('%]', '&#93;')
        return s
    end


-- Build the start box div.
    local ret
local sbox = htmlBuilder.create('div')
    local docTitle = data.docTitle
sbox
    local title = data.title
.css('padding-bottom', '3px')
    local purgeLink = makeUrlLink(title:fullUrl{action = 'purge'}, data.purgeLinkDisplay)
.css('border-bottom', '1px solid #aaa')
    if docTitle.exists then
.css('margin-bottom', '1ex')
        local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
        local editLink = makeUrlLink(docTitle:fullUrl{action = 'edit'}, data.editLinkDisplay)
        local historyLink = makeUrlLink(docTitle:fullUrl{action = 'history'}, data.historyLinkDisplay)
        local purgeLink = makeUrlLink(title:fullUrl{action = 'purge'}, data.purgeLinkDisplay)
        ret = '[%s] [%s] [%s] [%s]'
        ret = escapeBrackets(ret)
        ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink)
    else
        local createLink = makeUrlLink(docTitle:fullUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay)
        ret = '[%s] [%s]'
        ret = escapeBrackets(ret)
        ret = mw.ustring.format(ret, createLink, purgeLink)
    end
    return ret
end


-- Make the heading.
function p.makeStartBoxData(args, env, links)
local hspan = sbox.tag('span')
    --[=[
if headingStyle then
    -- Does initial processing of data to pass to the start-box render function, p.renderStartBox.
hspan.cssText(headingStyle)
    -- @args - a table of arguments passed by the user
elseif subjectSpace == 10 then
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
-- We are in the template or template talk namespaces.
    -- @links - a string containing the [view][edit][history][purge] links - could be nil if there's an error.
hspan
    --
.css('font-weight', 'bold')
    -- Messages:
.css('fond-size', '125%')
    -- 'documentation-icon-wikitext' --> '[[File:Test Template Info-Icon - Version (2).svg|50px|link=|alt=]]'
else
    -- 'template-namespace-heading' --> 'Template documentation'
hspan.css('font-size', '150%')
    -- 'module-namespace-heading' --> 'Module documentation'
end
    -- 'widget-namespace-heading' --> 'Widget documentation'
if heading then
    -- 'file-namespace-heading' --> 'Summary'
-- "heading" has data.
    -- 'other-namespaces-heading' --> 'Documentation'
hspan.wikitext(heading)
    -- 'start-box-classes' --> 'template-documentation__start'
elseif subjectSpace == 10 then -- Template namespace
    -- 'start-box-heading-classes' --> 'template-documentation__heading'
hspan.wikitext('[[File:Template-info.png|50px|link=|alt=Documentation icon]] Template documentation')
    -- 'start-box-links-id' --> 'doc_editlinks'
elseif subjectSpace == 828 then -- Module namespace
    -- 'start-box-links-classes' --> 'mw-editsection-like plainlinks'
hspan.wikitext('[[File:Template-info.png|50px|link=|alt=Documentation icon]] Module documentation')
    -- 'testcases-create-link-display' --> 'create'
elseif subjectSpace == 6 then -- File namespace
    --]=]
hspan.wikitext('Summary')
    local subjectSpace = env.subjectSpace
else
    if not subjectSpace then
hspan.wikitext('Documentation')
        -- Default to an "other namespaces" namespace, so that we get at least some output
end
        -- if an error occurs.
        subjectSpace = 2
    end
    local data = {}
   
    -- Heading
    local heading = args.heading -- Blank values are not removed.
    if heading == '' then
        -- Don't display the start box if the heading arg is defined but blank.
        return nil
    end
    if heading then
        data.heading = heading
    elseif subjectSpace == 10 then -- Template namespace
        data.heading = message('documentation-icon-wikitext') .. ' ' .. message('template-namespace-heading')
    elseif subjectSpace == 828 then -- Module namespace
        data.heading = message('documentation-icon-wikitext') .. ' ' .. message('module-namespace-heading')
    elseif subjectSpace == 274 then -- Widget namespace
        data.heading = message('documentation-icon-wikitext') .. ' ' .. message('widget-namespace-heading')
    elseif subjectSpace == 6 then -- File namespace
        data.heading = message('file-namespace-heading')
    else
        data.heading = message('other-namespaces-heading')
    end


-- Add the [view][edit][history][purge] or [create] links.
    -- CSS
-- Check for the content parameter first, as we don't need the links if the documentation
    data.startClass = message('start-box-classes');
-- content is being entered directly onto the template page.
   
if not content then
    -- Heading CSS
local lspan = sbox.tag('span') -- lspan is short for "link span".
    data.headingClass = message('start-box-heading-classes');
lspan
    local headingStyle = args['heading-style']
.addClass('mw-editsection plainlinks')
    if headingStyle then
.attr('id', 'doc_editlinks')
        data.headingStyleText = headingStyle
if docExist then
    end
local viewLink = makeWikilink(docpage, 'view')
   
local editLink = makeUrlLink(docTitle:fullUrl{action = 'edit'}, 'edit')
    -- Data for the [view][edit][history][purge] or [create] links.
local historyLink = makeUrlLink(docTitle:fullUrl{action = 'history'}, 'history')
    if links then
local purgeLink = makeUrlLink(docTitle:fullUrl{action = 'purge'}, 'purge')
        data.linksId = message('start-box-links-id')
local text = '[%s] [%s] [%s] [%s]'
        data.linksClass = message('start-box-links-classes')
text = text:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
        data.links = links
text = text:gsub('%]', '&#93;')
    end
lspan.wikitext(mw.ustring.format(text, viewLink, editLink, historyLink, purgeLink))
   
else
    return data
if not preload then
end
if subjectSpace == 6 then -- File namespace
preload = 'Template:Documentation/preload-filespace'
else
preload = 'Template:Documentation/preload'
end
end
lspan.wikitext(makeUrlLink(docTitle:fullUrl{action = 'edit', preload = preload}, 'create'))
end
end


return tostring(sbox)
function p.renderStartBox(data)
    -- Renders the start box html.
    -- @data - a table of data generated by p.makeStartBoxData.
    local sbox = mw.html.create('div')
    sbox
        :addClass(data.startClass)
        :newline()
        :tag('span')
            :addClass(data.headingClass)
            :cssText(data.headingStyleText)
            :wikitext(data.heading)
    local links = data.links
    if links then
        sbox:tag('span')
            :attr('id', data.linksId)
            :addClass(data.linksClass)
            :wikitext(links)
    end
    return tostring(sbox)
end
end


function p.content(args)
----------------------------------------------------------------------------
local content = args.content
-- Documentation content
if not content then
----------------------------------------------------------------------------
local docpage = args[1]
 
if docpage and mw.title.new(docpage).exists then
p.content = makeInvokeFunc('_content')
local frame = mw.getCurrentFrame()
 
content = frame:preprocess('{{ ' .. docpage .. ' }}')
function p._content(args, env)
else
    -- Displays the documentation contents
docpage = p.docspace() .. ':' .. p.templatePage() .. '/doc'
    -- @args - a table of arguments passed by the user
if mw.title.new(docpage).exists then
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
local frame = mw.getCurrentFrame()
    env = env or p.getEnvironment(args)
content = frame:preprocess('{{ ' .. docpage .. ' }}')
    local docTitle = env.docTitle
end
    local content = ''
end
    if docTitle and docTitle.exists then
end
        content = args._content or mw.getCurrentFrame():expandTemplate{title = docTitle.prefixedText}
-- The line breaks below are necessary so that "=== Headings ===" at the start and end
    end
-- of docs are interpreted correctly.
    if args.content then
return '\n' .. (content or '') .. '\n'  
        content = args.content .. '\n' .. content
    end
    -- The line breaks below are necessary so that "=== Headings ===" at the start and end
    -- of docs are interpreted correctly.
    return '\n' .. content .. '\n'  
end
end


function p.endBox(args)
----------------------------------------------------------------------------
-- Argument processing in {{documentation}}.
-- End box
local preload = args.preload -- Allow custom preloads.
----------------------------------------------------------------------------
local content = args.content
 
local linkBox = args['link box'] -- So "link box=off" works.
p.endBox = makeInvokeFunc('_endBox')
local docspace = p.docspace()
local docname = args[1] -- Other docname, if fed.
local templatePage = p.templatePage()


-- Argument processing in {{documentation/end box2}}.
function p._endBox(args, env)
local docpageRoot = (docspace or currentTitle.nsText) .. ':' .. (templatePage or currentTitle.text)
    --[=[
local docpage
    -- This function generates the end box (also known as the link box).
if docname then
    -- @args - a table of arguments passed by the user
docpage = docname
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
else
    --
docpage = docpageRoot .. '/doc'
    -- Messages:
end
    -- 'end-box-id' --> 'documentation-meta-data'
local docTitle = mw.title.new(docpage)
    -- 'end-box-classes' --> 'template-documentation__end'
local docExist = docTitle.exists
    --]=]
local docnameFed = args[1] and true
   
local sandbox = docpageRoot .. '/sandbox'
    -- Get environment data.
local testcases = docpageRoot .. '/testcases'
    env = env or p.getEnvironment(args)
templatePage = currentTitle.nsText .. ':' .. templatePage
    local subjectSpace = env.subjectSpace
    local docTitle = env.docTitle
    if not subjectSpace or not docTitle then
        return nil
    end
       
    -- Check whether we should output the end box at all. Add the end
    -- box by default if the documentation exists or if we are in the
    -- user, module or template namespaces.
    local linkBox = args['link box']
    if linkBox == 'off'
        or not (
            docTitle.exists
            or subjectSpace == 2
            or subjectSpace == 828
            or subjectSpace == 10
        )
    then
        return nil
    end


-- Output from {{documentation/end box}}
    -- Assemble the end box text
    local text = ''
-- First, check whether we should output the end box at all. Add the end box by default if the documentation
    if linkBox then
-- exists or if we are in the user, module or template namespaces.
        text = text .. linkBox
if linkBox == 'off' or not (docExist or subjectSpace == 2 or subjectSpace == 828 or subjectSpace == 10) then
    else
return nil
        text = text .. (p.makeDocPageBlurb(args, env) or '') -- "This documentation is transcluded from [[Foo]]."
end
        if subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828 then
            -- We are in the user, template or module namespaces.
            -- Add sandbox and testcases links.
            -- "Editors can experiment in this template's sandbox and testcases pages."
            text = text .. (p.makeExperimentBlurb(args, env) or '')
            text = text .. '<br>'
            if not args[1] and (not args.content or docTitle.exists) then
                -- "Please add categories to the /doc subpage."
                -- Don't show this message with inline docs or with an explicitly specified doc page,
                -- as then it is unclear where to add the categories.
                text = text .. (p.makeCategoriesBlurb(args, env) or '')
            end
            text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') --"Subpages of this template"
        end
    end


-- Assemble the arguments for {{fmbox}}.
    local endId = message('end-box-id')
local fmargs = {}
    local endClass = message('end-box-classes')
fmargs.id = 'documentation-meta-data'
    local ebox = mw.html.create('div')
fmargs.image = 'none'
    ebox
fmargs.style = 'background-color: #ecfcf4'
        :attr('id', endId)
fmargs.textstyle = 'font-style: italic;'
        :addClass(endClass)
        :newline()
        :wikitext(text)
    return tostring(ebox)
end


-- Assemble the fmbox text field.
function p.makeDocPageBlurb(args, env)
local text = ''
    --[=[
if linkBox then
    -- Makes the blurb "This documentation is transcluded from [[Template:Foo]] (edit, history)".
-- Use custom link box content if it is defined.
    -- @args - a table of arguments passed by the user
text = text .. linkBox
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
else
    --
if docExist then
    -- Messages:
-- /doc exists; link to it.
    -- 'edit-link-display' --> 'edit'
local docLink = makeWikilink(docpage)
    -- 'history-link-display' --> 'history'
local editLink = makeUrlLink(docTitle:fullUrl{action = 'edit'}, 'edit')
    -- 'transcluded-from-blurb' -->
local historyLink = makeUrlLink(docTitle:fullUrl{action = 'history'}, 'history')
    -- 'The above [[Wikipedia:Template documentation|documentation]]  
text = text .. 'The above [[Wikipedia:Template documentation|documentation]] is [[Wikipedia:Transclusion|transcluded]] from ' .. docLink .. '.'
    -- is [[Help:Transclusion|transcluded]] from $1.'
.. ' <small style="font-style: normal;">(' .. editLink .. ' &#124; ' .. historyLink .. ')</small> <br />'
    -- 'module-preload' --> 'Template:Documentation/preload-module-doc'
elseif subjectSpace == 828 then
    -- 'create-link-display' --> 'create'
-- /doc does not exist; ask to create it.
    -- 'create-module-doc-blurb' -->
local createLink = makeUrlLink(docTitle:fullUrl{action = 'edit', preload = 'Template:Documentation/preload-module-doc'}, 'create')
    -- 'You might want to $1 a documentation page for this [[Wikipedia:Lua|Scribunto module]].'
text = text .. 'You might want to ' .. createLink .. ' a documentation page for this [[Wikipedia:Lua|Scribunto module]].<br />'
    --]=]
end
    local docTitle = env.docTitle
-- Add links to /sandbox and /testcases when appropriate.
    if not docTitle then
if subjectSpace == 2 or subjectSpace == 828 or subjectSpace == 10 then
        return nil
-- We are in the user, module or template namespaces.
    end
local pagePossessive = subjectSpace == 828 and "module's" or "template's"
    local ret
text = text .. 'Editors can experiment in this ' .. pagePossessive .. ' '
    if docTitle.exists then
local sandboxTitle = mw.title.new(sandbox)
        -- /doc exists; link to it.
if sandboxTitle.exists then
        local docLink = makeWikilink(docTitle.prefixedText)
local sandboxLink = makeWikilink(sandbox, 'sandbox')
        local editUrl = docTitle:fullUrl{action = 'edit'}
local sandboxEditLink = makeUrlLink(sandboxTitle:fullUrl{action = 'edit'}, 'edit')
        local editDisplay = message('edit-link-display')
local compareLink = makeUrlLink(mw.title.new('Special:ComparePages'):fullUrl{page1 = templatePage, page2 = sandbox}, 'diff')
        local editLink = makeUrlLink(editUrl, editDisplay)
text = text .. sandboxLink .. ' <small style="font-style: normal">(' .. sandboxEditLink .. ' | ' .. compareLink .. ')</small>'
        local historyUrl = docTitle:fullUrl{action = 'history'}
else
        local historyDisplay = message('history-link-display')
local sandboxPreload = 'Template:Documentation/preload-' .. (subjectSpace == 828 and 'module-' or '') .. 'sandbox'
        local historyLink = makeUrlLink(historyUrl, historyDisplay)
local sandboxCreateLink = makeUrlLink(sandboxTitle:fullUrl{action = 'edit', preload = sandboxPreload}, 'create')
        ret = message('transcluded-from-blurb', {docLink})
local mirrorSummary = 'Create sandbox version of ' .. makeWikilink(templatePage)
            .. ' '
local mirrorLink = makeUrlLink(sandboxTitle:fullUrl{action = 'edit', preload = templatePage, summary = mirrorSummary}, 'mirror')
            .. makeToolbar(editLink, historyLink)
text = text .. 'sandbox <small style="font-style: normal">(' .. sandboxCreateLink .. ' | ' .. mirrorLink .. ')</small>'
            .. '<br>'
end
    elseif env.subjectSpace == 10 then
text = text .. ' and '
        -- /doc does not exist for this template; ask to create it.
local testcaseTitle = mw.title.new(testcases)
        local createUrl = docTitle:fullUrl{action = 'edit', preload = message('docpage-preload')}
if testcaseTitle.exists then
        local createDisplay = message('create-link-display')
local testcasesLink = makeWikilink(testcases, 'testcases')
        local createLink = makeUrlLink(createUrl, createDisplay)
local testcasesEditLink = makeUrlLink(testcaseTitle:fullUrl{action = 'edit'}, 'edit')
        ret = message('create-template-doc-blurb', {createLink})
text = text .. testcasesLink .. ' <small style="font-style: normal">(' .. testcasesEditLink .. ')</small>'
            .. '<br>'
else
    elseif env.subjectSpace == 828 then
local testcasesPreload = 'Template:Documentation/preload-' .. (subjectSpace == 828 and 'module-' or '') .. 'testcases'
        -- /doc does not exist for this module; ask to create it.
local testcasesCreateLink = makeUrlLink(testcaseTitle:fullUrl{action = 'edit', preload = testcasesPreload}, 'create')
        local createUrl = docTitle:fullUrl{action = 'edit', preload = message('module-preload')}
text = text .. 'testcases <small style="font-style: normal">(' .. testcasesCreateLink .. ')</small>'
        local createDisplay = message('create-link-display')
end
        local createLink = makeUrlLink(createUrl, createDisplay)
text = text .. ' pages. <br />'
        ret = message('create-module-doc-blurb', {createLink})
-- Show the categories text, but not if "content" fed or "docname fed" since then it is unclear where to add the categories.
            .. '<br>'
if not content and not docnameFed then
    end
text = text .. 'Please add categories to the ' .. makeWikilink(docpage, '/doc') .. ' subpage.'
    return ret
end
end
-- Show the "subpages" link.
if subjectSpace == 828 then -- Module space.
text = text .. ' ' .. makeWikilink('Special:PrefixIndex/' .. templatePage .. '/', 'Subpages of this module')
elseif subjectSpace == 10 then -- Template space.
text = text .. ' ' .. makeWikilink('Special:PrefixIndex/' .. templatePage .. '/', 'Subpages of this template')
elseif subjectSpace ~= 6 then -- Don't show the link in file space.
text = text .. ' ' .. makeWikilink('Special:PrefixIndex/' .. templatePage .. '/', 'Subpages of this page')
end
-- Show the "print" link if it exists.
local printPage = templatePage .. '/Print'
local printTitle = mw.title.new(printPage)
if printTitle.exists then
text = text .. '<br />A [[Help:Books/for experts#Improving the book layout|print version]] of this template exists at ' .. makeWikilink(printPage, '/Print') .. '.'
.. 'If you make a change to this template, please update the print version as well.[[Category:Templates with print versions]]'
end
end
end
fmargs.text = text


-- Return the fmbox output.
function p.makeExperimentBlurb(args, env)
return messageBox.main('fmbox', fmargs)
    --[[
    -- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    --
    -- Messages:
    -- 'sandbox-link-display' --> 'sandbox'
    -- 'sandbox-edit-link-display' --> 'edit'
    -- 'compare-link-display' --> 'diff'
    -- 'module-sandbox-preload' --> 'Template:Documentation/preload-module-sandbox'
    -- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
    -- 'sandbox-create-link-display' --> 'create'
    -- 'mirror-edit-summary' --> 'Create sandbox version of $1'
    -- 'mirror-link-display' --> 'mirror'
    -- 'mirror-link-preload' --> 'Template:Documentation/mirror'
    -- 'sandbox-link-display' --> 'sandbox'
    -- 'testcases-link-display' --> 'testcases'
    -- 'testcases-edit-link-display'--> 'edit'
    -- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
    -- 'testcases-create-link-display' --> 'create'
    -- 'testcases-link-display' --> 'testcases'
    -- 'testcases-edit-link-display' --> 'edit'
    -- 'module-testcases-preload' --> 'Template:Documentation/preload-module-testcases'
    -- 'template-testcases-preload' --> 'Template:Documentation/preload-testcases'
    -- 'experiment-blurb-module' --> 'Editors can experiment in this module's $1 and $2 pages.'
    -- 'experiment-blurb-template' --> 'Editors can experiment in this template's $1 and $2 pages.'
    --]]
    local subjectSpace = env.subjectSpace
    local templateTitle = env.templateTitle
    local sandboxTitle = env.sandboxTitle
    local testcasesTitle = env.testcasesTitle
    local templatePage = templateTitle.prefixedText
    if not subjectSpace or not templateTitle or not sandboxTitle or not testcasesTitle then
        return nil
    end
    -- Make links.
    local sandboxLinks, testcasesLinks
    if sandboxTitle.exists then
        local sandboxPage = sandboxTitle.prefixedText
        local sandboxDisplay = message('sandbox-link-display')
        local sandboxLink = makeWikilink(sandboxPage, sandboxDisplay)
        local sandboxEditUrl = sandboxTitle:fullUrl{action = 'edit'}
        local sandboxEditDisplay = message('sandbox-edit-link-display')
        local sandboxEditLink = makeUrlLink(sandboxEditUrl, sandboxEditDisplay)
        local compareUrl = env.compareUrl
        local compareLink
        if compareUrl then
            local compareDisplay = message('compare-link-display')
            compareLink = makeUrlLink(compareUrl, compareDisplay)
        end
        sandboxLinks = sandboxLink .. ' ' .. makeToolbar(sandboxEditLink, compareLink)
    else
        local sandboxPreload
        if subjectSpace == 828 then
            sandboxPreload = message('module-sandbox-preload')
        else
            sandboxPreload = message('template-sandbox-preload')
        end
        local sandboxCreateUrl = sandboxTitle:fullUrl{action = 'edit', preload = sandboxPreload}
        local sandboxCreateDisplay = message('sandbox-create-link-display')
        local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay)
        local mirrorSummary = message('mirror-edit-summary', {makeWikilink(templatePage)})
        local mirrorPreload = message('mirror-link-preload')
        local mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = mirrorPreload, summary = mirrorSummary}
        if subjectSpace == 828 then
            mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = templateTitle.prefixedText, summary = mirrorSummary}
        end
        local mirrorDisplay = message('mirror-link-display')
        local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay)
        sandboxLinks = message('sandbox-link-display') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink)
    end
    if testcasesTitle.exists then
        local testcasesPage = testcasesTitle.prefixedText
        local testcasesDisplay = message('testcases-link-display')
        local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay)
        local testcasesEditUrl = testcasesTitle:fullUrl{action = 'edit'}
        local testcasesEditDisplay = message('testcases-edit-link-display')
        local testcasesEditLink = makeUrlLink(testcasesEditUrl, testcasesEditDisplay)
        -- for Modules, add testcases run link if exists
        if testcasesTitle.contentModel == "Scribunto" then
            local testcasesRunLinkDisplay = message('testcases-run-link-display')
            local testcasesRunLink = makeWikilink(testcasesTitle.prefixedText, testcasesRunLinkDisplay)
            testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink, testcasesRunLink)
        else
            testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink)
        end
    else
        local testcasesPreload
        if subjectSpace == 828 then
            testcasesPreload = message('module-testcases-preload')
        else
            testcasesPreload = message('template-testcases-preload')
        end
        local testcasesCreateUrl = testcasesTitle:fullUrl{action = 'edit', preload = testcasesPreload}
        local testcasesCreateDisplay = message('testcases-create-link-display')
        local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay)
        testcasesLinks = message('testcases-link-display') .. ' ' .. makeToolbar(testcasesCreateLink)
    end
    local messageName
    if subjectSpace == 828 then
        messageName = 'experiment-blurb-module'
    else
        messageName = 'experiment-blurb-template'
    end
    return message(messageName, {sandboxLinks, testcasesLinks})
end
end


function p.addTrackingCategories()
function p.makeCategoriesBlurb(args, env)
-- Check if {{documentation}} is transcluded on a /doc or /testcases page.
    --[[
local ret = ''
    -- Generates the text "Please add categories to the /doc subpage."
local subpage = currentTitle.subpageText
    -- @args - a table of arguments passed by the user
if subpage == 'doc' or subpage == 'testcases' then
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
local sort = (currentTitle.namespace == 0 and 'Main:' or '') .. currentTitle.prefixedText -- Sort on namespace.
    -- Messages:
ret = ret .. makeWikilink('Category:Wikipedia pages with strange ((documentation)) usage', sort)
    -- 'doc-link-display' --> '/doc'
end
    -- 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
return ret
    --]]
    local docTitle = env.docTitle
    if not docTitle then
        return nil
    end
    local docPathLink = makeWikilink(docTitle.prefixedText, message('doc-link-display'))
    return message('add-categories-blurb', {docPathLink})
end
end


function p.docspace()
function p.makeSubpagesBlurb(args, env)
-- Determines the namespace of the documentation.
    --[[
if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then
    -- Generates the "Subpages of this template" link.
-- Pages in the Article, File, MediaWiki or Category namespaces must have their
    -- @args - a table of arguments passed by the user
-- /doc, /sandbox and /testcases pages in talk space.
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
return mw.site.namespaces[subjectSpace].talk.name
   
else
    -- Messages:
return currentTitle.subjectNsText
    -- 'template-pagetype' --> 'template'
end
    -- 'module-pagetype' --> 'module'
    -- 'default-pagetype' --> 'page'
    -- 'subpages-link-display' --> 'Subpages of this $1'
    --]]
    local subjectSpace = env.subjectSpace
    local templateTitle = env.templateTitle
    if not subjectSpace or not templateTitle then
        return nil
    end
    local pagetype
    if subjectSpace == 10 then
        pagetype = message('template-pagetype')
    elseif subjectSpace == 828 then
        pagetype = message('module-pagetype')
    else
        pagetype = message('default-pagetype')
    end
    local subpagesLink = makeWikilink(
        'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/',
        message('subpages-link-display', {pagetype})
    )
    return message('subpages-blurb', {subpagesLink})
end
end


function p.templatePage()
----------------------------------------------------------------------------
-- Determines the template page. No namespace or interwiki prefixes are included.
-- Tracking categories
local subpage = currentTitle.subpageText
----------------------------------------------------------------------------
if subpage == 'sandbox' or subpage == 'testcases' then
 
return currentTitle.baseText
function p.addTrackingCategories(env)
else
    --[[
return currentTitle.text
    -- Check if {{documentation}} is transcluded on a /doc or /testcases page.
end
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
   
    -- Messages:
    -- 'display-strange-usage-category' --> true
    -- 'doc-subpage' --> 'doc'
    -- 'testcases-subpage' --> 'testcases'
    -- 'strange-usage-category' --> 'Wikipedia pages with strange ((documentation)) usage'
    --
    -- /testcases pages in the module namespace are not categorised, as they may have
    -- {{documentation}} transcluded automatically.
    --]]
    local title = env.title
    local subjectSpace = env.subjectSpace
    if not title or not subjectSpace then
        return nil
    end
    local subpage = title.subpageText
    local ret = ''
    if message('display-strange-usage-category', nil, 'boolean')
        and (
            subpage == message('doc-subpage')
            or subjectSpace ~= 828 and subpage == message('testcases-subpage')
        )
    then
        ret = ret .. makeCategoryLink(message('strange-usage-category'))
    end
    return ret
end
end


return p
return p

Latest revision as of 02:28, 15 June 2023

Module documentation[view] [edit] [history] [purge]

Lua logo

This module depends on the following other modules:

This module displays a section containing documentation for templates, modules, or other pages. It implements the {{documentation}} template.

Usage

In most cases, you should use the {{documentation}} template. Refer to the template's documentation for usage instructions and parameters.

To use this module from another module, first require it:

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

Then call the function with a table of arguments:

documentation{content = 'Some documentation', ['link box'] = 'My custom link box'}
This module was adapted from Module:Documentation on Wikipedia.
Adaptation is noted for reference and attribution only. This module may differ from the original in function or in usage.

-- This module implements {{documentation}}.

-- Get required modules.
local getArgs = require('Module:Arguments').getArgs

-- Get the config table.
local cfg = mw.loadData('Module:Documentation/config')

local p = {}

-- Often-used functions.
local ugsub = mw.ustring.gsub

----------------------------------------------------------------------------
-- Helper functions
--
-- These are defined as local functions, but are made available in the p
-- table for testing purposes.
----------------------------------------------------------------------------

local function message(cfgKey, valArray, expectType)
    --[[
    -- Gets a message from the cfg table and formats it if appropriate.
    -- The function raises an error if the value from the cfg table is not
    -- of the type expectType. The default type for expectType is 'string'.
    -- If the table valArray is present, strings such as $1, $2 etc. in the
    -- message are substituted with values from the table keys [1], [2] etc.
    -- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
    -- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
    --]]
    local msg = cfg[cfgKey]
    expectType = expectType or 'string'
    if type(msg) ~= expectType then
        error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
    end
    if not valArray then
        return msg
    end

    local function getMessageVal(match)
        match = tonumber(match)
        return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
    end

    local ret = ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
    return ret
end

p.message = message

local function makeWikilink(page, display)
    if display then
        return mw.ustring.format('[[%s|%s]]', page, display)
    else
        return mw.ustring.format('[[%s]]', page)
    end
end

p.makeWikilink = makeWikilink

local function makeCategoryLink(cat, sort)
    local catns = mw.site.namespaces[14].name
    return makeWikilink(catns .. ':' .. cat, sort)
end

p.makeCategoryLink = makeCategoryLink

local function makeUrlLink(url, display)
    return mw.ustring.format('[%s %s]', url, display)
end

p.makeUrlLink = makeUrlLink

local function makeToolbar(...)
    local ret = {}
    local lim = select('#', ...)
    if lim < 1 then
        return nil
    end
    for i = 1, lim do
        ret[#ret + 1] = select(i, ...)
    end
    local toolbar = mw.html.create('span')
    toolbar
        :addClass(message('end-box-toolbar-classes'))
        :wikitext('(' .. table.concat(ret, ' &#124; ') .. ')')
    return tostring(toolbar)
end 

p.makeToolbar = makeToolbar

----------------------------------------------------------------------------
-- Argument processing
----------------------------------------------------------------------------

local function makeInvokeFunc(funcName)
    return function (frame)
        local args = getArgs(frame, {
            valueFunc = function (key, value)
                if type(value) == 'string' then
                    value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
                    if key == 'heading' or value ~= '' then
                        return value
                    else
                        return nil
                    end
                else
                    return value
                end
            end
        })
        return p[funcName](args)
    end
end

----------------------------------------------------------------------------
-- Main function
----------------------------------------------------------------------------

p.main = makeInvokeFunc('_main')

function p._main(args)
    --[[
    -- This function defines logic flow for the module.
    -- @args - table of arguments passed by the user
    -- 
    -- Messages:
    -- 'main-div-id' --> 'template-documentation'
    -- 'main-div-classes' --> 'template-documentation iezoomfix'
    --]]
    local env = p.getEnvironment(args)
    local root = mw.html.create()
    root
        :tag('div')
            :cssText('clear: both;')
            :done()
        :wikitext(p.sandboxNotice(args, env))
         -- This div tag is from {{documentation/start box}}, but moving it here
         -- so that we don't have to worry about unclosed tags.
        :tag('div')
            :attr('id', message('main-div-id'))
            :addClass(message('main-div-classes'))
            :addClass('clearfix')
            :newline()
            :wikitext(p._startBox(args, env))
            :wikitext(p._content(args, env))
            :done()
        :wikitext(p._endBox(args, env))
        :wikitext(p.addTrackingCategories(env))
    return tostring(root)
end

----------------------------------------------------------------------------
-- Environment settings
----------------------------------------------------------------------------

function p.getEnvironment(args)
    --[[
    -- Returns a table with information about the environment, including title objects and other namespace- or
    -- path-related data.
    -- @args - table of arguments passed by the user
    --
    -- Title objects include:
    -- env.title - the page we are making documentation for (usually the current title)
    -- env.templateTitle - the template (or module, file, etc.)
    -- env.docTitle - the /doc subpage.
    -- env.sandboxTitle - the /sandbox subpage.
    -- env.testcasesTitle - the /testcases subpage.
    --
    -- Data includes:
    -- env.subjectSpace - the number of the title's subject namespace.
    -- env.docSpace - the number of the namespace the title puts its documentation in.
    -- env.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
    -- env.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.
    -- 
    -- All table lookups are passed through pcall so that errors are caught. If an error occurs, the value
    -- returned will be nil.
    --]]
    
    local env, envFuncs = {}, {}

    -- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
    -- returned by that function is memoized in the env table so that we don't call any of the functions
    -- more than once. (Nils won't be memoized.)
    setmetatable(env, {
        __index = function (t, key)
            local envFunc = envFuncs[key]
            if envFunc then
                local success, val = pcall(envFunc)
                if success then
                    env[key] = val -- Memoise the value.
                    return val
                end
            end
            return nil
        end
    })  

    function envFuncs.title()
        -- The title object for the current page, or a test page passed with args.page.
        local title
        local titleArg = args.page
        if titleArg then
            title = mw.title.new(titleArg)
        else
            title = mw.title.getCurrentTitle()
        end
        return title
    end

    function envFuncs.templateTitle()
        --[[
        -- The template (or module, etc.) title object.
        -- Messages:
        -- 'sandbox-subpage' --> 'sandbox'
        -- 'testcases-subpage' --> 'testcases'
        --]]
        local subjectSpace = env.subjectSpace
        local title = env.title
        local subpage = title.subpageText
        if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') then
            return mw.title.makeTitle(subjectSpace, title.baseText)
        else
            return mw.title.makeTitle(subjectSpace, title.text)
        end
    end

    function envFuncs.docTitle()
        --[[
        -- Title object of the /doc subpage.
        -- Messages:
        -- 'doc-subpage' --> 'doc'
        --]]
        local title = env.title
        local docname = args[1] -- User-specified doc page.
        local docpage
        if docname then
            docpage = docname
        else
            docpage = env.docpageBase .. '/' .. message('doc-subpage')
        end
        return mw.title.new(docpage)
    end
    
    function envFuncs.sandboxTitle()
        --[[
        -- Title object for the /sandbox subpage.
        -- Messages:
        -- 'sandbox-subpage' --> 'sandbox'
        --]]
        return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
    end
    
    function envFuncs.testcasesTitle()
        --[[
        -- Title object for the /testcases subpage.
        -- Messages:
        -- 'testcases-subpage' --> 'testcases'
        --]]
        return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
    end

    function envFuncs.subjectSpace()
        -- The subject namespace number.
        return mw.site.namespaces[env.title.namespace].subject.id
    end

    function envFuncs.docSpace()
        -- The documentation namespace number. For most namespaces this is the same as the
        -- subject namespace. However, pages in the Article, File, MediaWiki, Category or Widget
        -- namespaces must have their /doc, /sandbox and /testcases pages in talk space.
        local subjectSpace = env.subjectSpace
        if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 or subjectSpace == 274 then
            return subjectSpace + 1
        else
            return subjectSpace
        end
    end

    function envFuncs.docpageBase()
        -- The base page of the /doc, /sandbox, and /testcases subpages.
        -- For some namespaces this is the talk page, rather than the template page.
        local templateTitle = env.templateTitle
        local docSpace = env.docSpace
        local docSpaceText = mw.site.namespaces[docSpace].name
        -- Assemble the link. docSpace is never the main namespace, so we can hardcode the colon.
        return docSpaceText .. ':' .. templateTitle.text
    end
    
    function envFuncs.compareUrl()
        -- Diff link between the sandbox and the main template using [[Special:ComparePages]].
        local templateTitle = env.templateTitle
        local sandboxTitle = env.sandboxTitle
        if templateTitle.exists and sandboxTitle.exists then
            local compareUrl = mw.uri.fullUrl(
                'Special:ComparePages',
                {page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
            )
            return tostring(compareUrl)
        else
            return nil
        end
    end     

    return env
end 

----------------------------------------------------------------------------
-- Auxiliary templates
----------------------------------------------------------------------------

function p.sandboxNotice(args, env)
    --[=[
    -- Generates a sandbox notice for display above sandbox pages.
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- 
    -- Messages:
    -- 'sandbox-notice-image' --> '[[File:Sandbox.svg|50px|alt=|link=]]'
    -- 'sandbox-notice-blurb' --> 'This is the $1 for $2.'
    -- 'sandbox-notice-diff-blurb' --> 'This is the $1 for $2 ($3).'
    -- 'sandbox-notice-pagetype-template' --> '[[Wikipedia:Template test cases|template sandbox]] page'
    -- 'sandbox-notice-pagetype-module' --> '[[Wikipedia:Template test cases|module sandbox]] page'
    -- 'sandbox-notice-pagetype-other' --> 'sandbox page'
    -- 'sandbox-notice-compare-link-display' --> 'diff'
    -- 'sandbox-notice-testcases-blurb' --> 'See also the companion subpage for $1.'
    -- 'sandbox-notice-testcases-link-display' --> 'test cases'
    -- 'sandbox-category' --> 'Template sandboxes'
    --]=]
    local title = env.title
    local sandboxTitle = env.sandboxTitle
    local templateTitle = env.templateTitle
    local subjectSpace = env.subjectSpace
    if not (subjectSpace and title and sandboxTitle and templateTitle and mw.title.equals(title, sandboxTitle)) then
        return nil
    end
    -- Build the table of arguments to pass to {{ombox}}. We need just two fields, "image" and "text".
    local mboxargs = {
        type = 'content',
        image = message('sandbox-notice-image'),
    }
    -- Get the text. We start with the opening blurb, which is something like
    -- "This is the template sandbox for [[Template:Foo]] (diff)."
    local pagetype
    if subjectSpace == 10 then
        pagetype = message('sandbox-notice-pagetype-template')
    elseif subjectSpace == 828 then
        pagetype = message('sandbox-notice-pagetype-module')
    else
        pagetype = message('sandbox-notice-pagetype-other')
    end
    local templateLink = makeWikilink(templateTitle.prefixedText)
    local compareUrl = env.compareUrl
    if compareUrl then
        local compareDisplay = message('sandbox-notice-compare-link-display')
        local compareLink = mw.html.create('span')
            :addClass('plainlinks')
            :wikitext(makeUrlLink(compareUrl, compareDisplay))
        mboxargs.title = message('sandbox-notice-diff-blurb', {pagetype, templateLink, tostring(compareLink)})
    else
        mboxargs.title = message('sandbox-notice-blurb', {pagetype, templateLink})
    end
    -- Get the test cases page blurb if the page exists. This is something like
    -- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
    local testcasesTitle = env.testcasesTitle
    if testcasesTitle and testcasesTitle.exists then
        if testcasesTitle.contentModel == "Scribunto" then
            local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
            local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
            local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
            local testcasesRunLink = makeWikilink(testcasesTitle.prefixedText, testcasesRunLinkDisplay)
            mboxargs.text = message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
        else
            local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
            local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
            mboxargs.text = message('sandbox-notice-testcases-blurb', {testcasesLink})
        end
    end
    local ret = {
        mw.getCurrentFrame():expandTemplate{title = message('template-message-box'), args = mboxargs},
        makeCategoryLink(message('sandbox-category')) -- Add the sandbox to the sandbox category.
    }
    return table.concat(ret)
end

----------------------------------------------------------------------------
-- Start box
----------------------------------------------------------------------------

p.startBox = makeInvokeFunc('_startBox')

function p._startBox(args, env)
    --[[
    -- This function generates the start box.
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- 
    -- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
    -- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
    -- which generate the box HTML.
    --]]
    env = env or p.getEnvironment(args)
    local links
    local docTitle = env.docTitle
    if not args.content or docTitle.exists then
        -- No need to include the links if the documentation is on the template page itself.
        local linksData = p.makeStartBoxLinksData(args, env)
        if linksData then
            links = p.renderStartBoxLinks(linksData)
        end
    end
    -- Generate the start box html.
    local data = p.makeStartBoxData(args, env, links)
    if data then
        return p.renderStartBox(data)
    else
        -- User specified no heading.
        return nil
    end
end

function p.makeStartBoxLinksData(args, env)
    --[[
    -- Does initial processing of data to make the [view] [edit] [history] [purge] links.
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- 
    -- Messages:
    -- 'view-link-display' --> 'view'
    -- 'edit-link-display' --> 'edit'
    -- 'history-link-display' --> 'history'
    -- 'purge-link-display' --> 'purge'
    -- 'module-preload' --> 'Template:Documentation/preload-module-doc'
    -- 'docpage-preload' --> 'Template:Documentation/preload'
    -- 'create-link-display' --> 'create'
    --]]
    local subjectSpace = env.subjectSpace
    local title = env.title
    local docTitle = env.docTitle
    if not title or not docTitle then
        return nil
    end
    if docTitle.isRedirect then 
        docTitle = docTitle.redirectTarget
    end

    local data = {}
    data.title = title
    data.docTitle = docTitle
    -- View, display, edit, and purge links if /doc exists.
    data.viewLinkDisplay = message('view-link-display')
    data.editLinkDisplay = message('edit-link-display')
    data.historyLinkDisplay = message('history-link-display')
    data.purgeLinkDisplay = message('purge-link-display')
    -- Create link if /doc doesn't exist.
    local preload = args.preload
    if not preload then
        if subjectSpace == 828 then -- Module namespace
            preload = message('module-preload')
        else
            preload = message('docpage-preload')
        end
    end
    data.preload = preload
    data.createLinkDisplay = message('create-link-display')
    return data
end

function p.renderStartBoxLinks(data)
    --[[
    -- Generates the [view][edit][history][purge] or [create][purge] links from the data table.
    -- @data - a table of data generated by p.makeStartBoxLinksData
    --]]
    
    local function escapeBrackets(s)
        -- Escapes square brackets with HTML entities.
        s = s:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
        s = s:gsub('%]', '&#93;')
        return s
    end

    local ret
    local docTitle = data.docTitle
    local title = data.title
    local purgeLink = makeUrlLink(title:fullUrl{action = 'purge'}, data.purgeLinkDisplay)
    if docTitle.exists then
        local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
        local editLink = makeUrlLink(docTitle:fullUrl{action = 'edit'}, data.editLinkDisplay)
        local historyLink = makeUrlLink(docTitle:fullUrl{action = 'history'}, data.historyLinkDisplay)
        local purgeLink = makeUrlLink(title:fullUrl{action = 'purge'}, data.purgeLinkDisplay)
        ret = '[%s] [%s] [%s] [%s]'
        ret = escapeBrackets(ret)
        ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink)
    else
        local createLink = makeUrlLink(docTitle:fullUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay)
        ret = '[%s] [%s]'
        ret = escapeBrackets(ret)
        ret = mw.ustring.format(ret, createLink, purgeLink)
    end
    return ret
end

function p.makeStartBoxData(args, env, links)
    --[=[
    -- Does initial processing of data to pass to the start-box render function, p.renderStartBox.
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- @links - a string containing the [view][edit][history][purge] links - could be nil if there's an error.
    --
    -- Messages:
    -- 'documentation-icon-wikitext' --> '[[File:Test Template Info-Icon - Version (2).svg|50px|link=|alt=]]'
    -- 'template-namespace-heading' --> 'Template documentation'
    -- 'module-namespace-heading' --> 'Module documentation'
    -- 'widget-namespace-heading' --> 'Widget documentation'
    -- 'file-namespace-heading' --> 'Summary'
    -- 'other-namespaces-heading' --> 'Documentation'
    -- 'start-box-classes' --> 'template-documentation__start'
    -- 'start-box-heading-classes' --> 'template-documentation__heading'
    -- 'start-box-links-id' --> 'doc_editlinks'
    -- 'start-box-links-classes' --> 'mw-editsection-like plainlinks'
    -- 'testcases-create-link-display' --> 'create'
    --]=]
    local subjectSpace = env.subjectSpace
    if not subjectSpace then
        -- Default to an "other namespaces" namespace, so that we get at least some output
        -- if an error occurs.
        subjectSpace = 2
    end
    local data = {}
    
    -- Heading
    local heading = args.heading -- Blank values are not removed.
    if heading == '' then
        -- Don't display the start box if the heading arg is defined but blank.
        return nil
    end
    if heading then
        data.heading = heading
    elseif subjectSpace == 10 then -- Template namespace
        data.heading = message('documentation-icon-wikitext') .. ' ' .. message('template-namespace-heading')
    elseif subjectSpace == 828 then -- Module namespace
        data.heading = message('documentation-icon-wikitext') .. ' ' .. message('module-namespace-heading')
    elseif subjectSpace == 274 then -- Widget namespace
        data.heading = message('documentation-icon-wikitext') .. ' ' .. message('widget-namespace-heading')
    elseif subjectSpace == 6 then -- File namespace
        data.heading = message('file-namespace-heading')
    else
        data.heading = message('other-namespaces-heading')
    end

    -- CSS
    data.startClass = message('start-box-classes');
    
    -- Heading CSS
    data.headingClass = message('start-box-heading-classes');
    local headingStyle = args['heading-style']
    if headingStyle then
        data.headingStyleText = headingStyle
    end
    
    -- Data for the [view][edit][history][purge] or [create] links.
    if links then
        data.linksId = message('start-box-links-id')
        data.linksClass = message('start-box-links-classes')
        data.links = links
    end
    
    return data
end

function p.renderStartBox(data)
    -- Renders the start box html.
    -- @data - a table of data generated by p.makeStartBoxData.
    local sbox = mw.html.create('div')
    sbox
        :addClass(data.startClass)
        :newline()
        :tag('span')
            :addClass(data.headingClass)
            :cssText(data.headingStyleText)
            :wikitext(data.heading)
    local links = data.links
    if links then
        sbox:tag('span')
            :attr('id', data.linksId)
            :addClass(data.linksClass)
            :wikitext(links)
    end
    return tostring(sbox)
end

----------------------------------------------------------------------------
-- Documentation content
----------------------------------------------------------------------------

p.content = makeInvokeFunc('_content')

function p._content(args, env)
    -- Displays the documentation contents
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    env = env or p.getEnvironment(args)
    local docTitle = env.docTitle
    local content = ''
    if docTitle and docTitle.exists then
        content = args._content or mw.getCurrentFrame():expandTemplate{title = docTitle.prefixedText}
    end
    if args.content then
        content = args.content .. '\n' .. content
    end
    -- The line breaks below are necessary so that "=== Headings ===" at the start and end
    -- of docs are interpreted correctly.
    return '\n' .. content .. '\n' 
end

----------------------------------------------------------------------------
-- End box
----------------------------------------------------------------------------

p.endBox = makeInvokeFunc('_endBox')

function p._endBox(args, env)
    --[=[
    -- This function generates the end box (also known as the link box).
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- 
    -- Messages:
    -- 'end-box-id' --> 'documentation-meta-data'
    -- 'end-box-classes' --> 'template-documentation__end'
    --]=]
    
    -- Get environment data.
    env = env or p.getEnvironment(args)
    local subjectSpace = env.subjectSpace
    local docTitle = env.docTitle
    if not subjectSpace or not docTitle then
        return nil
    end
        
    -- Check whether we should output the end box at all. Add the end
    -- box by default if the documentation exists or if we are in the
    -- user, module or template namespaces.
    local linkBox = args['link box']
    if linkBox == 'off'
        or not (
            docTitle.exists
            or subjectSpace == 2
            or subjectSpace == 828
            or subjectSpace == 10
        )
    then
        return nil
    end

    -- Assemble the end box text
    local text = ''
    if linkBox then
        text = text .. linkBox
    else
        text = text .. (p.makeDocPageBlurb(args, env) or '') -- "This documentation is transcluded from [[Foo]]." 
        if subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828 then
            -- We are in the user, template or module namespaces.
            -- Add sandbox and testcases links.
            -- "Editors can experiment in this template's sandbox and testcases pages."
            text = text .. (p.makeExperimentBlurb(args, env) or '')
            text = text .. '<br>'
            if not args[1] and (not args.content or docTitle.exists) then
                -- "Please add categories to the /doc subpage."
                -- Don't show this message with inline docs or with an explicitly specified doc page,
                -- as then it is unclear where to add the categories.
                text = text .. (p.makeCategoriesBlurb(args, env) or '')
            end
            text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') --"Subpages of this template"
        end
    end

    local endId = message('end-box-id')
    local endClass = message('end-box-classes')
    local ebox = mw.html.create('div')
    ebox
        :attr('id', endId)
        :addClass(endClass)
        :newline()
        :wikitext(text)
    return tostring(ebox)
end

function p.makeDocPageBlurb(args, env)
    --[=[
    -- Makes the blurb "This documentation is transcluded from [[Template:Foo]] (edit, history)".
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- 
    -- Messages:
    -- 'edit-link-display' --> 'edit'
    -- 'history-link-display' --> 'history'
    -- 'transcluded-from-blurb' --> 
    -- 'The above [[Wikipedia:Template documentation|documentation]] 
    -- is [[Help:Transclusion|transcluded]] from $1.'
    -- 'module-preload' --> 'Template:Documentation/preload-module-doc'
    -- 'create-link-display' --> 'create'
    -- 'create-module-doc-blurb' -->
    -- 'You might want to $1 a documentation page for this [[Wikipedia:Lua|Scribunto module]].'
    --]=]
    local docTitle = env.docTitle
    if not docTitle then
        return nil
    end
    local ret
    if docTitle.exists then
        -- /doc exists; link to it.
        local docLink = makeWikilink(docTitle.prefixedText)
        local editUrl = docTitle:fullUrl{action = 'edit'}
        local editDisplay = message('edit-link-display')
        local editLink = makeUrlLink(editUrl, editDisplay)
        local historyUrl = docTitle:fullUrl{action = 'history'}
        local historyDisplay = message('history-link-display')
        local historyLink = makeUrlLink(historyUrl, historyDisplay)
        ret = message('transcluded-from-blurb', {docLink})
            .. ' '
            .. makeToolbar(editLink, historyLink)
            .. '<br>'
    elseif env.subjectSpace == 10 then
        -- /doc does not exist for this template; ask to create it.
        local createUrl = docTitle:fullUrl{action = 'edit', preload = message('docpage-preload')}
        local createDisplay = message('create-link-display')
        local createLink = makeUrlLink(createUrl, createDisplay)
        ret = message('create-template-doc-blurb', {createLink})
            .. '<br>'
    elseif env.subjectSpace == 828 then
        -- /doc does not exist for this module; ask to create it.
        local createUrl = docTitle:fullUrl{action = 'edit', preload = message('module-preload')}
        local createDisplay = message('create-link-display')
        local createLink = makeUrlLink(createUrl, createDisplay)
        ret = message('create-module-doc-blurb', {createLink})
            .. '<br>'
    end
    return ret
end

function p.makeExperimentBlurb(args, env)
    --[[
    -- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- 
    -- Messages:
    -- 'sandbox-link-display' --> 'sandbox'
    -- 'sandbox-edit-link-display' --> 'edit'
    -- 'compare-link-display' --> 'diff'
    -- 'module-sandbox-preload' --> 'Template:Documentation/preload-module-sandbox'
    -- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
    -- 'sandbox-create-link-display' --> 'create'
    -- 'mirror-edit-summary' --> 'Create sandbox version of $1'
    -- 'mirror-link-display' --> 'mirror'
    -- 'mirror-link-preload' --> 'Template:Documentation/mirror'
    -- 'sandbox-link-display' --> 'sandbox'
    -- 'testcases-link-display' --> 'testcases'
    -- 'testcases-edit-link-display'--> 'edit'
    -- 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
    -- 'testcases-create-link-display' --> 'create'
    -- 'testcases-link-display' --> 'testcases'
    -- 'testcases-edit-link-display' --> 'edit'
    -- 'module-testcases-preload' --> 'Template:Documentation/preload-module-testcases'
    -- 'template-testcases-preload' --> 'Template:Documentation/preload-testcases'
    -- 'experiment-blurb-module' --> 'Editors can experiment in this module's $1 and $2 pages.'
    -- 'experiment-blurb-template' --> 'Editors can experiment in this template's $1 and $2 pages.'
    --]]
    local subjectSpace = env.subjectSpace
    local templateTitle = env.templateTitle
    local sandboxTitle = env.sandboxTitle
    local testcasesTitle = env.testcasesTitle
    local templatePage = templateTitle.prefixedText
    if not subjectSpace or not templateTitle or not sandboxTitle or not testcasesTitle then
        return nil
    end
    -- Make links.
    local sandboxLinks, testcasesLinks
    if sandboxTitle.exists then
        local sandboxPage = sandboxTitle.prefixedText
        local sandboxDisplay = message('sandbox-link-display')
        local sandboxLink = makeWikilink(sandboxPage, sandboxDisplay)
        local sandboxEditUrl = sandboxTitle:fullUrl{action = 'edit'}
        local sandboxEditDisplay = message('sandbox-edit-link-display')
        local sandboxEditLink = makeUrlLink(sandboxEditUrl, sandboxEditDisplay)
        local compareUrl = env.compareUrl
        local compareLink
        if compareUrl then
            local compareDisplay = message('compare-link-display')
            compareLink = makeUrlLink(compareUrl, compareDisplay)
        end
        sandboxLinks = sandboxLink .. ' ' .. makeToolbar(sandboxEditLink, compareLink)
    else
        local sandboxPreload
        if subjectSpace == 828 then
            sandboxPreload = message('module-sandbox-preload')
        else
            sandboxPreload = message('template-sandbox-preload')
        end
        local sandboxCreateUrl = sandboxTitle:fullUrl{action = 'edit', preload = sandboxPreload}
        local sandboxCreateDisplay = message('sandbox-create-link-display')
        local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay)
        local mirrorSummary = message('mirror-edit-summary', {makeWikilink(templatePage)})
        local mirrorPreload = message('mirror-link-preload')
        local mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = mirrorPreload, summary = mirrorSummary}
        if subjectSpace == 828 then
            mirrorUrl = sandboxTitle:fullUrl{action = 'edit', preload = templateTitle.prefixedText, summary = mirrorSummary}
        end
        local mirrorDisplay = message('mirror-link-display')
        local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay)
        sandboxLinks = message('sandbox-link-display') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink)
    end
    if testcasesTitle.exists then
        local testcasesPage = testcasesTitle.prefixedText
        local testcasesDisplay = message('testcases-link-display')
        local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay)
        local testcasesEditUrl = testcasesTitle:fullUrl{action = 'edit'}
        local testcasesEditDisplay = message('testcases-edit-link-display')
        local testcasesEditLink = makeUrlLink(testcasesEditUrl, testcasesEditDisplay)
        -- for Modules, add testcases run link if exists
        if testcasesTitle.contentModel == "Scribunto" then
            local testcasesRunLinkDisplay = message('testcases-run-link-display')
            local testcasesRunLink = makeWikilink(testcasesTitle.prefixedText, testcasesRunLinkDisplay)
            testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink, testcasesRunLink)
        else
            testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink)
        end
    else
        local testcasesPreload
        if subjectSpace == 828 then
            testcasesPreload = message('module-testcases-preload')
        else
            testcasesPreload = message('template-testcases-preload')
        end
        local testcasesCreateUrl = testcasesTitle:fullUrl{action = 'edit', preload = testcasesPreload}
        local testcasesCreateDisplay = message('testcases-create-link-display')
        local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay)
        testcasesLinks = message('testcases-link-display') .. ' ' .. makeToolbar(testcasesCreateLink)
    end
    local messageName
    if subjectSpace == 828 then
        messageName = 'experiment-blurb-module'
    else
        messageName = 'experiment-blurb-template'
    end
    return message(messageName, {sandboxLinks, testcasesLinks})
end

function p.makeCategoriesBlurb(args, env)
    --[[
    -- Generates the text "Please add categories to the /doc subpage."
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    -- Messages:
    -- 'doc-link-display' --> '/doc'
    -- 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
    --]]
    local docTitle = env.docTitle
    if not docTitle then
        return nil
    end
    local docPathLink = makeWikilink(docTitle.prefixedText, message('doc-link-display'))
    return message('add-categories-blurb', {docPathLink})
end

function p.makeSubpagesBlurb(args, env)
    --[[
    -- Generates the "Subpages of this template" link.
    -- @args - a table of arguments passed by the user
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    
    -- Messages:
    -- 'template-pagetype' --> 'template'
    -- 'module-pagetype' --> 'module'
    -- 'default-pagetype' --> 'page'
    -- 'subpages-link-display' --> 'Subpages of this $1'
    --]]
    local subjectSpace = env.subjectSpace
    local templateTitle = env.templateTitle
    if not subjectSpace or not templateTitle then
        return nil
    end
    local pagetype
    if subjectSpace == 10 then
        pagetype = message('template-pagetype')
    elseif subjectSpace == 828 then
        pagetype = message('module-pagetype')
    else
        pagetype = message('default-pagetype')
    end
    local subpagesLink = makeWikilink(
        'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/',
        message('subpages-link-display', {pagetype})
    )
    return message('subpages-blurb', {subpagesLink})
end

----------------------------------------------------------------------------
-- Tracking categories
----------------------------------------------------------------------------

function p.addTrackingCategories(env)
    --[[
    -- Check if {{documentation}} is transcluded on a /doc or /testcases page.
    -- @env - environment table containing title objects, etc., generated with p.getEnvironment
    
    -- Messages:
    -- 'display-strange-usage-category' --> true
    -- 'doc-subpage' --> 'doc'
    -- 'testcases-subpage' --> 'testcases'
    -- 'strange-usage-category' --> 'Wikipedia pages with strange ((documentation)) usage'
    -- 
    -- /testcases pages in the module namespace are not categorised, as they may have
    -- {{documentation}} transcluded automatically.
    --]]
    local title = env.title
    local subjectSpace = env.subjectSpace
    if not title or not subjectSpace then
        return nil
    end
    local subpage = title.subpageText
    local ret = ''
    if message('display-strange-usage-category', nil, 'boolean')
        and (
            subpage == message('doc-subpage')
            or subjectSpace ~= 828 and subpage == message('testcases-subpage')
        )
    then
        ret = ret .. makeCategoryLink(message('strange-usage-category'))
    end
    return ret
end

return p