Jump to content

Module:Documentation: Difference between revisions

From The Mighty Kingdoms Wiki
Template>Paine Ellsworth
per edit request on talk page - make the doc environment behave correctly, giving the actual template name on the doc page rather than the name of the "doc" subpage
m 1 revision imported
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
-- This module implements {{documentation}}.
local export = {}


-- Get required modules.
local array_module = "Module:array"
local getArgs = require('Module:Arguments').getArgs
local debug_track_module = "Module:debug/track"
local frame_module = "Module:frame"
local fun_is_callable_module = "Module:fun/isCallable"
local languages_module = "Module:languages"
local links_module = "Module:links"
local load_module = "Module:load"
local module_categorization_module = "Module:module categorization"
local number_list_show_module = "Module:number list/show"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local scripts_module = "Module:scripts"
local string_endswith_module = "Module:string/endswith"
local string_gline_module = "Module:string/gline"
local string_insert_module = "Module:string/insert"
local string_startswith_module = "Module:string/startswith"
local string_utilities_module = "Module:string utilities"
local template_parser_module = "Module:template parser"
local title_exists_module = "Module:title/exists"
local title_new_title_module = "Module:title/newTitle"


-- Get the config table.
local concat = table.concat
local cfg = mw.loadData('Module:Documentation/config')
local error = error
local full_url = mw.uri.fullUrl
local get_current_title = mw.title.getCurrentTitle
local insert = table.insert
local ipairs = ipairs
local list_to_text = mw.text.listToText
local new_message = mw.message.new
local pcall = pcall
local require = require
local tonumber = tonumber
local tostring = tostring
local type = type
local unpack = unpack or table.unpack -- Lua 5.2 compatibility


local p = {}
local function Array(...)
Array = require(array_module)
return Array(...)
end
 
local function categorize_module(...)
categorize_module = require(module_categorization_module).categorize
return categorize_module(...)
end
 
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
 
local function endswith(...)
endswith = require(string_endswith_module)
return endswith(...)
end
 
local function expand_template(...)
expand_template = require(frame_module).expandTemplate
return expand_template(...)
end


-- Often-used functions.
local function find_templates(...)
local ugsub = mw.ustring.gsub
find_templates = require(template_parser_module).find_templates
local format = mw.ustring.format
return find_templates(...)
end


----------------------------------------------------------------------------
local function full_link(...)
-- Helper functions
full_link = require(links_module).full_link
--
return full_link(...)
-- These are defined as local functions, but are made available in the p
end
-- table for testing purposes.
----------------------------------------------------------------------------


local function message(cfgKey, valArray, expectType)
local function get_lang(...)
--[[
get_lang = require(languages_module).getByCode
-- Gets a message from the cfg table and formats it if appropriate.
return get_lang(...)
-- The function raises an error if the value from the cfg table is not
end
-- 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
local function get_pagetype(...)
-- message are substituted with values from the table keys [1], [2] etc.
get_pagetype = require(pages_module).get_pagetype
-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
return get_pagetype(...)
-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
end
--]]
 
local msg = cfg[cfgKey]
local function get_script(...)
expectType = expectType or 'string'
get_script = require(scripts_module).getByCode
if type(msg) ~= expectType then
return get_script(...)
error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
end
end
if not valArray then
return msg
end


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


return ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
local function is_callable(...)
is_callable = require(fun_is_callable_module)
return is_callable(...)
end
end


p.message = message
local function is_documentation(...)
is_documentation = require(pages_module).is_documentation
return is_documentation(...)
end


local function makeWikilink(page, display)
local function is_sandbox(...)
if display then
is_sandbox = require(pages_module).is_sandbox
return format('[[%s|%s]]', page, display)
return is_sandbox(...)
else
return format('[[%s]]', page)
end
end
end


p.makeWikilink = makeWikilink
local function new_title(...)
new_title = require(title_new_title_module)
return new_title(...)
end


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


p.makeCategoryLink = makeCategoryLink
local function preprocess(...)
preprocess = require(frame_module).preprocess
return preprocess(...)
end


local function makeUrlLink(url, display)
local function process_params(...)
return format('[%s %s]', url, display)
process_params = require(parameters_module).process
return process_params(...)
end
end


p.makeUrlLink = makeUrlLink
local function safe_load_data(...)
safe_load_data = require(load_module).safe_load_data
return safe_load_data(...)
end


local function makeToolbar(...)
local function split(...)
local ret = {}
split = require(string_utilities_module).split
local lim = select('#', ...)
return split(...)
if lim < 1 then
end
return nil
end
for i = 1, lim do
ret[#ret + 1] = select(i, ...)
end
-- 'documentation-toolbar'
return format(
'<span class="%s">(%s)</span>',
message('toolbar-class'),
table.concat(ret, ' &#124; ')
)
end


p.makeToolbar = makeToolbar
local function startswith(...)
startswith = require(string_startswith_module)
return startswith(...)
end


----------------------------------------------------------------------------
local function string_insert(...)
-- Argument processing
string_insert = require(string_insert_module)
----------------------------------------------------------------------------
return string_insert(...)
end


local function makeInvokeFunc(funcName)
local function title_exists(...)
return function (frame)
title_exists = require(title_exists_module)
local args = getArgs(frame, {
return title_exists(...)
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
end


----------------------------------------------------------------------------
local function ugsub(...)
-- Entry points
ugsub = require(string_utilities_module).gsub
----------------------------------------------------------------------------
return ugsub(...)
end


function p.nonexistent(frame)
local function umatch(...)
if mw.title.getCurrentTitle().subpageText == 'testcases' then
umatch = require(string_utilities_module).match
return frame:expandTemplate{title = 'module test cases notice'}
return umatch(...)
else
return p.main(frame)
end
end
end


p.main = makeInvokeFunc('_main')
local skins = {
["common"    ] = "";
["vector"    ] = "Vector";
["monobook"  ] = "Monobook";
["cologneblue"] = "Cologne Blue";
["modern"    ] = "Modern";
}


function p._main(args)
local function track(page)
--[[
debug_track("documentation/" .. page)
-- This function defines logic flow for the module.
return true
-- @args - table of arguments passed by the user
--]]
local env = p.getEnvironment(args)
local root = mw.html.create()
root
:wikitext(p._getModuleWikitext(args, env))
:wikitext(p.protectionTemplate(env))
:wikitext(p.sandboxNotice(args, env))
:tag('div')
-- 'documentation-container'
:addClass(message('container'))
:attr('role', 'complementary')
:attr('aria-labelledby', args.heading ~= '' and 'documentation-heading' or nil)
:attr('aria-label', args.heading == '' and 'Documentation' or nil)
:newline()
:tag('div')
-- 'documentation'
:addClass(message('main-div-classes'))
:newline()
:wikitext(p._startBox(args, env))
:wikitext(p._content(args, env))
:tag('div')
-- 'documentation-clear'
:addClass(message('clear'))
:done()
:newline()
:done()
:wikitext(p._endBox(args, env))
:done()
:wikitext(p.addTrackingCategories(env))
-- 'Module:Documentation/styles.css'
return mw.getCurrentFrame():extensionTag (
'templatestyles', '', {src=cfg['templatestyles']
}) .. tostring(root)
end
end


----------------------------------------------------------------------------
local function compare_pages(page1, page2, text)
-- Environment settings
return "[" .. tostring(
----------------------------------------------------------------------------
full_url("Special:ComparePages", {page1 = page1, page2 = page2}))
.. " " .. text .. "]"
end
 
-- Avoid transcluding [[Module:languages/cache]] everywhere.
local lang_cache = setmetatable({}, { __index = function (self, k)
return require("Module:languages/cache")[k]
end })
 
local function zh_link(word)
return full_link{
lang = lang_cache.zh,
term = word
}
end


function p.getEnvironment(args)
local function make_languages_data_documentation(title, cats, division)
--[[
local doc_template, module_cat
-- Returns a table with information about the environment, including title
if endswith(division, "/extra") then
-- objects and other namespace- or path-related data.
division = division:sub(1, -7)
-- @args - table of arguments passed by the user
doc_template = "language extradata documentation"
--
module_cat = "Language extra data modules"
-- Title objects include:
else
-- env.title - the page we are making documentation for (usually the current title)
doc_template = "language data documentation"
-- env.templateTitle - the template (or module, file, etc.)
module_cat = "Language data modules"
-- env.docTitle - the /doc subpage.
end
-- env.sandboxTitle - the /sandbox subpage.
local sort_key
-- env.testcasesTitle - the /testcases subpage.
if division == "exceptional" then
--
sort_key = "x"
-- Data includes:
else
-- env.protectionLevels - the protection levels table of the title object.
sort_key = division:gsub("/", "")
-- env.subjectSpace - the number of the title's subject namespace.
end
-- env.docSpace - the number of the namespace the title puts its documentation in.
cats:insert(module_cat .. "|" .. sort_key)
-- env.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
return {
-- env.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.
title = doc_template
--
}
-- All table lookups are passed through pcall so that errors are caught. If an error occurs, the value
end
-- returned will be nil.
--]]
local env, envFuncs = {}, {}


-- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
local function make_Unicode_data_documentation(title, cats)
-- returned by that function is memoized in the env table so that we don't call any of the functions
local subpage, first_three_of_code_point
-- more than once. (Nils won't be memoized.)
= title.fullText:match("^Module:Unicode data/([^/]+)/(%x%x%x)$")
setmetatable(env, {
if subpage == "names" or subpage == "images" or subpage == "emoji images" then
__index = function (t, key)
local low, high =
local envFunc = envFuncs[key]
tonumber(first_three_of_code_point .. "000", 16),
if envFunc then
tonumber(first_three_of_code_point .. "FFF", 16)
local success, val = pcall(envFunc)
local text, text_type
if success then
if subpage == "names" then
env[key] = val -- Memoise the value.
text_type = "titles of images"
return val
elseif subpage == "images" then
end
text_type = "titles of images"
end
elseif subpage == "emoji images" then
return nil
text_type = "emoji-style images"
end
end
})
text = string.format(
 
"This data module contains the " .. text_type .. " of " ..
function envFuncs.title()
"[[Appendix:Unicode|Unicode]] code points within the range U+%04X to U+%04X.",
-- The title object for the current page, or a test page passed with args.page.
low, high)
local title
if subpage == "images" and safe_load_data("Module:Unicode data/emoji images/" .. first_three_of_code_point) then
local titleArg = args.page
text = text .. " This list includes the text variants of emojis. For the list of emoji variants of those characters, see [[Module:Unicode data/emoji images/" .. first_three_of_code_point .. "]]."
if titleArg then
elseif subpage == "emoji images" then
title = mw.title.new(titleArg)
text = text .. " For text-style images, see [[Module:Unicode data/images/" .. first_three_of_code_point .. "]]."
else
title = mw.title.getCurrentTitle()
end
end
return title
return text
end
end
end


function envFuncs.templateTitle()
local function insert_lang_data_module_cats(cats, langcode, overall_data_module_cat)
--[[
local lang = lang_cache[langcode]
-- The template (or module, etc.) title object.
if lang then
-- Messages:
local langname
-- 'sandbox-subpage' --> 'sandbox'
if lang._fullCode then
-- 'testcases-subpage' --> 'testcases'
langname = lang_cache[lang._fullCode]:getCanonicalName()
--]]
local subjectSpace = env.subjectSpace
local title = env.title
local subpage = title.subpageText
if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') or (subpage == message('doc-subpage') and mw.title.getCurrentTitle().namespace == env.docSpace) then
return mw.title.makeTitle(subjectSpace, title.baseText)
else
else
return mw.title.makeTitle(subjectSpace, title.text)
langname = lang:getCanonicalName()
end
end
cats:insert(overall_data_module_cat .. "|" .. langname)
cats:insert(langname .. " modules")
cats:insert(langname .. " data modules")
return lang, langname
end
end
end
--[=[
This provides categories and documentation for various data modules, so that [[Category:Uncategorized modules]] isn't
unnecessarily cluttered. It is a list of tables, each of which have the following possible fields:
`regex` (required): A Lua pattern to match the module's title. If it matches, the data in this entry will be used.
Any captures in the pattern can by referenced in the `cat` field using %1 for the first capture, %2 for the
second, etc. (often used for creating the sortkey for the category). In addition, the captures are passed to the
`process` function as the third and subsequent parameters.


function envFuncs.docTitle()
`process` (optional): This may be a function or a string. If it is a function, it is called as follows:
--[[
  `process(TITLE, CATS, CAPTURE1, CAPTURE2, ...)`
-- Title object of the /doc subpage.
where:
-- Messages:
  * TITLE is a title object describing the module's title; see
-- 'doc-subpage' --> 'doc'
    [https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Title_objects].
--]]
  * CATS is an array object (see [[Module:array]]) of categories that the module will be added to.
local title = env.title
  * CAPTURE1, CAPTURE2, ... contain any captures in the `regex` field.
local docname = args[1] -- User-specified doc page.
The return value of `process` should either be a string (which will be used as the module's documentation), or a
local docpage
table specifying the name of a template to expand to get the documentation, along with the arguments to that
if docname then
template. In the latter format, the template name (bare, without the "Template:" prefix) should be in the `title`
docpage = docname
field, and any arguments should be in `args; in this case, the template name will be listed above the generated
else
documentation as the source of the documentation, along with an edit button to edit the template's contents.
docpage = env.docpageBase .. '/' .. message('doc-subpage')
If, however, the return value of the `process` function is a string, any template invocations will be expanded
using frame:preprocess(), and [[Module:documentation]] will be listed as the source of the documentation.
 
If `process` itself is a string rather than a function, it should name a submodule under
[[Module:documentation/functions/]] which returns a function, of the same type as described above. This submodule
will be specified as the source of the documentation (unless it returns a table naming a template to expand to get
the documentation, as described above).
 
If `process` is omitted entirely, the module will have no documentation.
 
`cat` (optional): A string naming the category into which the module should be placed, or a list of such strings.
Captures specified in `regex` may be referenced in this string using %1 for the first capture, %2 for the second,
etc. It is also possible to add categories in the `process` function by inserting them into the passed-in CATS
array (the second parameter).
]=]
 
local module_regex = {
{
regex = "^Module:languages/data/(3/%l/extra)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(3/%l)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(2/extra)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(2)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(exceptional/extra)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(exceptional)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/.+$",
cat = "Language and script modules",
},
{
regex = "^Module:scripts/.+$",
cat = "Language and script modules",
},
{
regex = "^Module:data tables/data..?.?.?$",
cat = "Reference module sharded data tables",
},
{
regex = "^Module:zh/data/dial%-pron/.+$",
cat = "Chinese dialectal pronunciation data modules",
process = "zh dial or syn",
},
{
regex = "^Module:zh/data/dial%-syn/.+$",
cat = "Chinese dialect synonyms data modules",
process = "zh dial or syn",
},
{
regex = "^Module:zh/data/glyph%-data/.+$",
cat = "Chinese historical character forms data modules",
process = function(title, cats)
local character = title.fullText:match("^Module:zh/data/glyph%-data/(.+)")
if character then
return ("This module contains data on historical forms of the Chinese character %s.")
:format(zh_link(character))
end
end,
},
{
regex = "^Module:zh/data/ltc%-pron/(.+)$",
cat = "Middle Chinese pronunciation data modules|%1",
process = "zh data",
},
{
regex = "^Module:zh/data/och%-pron%-BS/(.+)$",
cat = "Old Chinese (Baxter-Sagart) pronunciation data modules|%1",
process = "zh data",
},
{
regex = "^Module:zh/data/och%-pron%-ZS/(.+)$",
cat = "Old Chinese (Zhengzhang) pronunciation data modules|%1",
process = "zh data",
},
{
-- capture rest of zh/data submodules
regex = "^Module:zh/data/(.+)$",
cat = "Chinese data modules|%1",
},
{
regex = "^Module:mul/guoxue%-data/cjk%-?(.*)$",
process = "guoxue-data",
},
{
regex = "^Module:Unicode data/(.+)$",
cat = "Unicode data modules|%1",
process = make_Unicode_data_documentation,
},
{
regex = "^Module:number list/data/(.+)$",
process = function(title, cats, lang_code)
local lang = insert_lang_data_module_cats(cats, lang_code, "Number data modules")
if lang then
return ("This module contains data on various types of numbers in %s.\n%s")
:format(lang:makeCategoryLink(), number_list_show_table() or "")
end
end,
},
{
regex = "^Module:accel/(.+)$",
process = function(title, cats)
local lang_code = title.subpageText
local lang = lang_cache[lang_code]
if lang then
cats:insert(lang:getCanonicalName() .. " modules|accel")
cats:insert(("Accel submodules|%s"):format(lang:getCanonicalName()))
return ("This module contains new entry creation rules for %s; see [[WT:ACCEL]] for an overview, and [[Module:accel]] for information on creating new rules.")
:format(lang:makeCategoryLink())
end
end,
},
{
regex = "^Module:inc%-ash/dial/data/(.+)$",
cat = "Ashokan Prakrit modules|%1",
process = function(title, cats)
local word = title.fullText:match("^Module:inc%-ash/dial/data/(.+)$")
if word then
local lang = lang_cache["inc-ash"]
return ("This module contains data on the pronunciation of %s in dialects of %s.")
:format(full_link({ term = word, lang = lang }, "term"),
lang:makeCategoryLink())
end
end,
},
{
regex = "^.+%-translit$",
process = "translit",
},
{
regex = "^Module:form of/lang%-data/(.+)$",
process = function(title, cats, lang_code)
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Language-specific form-of modules")
if lang then
-- FIXME, display more info.
return "This module contains language-specific form-of data (tags, shortcuts, base lemma params. etc.) for " ..
langname .. "."
end
end
},
{
regex = "^Module:labels/data/lang/(.+)$",
process = function(title, cats, lang_code)
local lang = insert_lang_data_module_cats(cats, lang_code, "Language-specific label data modules")
if lang then
return {
title = "label language-specific data documentation",
args = { [1] = lang_code },
}
end
end
},
{
regex = "^Module:category tree/lang/(.+)$",
process = function(title, cats, lang_code)
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Category tree data modules/lang")
if lang then
return "This module handles generating the descriptions and categorization for " .. langname .. " category pages "
.. "of the format \"" .. langname .. " LABEL\" where LABEL can be any text. Examples are "
.. "[[:Category:Bulgarian conjugation 2.1 verbs]] and [[:Category:Russian velar-stem neuter-form nouns]]. "
.. "This module is part of the category tree system, which is a general framework for generating the "
.. "descriptions and categorization of category pages.\n\n"
.. "For more information, see [[Module:category tree/lang/documentation]].\n\n"
.. "'''NOTE:''' If you add a new language-specific module, you must add the language code to the "
.. "list at the top of [[Module:category tree/lang]] in order for the module to be recognized."
end
end
},
{
regex = "^Module:category tree/topic/(.+)$",
process = function(title, cats, submodule)
cats:insert("Category tree data modules/topic| ")
return {
title = "topic cat data submodule documentation"
}
end
},
{
regex = "^Module:category tree/(.+)$",
process = function(title, cats, submodule)
cats:insert("Category tree data modules| ")
return {
title = "category tree data submodule documentation"
}
end
},
{
regex = "^Module:ja/data/(.+)$",
cat = "Japanese data modules|%1",
},
{
regex = "^Module:fi%-dialects/data/feature/Kettunen1940 ([0-9]+)$",
cat = "Finnish dialectal data atlas modules|%1",
process = function(title, cats, shard)
return "This module contains shard " .. shard .. " of the online version of Lauri Kettunen's 1940 work " ..
"''Suomen murteet III A. Murrekartasto'' (\"Finnish dialects III A: Dialect atlas\"). " ..
"It was imported and converted from urn:nbn:fi:csc-kata20151130145346403821, published by the " ..
"''Kotimaisten kielten keskus'' under the CC BY 4.0 license."
end
},
{
regex = "^Module:fi%-dialects/data/feature/(.+)",
cat = "Finnish dialectal data modules|%1",
},
{
regex = "^Module:fi%-dialects/data/word/(.+)",
cat = "Finnish dialectal data modules|%1",
},
{
regex = "^Module:Swadesh/data/([%l-]+)$",
process = function(title, cats, lang_code)
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
if lang then
return "This module contains the [[Swadesh list]] of basic vocabulary in " .. langname .. "."
end
end
},
{
regex = "^Module:Swadesh/data/([%l-]+)/([^/]*)$",
process = function(title, cats, lang_code, variety)
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
if lang then
local prefix = "This module contains the [[Swadesh list]] of basic vocabulary in the "
local etym_lang = get_lang(variety, nil, "allow etym")
if etym_lang then
return ("%s %s variety of %s."):format(prefix, etym_lang:getCanonicalName(), langname)
end
local script = get_script(variety)
if script then
return ("%s %s %s script."):format(prefix, langname, script:getCanonicalName())
end
return ("%s %s variety of %s."):format(prefix, variety, langname)
end
end
end
return mw.title.new(docpage)
},
{
regex = "^Module:typing%-aids",
process = function(title, cats)
local data_suffix = title.fullText:match("^Module:typing%-aids/data/(.+)$")
local sortkey
if data_suffix then
if data_suffix:find "^[%l-]+$" then
local lang = get_lang(data_suffix)
if lang then
sortkey = lang:getCanonicalName()
cats:insert(sortkey .. " data modules")
end
elseif data_suffix:find "^%u%l%l%l$" then
local script = get_script(data_suffix)
if script then
sortkey = script:getCanonicalName()
cats:insert(script:getCategoryName())
end
end
cats:insert("Character insertion data modules|" .. (sortkey or data_suffix))
end
end,
},
{
regex = "^Module:R:([%l-]+):(.+)$",
process = function(title, cats, lang_code, refname)
local lang = lang_cache[lang_code]
if lang then
cats:insert(lang:getCanonicalName() .. " modules|" .. refname)
cats:insert(("Reference modules|%s"):format(lang:getCanonicalName()))
return "This module implements the reference template {{temp|R:" .. lang_code .. ":" .. refname .. "}}."
end
end,
},
{
regex = "^Module:Quotations/([%l-]+)/?(.*)",
process = "Quotation",
},
{
regex = "^Module:affix/lang%-data/([%l-]+)",
process = "affix lang-data",
},
{
regex = "^Module:dialect synonyms/([%l-]+)$",
process = function(title, cats, lang_code)
local lang = lang_cache[lang_code]
if lang then
local langname = lang:getCanonicalName()
cats:insert("Dialect synonyms data modules|" .. langname)
cats:insert(langname .. " dialect synonyms data modules| ")
return "This module contains data on specific varieties of " .. langname .. ", for use by " ..
"{{tl|dialect synonyms}}. The actual synonyms themselves are contained in submodules.\n\n" ..
expand_template({ title = 'dial syn', args = { lang_code, ["demo mode"] = "y" } })
end
end,
},
{
regex = "^Module:dialect synonyms/([%l-]+)/(.+)$",
process = function(title, cats, lang_code, term)
local lang = lang_cache[lang_code]
if lang then
local langname = lang:getCanonicalName()
cats:insert("Dialect synonyms data modules|" .. langname)
cats:insert(langname .. " dialect synonyms data modules|" .. term)
return ("This module contains dialectal %s synonyms for {{m|%s|%s}}.\n\n%s"):format(langname, lang_code, term, expand_template({ title = 'dial syn', args = { lang_code, term } }))
end
end,
},
{
regex = "^Module:bibliography/data/([%l-]+)$",
process = function(title, cats, lang_code)
if lang_code == "preload" then
return 'Used as a base model for other languages when the button "create new language submodule" is clicked.'
end
local page = require(title.fullText).bib_page
if not page then
page = lang_cache[lang_code]:getCanonicalName()
if page then
cats:insert(page.." modules")
end
end
cats:insert("Reference modules")
return "This module holds bibliographical data for "..page..". For the formatted bibliography see '''[[Appendix:Bibliography/"..page.."]]'''."
end,
},
}
 
function export.show(frame)
local boolean_default_false = {type = "boolean", default = false}
local args = process_params(frame.args, {
["hr"] = true,
["for"] = true,
["from"] = true,
["allowondoc"] = boolean_default_false, -- Don't throw an error if used on a documentation subpage.
["notsubpage"] = boolean_default_false,
["nodoc"] = boolean_default_false,
["nolinks"] = boolean_default_false, -- suppress all "Useful links"
["nosandbox"] = boolean_default_false, -- supress sandbox
})
local output = Array('\n<div class="documentation" style="display:block; clear:both">\n')
local cats = Array()
local nodoc = args.nodoc
if (not args.hr) or (args.hr == "above") then
output:insert("----\n")
end
end
function envFuncs.sandboxTitle()
local title = args["for"] and new_title(args["for"]) or get_current_title()
--[[
local doc_title = args.from ~= "-" and new_title(args.from or title.fullText .. '/documentation') or nil
-- Title object for the /sandbox subpage.
local contentModel = title.contentModel
-- Messages:
local pagetype, is_script_or_stylesheet = get_pagetype(title)
-- 'sandbox-subpage' --> 'sandbox'
local preload, fallback_docs, doc_content, old_doc_title, user_name, skin_name, needs_doc
--]]
local doc_content_source = "Module:documentation"
return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
local auto_generated_cat_source
end
local cats_auto_generated = false
function envFuncs.testcasesTitle()
if not args.allowondoc and is_documentation(title) then
--[[
-- TODO: merge with {{documentation subpage}}, and choose behaviour based on the page type.
-- Title object for the /testcases subpage.
error("This template should not be used on a documentation page. Please use [[Template:documentation subpage]].")
-- Messages:
elseif is_sandbox(title) then
-- 'testcases-subpage' --> 'testcases'
local sandbox_ns = title.nsText
--]]
preload = ("Template:documentation/preload%s%sSandbox"):format(
return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
sandbox_ns == "Module" and sandbox_ns or "Template",
title.rootText:match("^[Uu]ser:(.+)") and "User" or ""
)
elseif pagetype:match("%f[%w]gadget%f[%W]") then
preload = "Template:documentation/preloadGadget"
elseif pagetype:match("%f[%w]script%f[%W]") then -- .js
if title.nsText == "MediaWiki" then
preload = "Template:documentation/preloadMediaWikiJavaScript"
else
preload  = "Template:documentation/preloadTemplate" -- XXX
if title.nsText == "User" then
user_name = title.rootText
end
end
is_script_or_stylesheet = true
elseif pagetype:match("%f[%w]stylesheet%f[%W]") then -- .css
preload  = "Template:documentation/preloadTemplate" -- XXX
if title.nsText == "User" then
user_name = title.rootText
end
is_script_or_stylesheet = true
elseif contentModel == "Scribunto" then -- Exclude pages in Module: which aren't Scribunto.
preload  = "Template:documentation/preloadModule"
elseif pagetype:match("%f[%w]template%f[%W]") or pagetype:match("%f[%w]project%f[%W]") then
preload  = "Template:documentation/preloadTemplate"
end
end


function envFuncs.protectionLevels()
if doc_title and doc_title.isRedirect then
-- The protection levels table of the title object.
old_doc_title = doc_title
return env.title.protectionLevels
doc_title = doc_title.redirectTarget
end
end


function envFuncs.subjectSpace()
output:insert("<dl class=\"plainlinks\" style=\"font-size: smaller;\">")
-- The subject namespace number.
return mw.site.namespaces[env.title.namespace].subject.id
end


function envFuncs.docSpace()
local function get_module_doc_and_cats(categories_only)
-- The documentation namespace number. For most namespaces this is the
cats_auto_generated = true
-- same as the subject namespace. However, pages in the Article, File,
local automatic_cats = nil
-- MediaWiki or Category namespaces must have their /doc, /sandbox and
if user_name then
-- /testcases pages in talk space.
fallback_docs = "documentation/fallback/user module"
local subjectSpace = env.subjectSpace
automatic_cats = {"User sandbox modules"}
if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then
return subjectSpace + 1
else
else
return subjectSpace
for _, data in ipairs(module_regex) do
local captures = {umatch(title.fullText, data.regex)}
if #captures > 0 then
local cat, process_function
if is_callable(data.process) then
process_function = data.process
elseif type(data.process) == "string" then
doc_content_source = "Module:documentation/functions/" .. data.process
process_function = require(doc_content_source)
end
 
if process_function then
doc_content = process_function(title, cats, unpack(captures))
end
if type(doc_content) == "table" then
doc_content_source = doc_content.title and "Template:" .. doc_content.title or doc_content_source
doc_content = expand_template(doc_content)
elseif doc_content ~= nil then
doc_content = preprocess(doc_content)
end
cat = data.cat
if cat then
if type(cat) == "string" then
cat = {cat}
end
for _, c in ipairs(cat) do
insert(cats, (ugsub(title.fullText, data.regex, c)))
end
end
break
end
end
end
end
end


function envFuncs.docpageBase()
if title.subpageText == "templates" then
-- The base page of the /doc, /sandbox, and /testcases subpages.
cats:insert("Template interface modules")
-- 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.canonicalUrl(
'Special:ComparePages',
{ page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
)
return tostring(compareUrl)
else
return nil
end
end
end


return env
if automatic_cats then
end
for _, c in ipairs(automatic_cats) do
cats:insert(c)
end
end
if #cats == 0 then
local auto_cats = categorize_module(frame, "return raw", "noerror")
if #auto_cats > 0 then
auto_generated_cat_source = "Module:module categorization"
end
for _, category in ipairs(auto_cats) do
cats:insert(category)
end
end


----------------------------------------------------------------------------
-- meaning module is not in user’s sandbox or one of many datamodule boring series
-- Auxiliary templates
needs_doc = not categories_only and not (automatic_cats or doc_content or fallback_docs)
----------------------------------------------------------------------------
end


p.getModuleWikitext = makeInvokeFunc('_getModuleWikitext')
-- Override automatic documentation, if present.
if doc_title and doc_title.exists then
local cats_auto_generated_text = ""
if contentModel == "Scribunto" then
local doc_page_content = doc_title.content
-- Track then do nothing if there are uses of includeonly. The
-- pattern is slightly too permissive, but any false-positives are
-- obvious typos that should be corrected.
if doc_page_content:lower():match("</?includeonly%f[%s/>][^>]*>") then
track("module-includeonly")
else
-- Check for uses of {{module cat}}. find_templates treats the
-- input as transcluded by default (i.e. it parses the wikitext
-- which will be transcluded through to the module page).
local module_cat
for template in find_templates(doc_page_content) do
if template:get_name() == "module cat" then
module_cat = true
break
end
end
if not module_cat then
get_module_doc_and_cats("categories only")
auto_generated_cat_source = auto_generated_cat_source or doc_content_source
cats_auto_generated_text = " Categories were auto-generated by [[" .. auto_generated_cat_source .. "]]. <sup>[[" ..
new_title(auto_generated_cat_source):fullUrl{action = "edit"} ..  " edit]]</sup>"
end
end
end


function p._getModuleWikitext(args, env)
output:insert(
local currentTitle = mw.title.getCurrentTitle()
"<dd><i style=\"font-size: larger;\">The following " ..
if currentTitle.contentModel ~= 'Scribunto' then return end
"[[Help:Documenting templates and modules|documentation]] is located at [[" ..
pcall(require, currentTitle.prefixedText) -- if it fails, we don't care
doc_title.fullText .. "]]. " .. "<sup>[[" .. doc_title:fullUrl{action = "edit"} .. " edit]]</sup>" ..
local moduleWikitext =  package.loaded["Module:Module wikitext"]
cats_auto_generated_text .. "</i></dd>")
if moduleWikitext then
return moduleWikitext.main()
end
end
 
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'
-- 'module-sandbox-category' --> 'Module sandboxes'
-- 'other-sandbox-category' --> 'Sandboxes outside of template or module namespace'
--]=]
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 omargs = {}
omargs.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 text = '__EXPECTUNUSEDTEMPLATE__'
local pagetype, sandboxCat
if subjectSpace == 10 then
pagetype = message('sandbox-notice-pagetype-template')
sandboxCat = message('sandbox-category')
elseif subjectSpace == 828 then
pagetype = message('sandbox-notice-pagetype-module')
sandboxCat = message('module-sandbox-category')
else
else
pagetype = message('sandbox-notice-pagetype-other')
if contentModel == "Scribunto" then
sandboxCat = message('other-sandbox-category')
get_module_doc_and_cats(false)
end
elseif title.nsText == "Template" then
local templateLink = makeWikilink(templateTitle.prefixedText)
--cats:insert("Uncategorized templates")
local compareUrl = env.compareUrl
needs_doc = not (fallback_docs or nodoc)
if compareUrl then
elseif user_name and is_script_or_stylesheet then
local compareDisplay = message('sandbox-notice-compare-link-display')
skin_name = skins[title.text:sub(#title.rootText + 1):match("^/(%l+)%.[jc]ss?$")]
local compareLink = makeUrlLink(compareUrl, compareDisplay)
if skin_name then
text = text .. message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
fallback_docs = "documentation/fallback/user " .. contentModel
else
end
text = text .. message('sandbox-notice-blurb', {pagetype, templateLink})
end
end
-- Get the test cases page blurb if the page exists. This is something like
if doc_content then
-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
output:insert(
local testcasesTitle = env.testcasesTitle
"<dd><i style=\"font-size: larger;\">The following " ..
if testcasesTitle and testcasesTitle.exists then
"[[Help:Documenting templates and modules|documentation]] is " ..
if testcasesTitle.contentModel == "Scribunto" then
"generated by [[" .. doc_content_source .. "]]. <sup>[[" ..
local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
new_title(doc_content_source):fullUrl{action = "edit"} ..
local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
" edit]]</sup> </i></dd>")
local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
elseif not nodoc then
local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
if doc_title then
text = text .. '<br />' .. message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
output:insert(
else
"<dd><i style=\"font-size: larger;\">This " .. pagetype ..
local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
" lacks a [[Help:Documenting templates and modules|documentation subpage]]. " ..
local testcasesLink = makeWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
(fallback_docs and "You may " or "Please ") ..
text = text .. '<br />' .. message('sandbox-notice-testcases-blurb', {testcasesLink})
"[" .. doc_title:fullUrl{action = "edit", preload = preload}
.. " create it].</i></dd>\n")
else
output:insert(
"<dd><i style=\"font-size: larger; color: #FF0000;\">Unable to auto-generate " ..
"documentation for this " .. pagetype ..".</i></dd>\n")
end
end
end
end
end
-- Add the sandbox to the sandbox category.
if startswith(title.fullText, "MediaWiki:Gadget-") then
omargs.text = text .. makeCategoryLink(sandboxCat)
local is_gadget = false
for line in gline(new_title("MediaWiki:Gadgets-definition").content) do
local gadget, items = line:match("^%*%s*(%a[%w_-]*)%[.-%]|(.+)$")
if not gadget then
gadget, items = line:match("^%*%s*(%a[%w_-]*)|(.+)$")
end
if gadget then
items = Array(split(items, "|"))
for i, item in ipairs(items) do
if title.fullText == ("MediaWiki:Gadget-" .. item) then
is_gadget = true


-- 'documentation-clear'
output:insert("<dd> ''This script is a part of the <code>")
return '<div class="' .. message('clear') .. '"></div>'
output:insert(gadget)
.. require('Module:Message box').main('ombox', omargs)
output:insert("</code> gadget ([")
end
output:insert(tostring(full_url("MediaWiki:Gadgets-definition", {action = "edit"})))
 
output:insert(" edit definitions])'' <dl>")
function p.protectionTemplate(env)
-- Generates the padlock icon in the top right.
output:insert("<dd> ''Description ([")
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
output:insert(tostring(full_url("MediaWiki:Gadget-" .. gadget, {action = "edit"})))
-- Messages:
output:insert(" edit])'': ")
-- 'protection-template' --> 'pp-template'
-- 'protection-template-args' --> {docusage = 'yes'}
output:insert(preprocess(new_message('Gadget-' .. gadget):plain()))
local protectionLevels = env.protectionLevels
output:insert(" </dd>")
if not protectionLevels then
return nil
end
local editProt = protectionLevels.edit and protectionLevels.edit[1]
local moveProt = protectionLevels.move and protectionLevels.move[1]
if editProt then
-- The page is edit-protected.
return require('Module:Protection banner')._main{
message('protection-reason-edit'), small = true
}
elseif moveProt and moveProt ~= 'autoconfirmed' then
-- The page is move-protected but not edit-protected. Exclude move
-- protection with the level "autoconfirmed", as this is equivalent to
-- no move protection at all.
return require('Module:Protection banner')._main{
action = 'move', small = true
}
else
return nil
end
end


----------------------------------------------------------------------------
items:remove(i)
-- Start box
if #items > 0 then
----------------------------------------------------------------------------
for j, item in ipairs(items) do
items[j] = '[[MediaWiki:Gadget-' .. item .. '|' .. item .. ']]'
end
output:insert("<dd> ''Other parts'': ")
output:insert(list_to_text(items))
output:insert("</dd>")
end


p.startBox = makeInvokeFunc('_startBox')
output:insert("</dl></dd>")


function p._startBox(args, env)
break
--[[
end
-- 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
end
--
-- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
if not is_gadget then
-- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
output:insert("<dd> ''This script is not a part of any [")
-- which generate the box HTML.
output:insert(tostring(full_url("Special:Gadgets", {uselang = "en"})))
--]]
output:insert(' gadget] ([')
env = env or p.getEnvironment(args)
output:insert(tostring(full_url("MediaWiki:Gadgets-definition", {action = "edit"})))
local links
output:insert(' edit definitions]).</dd>')
local content = args.content
-- else
if not content or args[1] then
-- cats:insert("Wiktionary gadgets")
-- 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
end
end
-- Generate the start box html.
local data = p.makeStartBoxData(args, env, links)
if old_doc_title then
if data then
output:insert("<dd> ''Redirected from'' [")
return p.renderStartBox(data)
output:insert(old_doc_title:fullUrl{redirect = "no"})
else
output:insert(" ")
-- User specified no heading.
output:insert(old_doc_title.fullText)
return nil
output:insert("] ([")
output:insert(old_doc_title:fullUrl{action = "edit"})
output:insert(" edit]).</dd>\n")
end
end
end
if not args.nolinks then
local links = Array()


function p.makeStartBoxLinksData(args, env)
if title.isSubpage and not args.notsubpage then
--[[
links:insert("[[:" .. title.nsText .. ":" .. title.rootText .. "|root page]]")
-- Does initial processing of data to make the [view] [edit] [history] [purge] links.
links:insert("[[Special:PrefixIndex/" .. title.nsText .. ":" .. title.rootText .. "/|root page’s subpages]]")
-- @args - a table of arguments passed by the user
else
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
links:insert("[[Special:PrefixIndex/" .. title.fullText .. "/|subpage list]]")
--
end
-- Messages:
-- 'view-link-display' --> 'view'
links:insert(
-- 'edit-link-display' --> 'edit'
"[" .. tostring(full_url("Special:WhatLinksHere/" .. title.fullText, {hidetrans = true, hideredirs = true})) .. " links]")
-- 'history-link-display' --> 'history'
-- 'purge-link-display' --> 'purge'
if contentModel ~= "Scribunto" then
-- 'module-preload' --> 'Template:Documentation/preload-module-doc'
links:insert(
-- 'docpage-preload' --> 'Template:Documentation/preload'
"[" .. tostring(full_url("Special:WhatLinksHere/" .. title.fullText, {hidelinks = true, hidetrans = true})) .. " redirects]")
-- 'create-link-display' --> 'create'
end
--]]
local subjectSpace = env.subjectSpace
if is_script_or_stylesheet then
local title = env.title
if user_name then
local docTitle = env.docTitle
links:insert("[[Special:MyPage" .. title.text:sub(#title.rootText + 1) .. "|your own]]")
if not title or not docTitle then
end
return nil
end
if docTitle.isRedirect then
docTitle = docTitle.redirectTarget
end
 
-- 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
else
preload = message('docpage-preload')
links:insert(
"[" .. tostring(full_url("Special:WhatLinksHere/" .. title.fullText, {hidelinks = true, hideredirs = true})) .. " transclusions]")
end
if contentModel == "Scribunto" then
local is_testcases = title.isSubpage and title.subpageText == "testcases"
local without_subpage = title.nsText .. ":" .. title.baseText
if is_testcases then
links:insert("[[:" .. without_subpage .. "|tested module]]")
else
links:insert("[[" .. title.fullText .. "/testcases|testcases]]")
end
if user_name then
links:insert("[[User:" .. user_name .. "|user page]]")
links:insert("[[User talk:" .. user_name .. "|user talk page]]")
links:insert("[[Special:PrefixIndex/User:" .. user_name .. "/|userspace]]")
-- If sandbox module, add a link to the module that this is a sandbox of.
-- Exclude user sandbox modules like [[User:Dine2016/sandbox]].
elseif title.text:find("/sandbox%d*%f[/%z]") then
cats:insert("Sandbox modules")
-- Sandbox modules don’t really need documentation.
needs_doc = false
-- Don't track user sandbox modules.
local text_title = new_title(title.text)
if not (text_title and text_title.namespace == 2) then
track("sandbox to be moved")
local sandbox_of, diff = title.baseText
if title_exists(sandbox_of) then
diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
else
track("no sandbox of")
end
links:insert("[[:" .. sandbox_of .. "|sandbox of]]" .. (diff or ""))
end
-- If not a sandbox module, add link to sandbox module.
-- Sometimes there are multiple sandboxes for a single module:
-- [[Module:sa-pronunc/sandbox]],  [[Module:sa-pronunc/sandbox2]].
-- Occasionally sandbox modules have their own subpages that are also
-- sandboxes: [[Module:grc-decl/sandbox/decl]].
else
local sandbox_title
if title.rootText == "grc-decl" then
sandbox_title = string_insert(title.fullText, 16, "/sandbox")
elseif is_testcases then
sandbox_title = title.fullText:gsub("/testcases", "/sandbox/testcases")
else
sandbox_title = title.fullText .. "/sandbox"
end
local sandbox_link = "[[:" .. sandbox_title .. "|sandbox]]"
local diff
if title_exists(sandbox_title) then
diff = " (" .. compare_pages(title.fullText, sandbox_title, "diff") .. ")"
end
links:insert(sandbox_link .. (diff or ""))
end
end
if title.nsText == "Template" then
-- Error search: all(any namespace), hastemplate (show pages using the template), insource (show source code), incategory (any/specific error) -- [[mw:Help:CirrusSearch]], [[w:Help:Searching/Regex]]
-- apparently same with/without: &profile=advanced&fulltext=1
local errorq = 'searchengineselect=mediawiki&search=all: hastemplate:\"'..title.rootText..'\" insource:\"'..title.rootText..'\" incategory:'
local eincategory = "Pages_with_module_errors|ParserFunction_errors|DisplayTitle_errors|Pages_with_ISBN_errors|Pages_with_ISSN_errors|Pages_with_reference_errors|Pages_with_syntax_highlighting_errors|Pages_with_TemplateStyles_errors"
links:insert(
'[' .. tostring(full_url('Special:Search', errorq..eincategory )) .. ' errors]'
.. ' (' ..
'[' .. tostring(full_url('Special:Search', errorq..'ParserFunction_errors' )) .. ' parser]'
.. '/' ..
'[' .. tostring(full_url('Special:Search', errorq..'Pages_with_module_errors' )) .. ' module]'
.. ')'
)
if title.isSubpage and title.text:find("/sandbox%d*%f[/%z]") then -- This is a sandbox template.
-- At the moment there are no user sandbox templates with subpage
-- “/sandbox”.
cats:insert("Sandbox templates")
-- Sandbox templates don’t really need documentation.
needs_doc = false
-- Will behave badly if “/sandbox” occurs twice in title!
local sandbox_of = title.fullText:gsub("/sandbox%d*%f[/%z]", "")
local diff
if title_exists(sandbox_of) then
diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
else
track("no sandbox of")
end
links:insert("[[:" .. sandbox_of .. "|sandbox of]]" .. (diff or ""))
-- This is a template that can have a sandbox.
elseif not args.nosandbox then -- unless we tell it not to
local sandbox_title = title.fullText .. "/sandbox"
local diff
if title_exists(sandbox_title) then
diff = " (" .. compare_pages(title.fullText, sandbox_title, "diff") .. ")"
end
links:insert("[[:" .. sandbox_title .. "|sandbox]]" .. (diff or ""))
end
end
if #links > 0 then
output:insert("<dd> ''Useful links'': " .. links:concat(" • ") .. "</dd>")
end
end
end
end
return {
output:insert("</dl>\n")
title = title,
docTitle = docTitle,
-- Show error from [[Module:category tree/topic cat/data]] on its submodules'
-- View, display, edit, and purge links if /doc exists.
-- documentation to, for instance, warn about duplicate labels.
viewLinkDisplay = message('view-link-display'),
if startswith(title.fullText, "Module:category tree/topic/") then
editLinkDisplay = message('edit-link-display'),
local ok, err = pcall(require, "Module:category tree/topic/data")
historyLinkDisplay = message('history-link-display'),
if not ok then
purgeLinkDisplay = message('purge-link-display'),
output:insert('<span class="error">' .. err .. '</span>\n\n')
preload = preload,
end
createLinkDisplay = message('create-link-display')
end
}
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 docTitle = data.docTitle
-- yes, we do intend to purge the template page on which the documentation appears
local purgeLink = makeWikilink("Special:Purge/" .. data.title.prefixedText, data.purgeLinkDisplay)
if docTitle.exists then
if doc_title and doc_title.exists then
local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
-- Override automatic documentation, if present.
local editLink = makeWikilink("Special:EditPage/" .. docTitle.prefixedText, data.editLinkDisplay)
doc_content = expand_template{ title = doc_title.fullText }
local historyLink = makeWikilink("Special:PageHistory/" .. docTitle.prefixedText, data.historyLinkDisplay)
elseif not doc_content and fallback_docs then
return "&#91;" .. viewLink .. "&#93; &#91;" .. editLink .. "&#93; &#91;" .. historyLink .. "&#93; &#91;" .. purgeLink .. "&#93;"
doc_content = expand_template{
else
title = fallback_docs,
local createLink = makeUrlLink(docTitle:canonicalUrl{action = 'edit', preload = data.preload}, data.createLinkDisplay)
args = {
return  "&#91;" .. createLink .. "&#93; &#91;" .. purgeLink .. "&#93;"
['user'] = user_name,
['page'] = title.fullText,
['skin name'] = skin_name,
},
}
end
end
return ret
end


function p.makeStartBoxData(args, env, links)
if doc_content then
--[=[
output:insert(doc_content)
-- 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'
-- 'file-namespace-heading' --> 'Summary'
-- 'other-namespaces-heading' --> 'Documentation'
-- '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
end
local data = {}
 
output:insert(('\n<%s style="clear: both;" />'):format(args.hr == "below" and "hr" or "br"))
-- Heading
if cats_auto_generated and not cats[1] and (not doc_content or not doc_content:find("%[%[Category:")) then
local heading = args.heading -- Blank values are not removed.
if contentModel == "Scribunto" then
if heading == '' then
cats:insert("Uncategorized modules")
-- Don't display the start box if the heading arg is defined but blank.
-- elseif title.nsText == "Template" then
return nil
-- cats:insert("Uncategorized templates")
end
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 == 6 then -- File namespace
data.heading = message('file-namespace-heading')
else
data.heading = message('other-namespaces-heading')
end
end
-- Heading CSS
if needs_doc then
local headingStyle = args['heading-style']
cats:insert("Templates and modules needing documentation")
if headingStyle then
data.headingStyleText = headingStyle
else
-- 'documentation-heading'
data.headingClass = message('main-div-heading-class')
end
end
-- Data for the [view][edit][history][purge] or [create] links.
for _, cat in ipairs(cats) do
if links then
output:insert("[[Category:" .. cat .. "]]")
-- 'mw-editsection-like plainlinks'
data.linksClass = message('start-box-link-classes')
data.links = links
end
end
return data
output:insert("</div>\n")
 
return output:concat()
end
end


function p.renderStartBox(data)
function export.module_auto_doc_table()
-- Renders the start box html.
local parts = {}
-- @data - a table of data generated by p.makeStartBoxData.
local function ins(text)
local sbox = mw.html.create('div')
insert(parts, text)
sbox
-- 'documentation-startbox'
:addClass(message('start-box-class'))
:newline()
:tag('span')
:addClass(data.headingClass)
:attr('id', 'documentation-heading')
:cssText(data.headingStyleText)
:wikitext(data.heading)
local links = data.links
if links then
sbox:tag('span')
:addClass(data.linksClass)
:attr('id', data.linksId)
:wikitext(links)
end
end
return tostring(sbox)
ins('{|class="wikitable"')
end
ins("! Regex !! Category !! Handling modules")
 
for _, spec in ipairs(module_regex) do
----------------------------------------------------------------------------
local cat_text
-- Documentation content
local cats = spec.cat
----------------------------------------------------------------------------
if cats then
 
local cat_parts = {}
p.content = makeInvokeFunc('_content')
if type(cats) == "string" then
 
cats = {cats}
function p._content(args, env)
end
-- Displays the documentation contents
for _, cat in ipairs(cats) do
-- @args - a table of arguments passed by the user
insert(cat_parts, ("<code>%s</code>"):format((cat:gsub("|", "&#124;"))))
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
end
env = env or p.getEnvironment(args)
cat_text = concat(cat_parts, ", ")
local docTitle = env.docTitle
else
local content = args.content
cat_text = "''(unspecified)''"
if not content and docTitle and docTitle.exists then
end
content = args._content or mw.getCurrentFrame():expandTemplate{title = docTitle.prefixedText}
ins("|-")
ins(("| <code>%s</code> || %s || %s"):format(spec.regex, cat_text,
is_callable(spec.process) and "''(handled internally)''" or
type(spec.process) == "string" and ("[[Module:documentation/functions/%s]]"):format(spec.process) or
"''(no documentation generator)''"))
end
end
-- The line breaks below are necessary so that "=== Headings ===" at the start and end
ins("|}")
-- of docs are interpreted correctly.
return concat(parts, "\n")
return '\n' .. (content or '') .. '\n'
end
end


p.contentTitle = makeInvokeFunc('_contentTitle')
-- Used by {{translit module documentation}}.
 
function export.translitModuleLangList(frame)
function p._contentTitle(args, env)
local pagename, subpage
env = env or p.getEnvironment(args)
local docTitle = env.docTitle
if frame.args[1] then
if not args.content and docTitle and docTitle.exists then
pagename = frame.args[1]
return docTitle.prefixedText
else
else
return ''
local title = get_current_title()
subpage = title.subpageText
pagename = title.text
if subpage ~= pagename then
pagename = title.rootText
end
end
end
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
--
--]=]
-- Get environment data.
local translitModule = pagename
env = env or p.getEnvironment(args)
local subjectSpace = env.subjectSpace
local languageObjects = require("Module:languages/byTranslitModule")(translitModule)
local docTitle = env.docTitle
local codeInPagename = pagename:match("^([%l-]+)%-.*translit$")
if not subjectSpace or not docTitle then
return nil
local categories = Array()
end
local codeInPagenameInList = false
if codeInPagename then
if languageObjects[1] and subpage ~= "documentation" then
local agreement = languageObjects[2] and "s" or ""
categories:insert("[[Category:Transliteration modules used by " ..
#languageObjects .. " language" .. agreement .. "]]")
end
-- Check whether we should output the end box at all. Add the end
languageObjects = Array(languageObjects)
-- box by default if the documentation exists or if we are in the
:filter(
-- user, module or template namespaces.
function (lang)
local linkBox = args['link box']
local result = lang:getCode() ~= codeInPagename
if linkBox == 'off'
codeInPagenameInList = codeInPagenameInList or result
or not (
return result
docTitle.exists
end)
or subjectSpace == 2
or subjectSpace == 828
or subjectSpace == 10
)
then
return nil
end
end
 
-- Assemble the link box.
if subpage ~= "documentation" then
local text = ''
for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
if linkBox then
local script = get_script(script_code)
text = text .. linkBox
if script then
else
categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
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 '') .. '<br />'
if not args.content and not args[1] 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
end
text = text .. ' ' .. (p.makeSubpagesBlurb(args, env) or '') --"Subpages of this template"
end
end
end
end
local box = mw.html.create('div')
if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
-- 'documentation-metadata'
categories:insert("[[Category:Transliteration modules without a testcases subpage]]")
box:attr('role', 'note')
:addClass(message('end-box-class'))
-- 'plainlinks'
:addClass(message('end-box-plainlinks'))
:wikitext(text)
:done()
 
return '\n' .. tostring(box)
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
end
if docTitle.exists then
-- /doc exists; link to it.
if not languageObjects[1] then
local docLink = makeWikilink(docTitle.prefixedText)
return categories:concat()
local editDisplay = message('edit-link-display')
local editLink = makeWikilink("Special:EditPage/" .. docTitle.prefixedText, editDisplay)
local historyDisplay = message('history-link-display')
local historyLink = makeWikilink("Special:PageHistory/" .. docTitle.prefixedText, historyDisplay)
return message('transcluded-from-blurb', {docLink})
.. ' '
.. makeToolbar(editLink, historyLink)
.. '<br />'
elseif env.subjectSpace == 828 then
-- /doc does not exist; ask to create it.
local createUrl = docTitle:canonicalUrl{action = 'edit', preload = message('module-preload')}
local createDisplay = message('create-link-display')
local createLink = makeUrlLink(createUrl, createDisplay)
return message('create-module-doc-blurb', {createLink})
.. '<br />'
end
end
local langs = Array(languageObjects)
:sort(
function(lang1, lang2)
return lang1:getCode() < lang2:getCode()
end)
-- This will not error because languageObjects is not empty.
:map(languageObjects[1].makeCategoryLink)
:serialCommaJoin()
return "It is " .. ( codeInPagenameInList and "also" or "" ) ..
" used to transliterate " .. langs .. "." .. categories:concat()
end
end


function p.makeExperimentBlurb(args, env)
-- Used by {{entry name module documentation}}.
--[[
function export.entryNameModuleLangList(frame)
-- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
local pagename, subpage
-- @args - a table of arguments passed by the user
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
if frame.args[1] then
--
pagename = frame.args[1]
-- 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 sandboxEditDisplay = message('sandbox-edit-link-display')
local sandboxEditLink = makeWikilink("Special:EditPage/" .. sandboxPage, 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
else
local sandboxPreload
local title = get_current_title()
if subjectSpace == 828 then
subpage = title.subpageText
sandboxPreload = message('module-sandbox-preload')
pagename = title.text
else
sandboxPreload = message('template-sandbox-preload')
if subpage ~= pagename then
pagename = title.rootText
end
end
local sandboxCreateUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = sandboxPreload}
end
local sandboxCreateDisplay = message('sandbox-create-link-display')
local sandboxCreateLink = makeUrlLink(sandboxCreateUrl, sandboxCreateDisplay)
local entryNameModule = pagename
local mirrorSummary = message('mirror-edit-summary', {makeWikilink(templatePage)})
local mirrorPreload = message('mirror-link-preload')
local languageObjects = require("Module:languages/byEntryNameModule")(entryNameModule)
local mirrorUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = mirrorPreload, summary = mirrorSummary}
local codeInPagename = pagename:match("^([%l-]+)%-.*entryname$")
if subjectSpace == 828 then
mirrorUrl = sandboxTitle:canonicalUrl{action = 'edit', preload = templateTitle.prefixedText, summary = mirrorSummary}
local categories = Array()
local codeInPagenameInList = false
if codeInPagename then
if languageObjects[1] and subpage ~= "documentation" then
local agreement = languageObjects[2] and "s" or ""
categories:insert("[[Category:Entry name-generating modules used by " ..
#languageObjects .. " language" .. agreement .. "]]")
end
end
local mirrorDisplay = message('mirror-link-display')
local mirrorLink = makeUrlLink(mirrorUrl, mirrorDisplay)
languageObjects = Array(languageObjects)
sandboxLinks = message('sandbox-link-display') .. ' ' .. makeToolbar(sandboxCreateLink, mirrorLink)
:filter(
function (lang)
local result = lang:getCode() ~= codeInPagename
codeInPagenameInList = codeInPagenameInList or result
return result
end)
end
end
if testcasesTitle.exists then
local testcasesPage = testcasesTitle.prefixedText
if subpage ~= "documentation" then
local testcasesDisplay = message('testcases-link-display')
for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
local testcasesLink = makeWikilink(testcasesPage, testcasesDisplay)
local script = get_script(script_code)
local testcasesEditUrl = testcasesTitle:canonicalUrl{action = 'edit'}
if script then
local testcasesEditDisplay = message('testcases-edit-link-display')
categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
local testcasesEditLink = makeWikilink("Special:EditPage/" .. testcasesPage, testcasesEditDisplay)
end
-- for Modules, add testcases run link if exists
if testcasesTitle.contentModel == "Scribunto" and testcasesTitle.talkPageTitle and testcasesTitle.talkPageTitle.exists then
local testcasesRunLinkDisplay = message('testcases-run-link-display')
local testcasesRunLink = makeWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink, testcasesRunLink)
else
testcasesLinks = testcasesLink .. ' ' .. makeToolbar(testcasesEditLink)
end
end
else
local testcasesPreload
if subjectSpace == 828 then
testcasesPreload = message('module-testcases-preload')
else
testcasesPreload = message('template-testcases-preload')
end
local testcasesCreateUrl = testcasesTitle:canonicalUrl{action = 'edit', preload = testcasesPreload}
local testcasesCreateDisplay = message('testcases-create-link-display')
local testcasesCreateLink = makeUrlLink(testcasesCreateUrl, testcasesCreateDisplay)
testcasesLinks = message('testcases-link-display') .. ' ' .. makeToolbar(testcasesCreateLink)
end
end
local messageName
if subjectSpace == 828 then
if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
messageName = 'experiment-blurb-module'
categories:insert("[[Category:Entry name-generating modules without a testcases subpage]]")
else
end
messageName = 'experiment-blurb-template'
if not languageObjects[1] then
return categories:concat()
end
end
return message(messageName, {sandboxLinks, testcasesLinks})
local langs = Array(languageObjects)
:sort(
function(lang1, lang2)
return lang1:getCode() < lang2:getCode()
end)
-- This will not error because languageObjects is not empty.
:map(languageObjects[1].makeCategoryLink)
:serialCommaJoin()
return "It is " .. ( codeInPagenameInList and "also" or "" ) ..
" used to generate entry names for " .. langs .. "." .. categories:concat()
end
end


function p.makeCategoriesBlurb(args, env)
-- Used by {{sortkey module documentation}}.
--[[
function export.sortkeyModuleLangList(frame)
-- Generates the text "Please add categories to the /doc subpage."
local pagename, subpage
-- @args - a table of arguments passed by the user
-- @env - environment table containing title objects, etc., generated with p.getEnvironment
if frame.args[1] then
-- Messages:
pagename = frame.args[1]
-- 'doc-link-display' --> '/doc'
else
-- 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
local title = get_current_title()
--]]
subpage = title.subpageText
local docTitle = env.docTitle
pagename = title.text
if not docTitle then
return nil
if subpage ~= pagename then
pagename = title.rootText
end
end
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:
local sortkeyModule = pagename
-- 'template-pagetype' --> 'template'
-- 'module-pagetype' --> 'module'
local languageObjects = require("Module:languages/bySortkeyModule")(sortkeyModule)
-- 'default-pagetype' --> 'page'
local codeInPagename = pagename:match("^([%l-]+)%-.*sortkey$")
-- 'subpages-link-display' --> 'Subpages of this $1'
--]]
local categories = Array()
local subjectSpace = env.subjectSpace
local codeInPagenameInList = false
local templateTitle = env.templateTitle
if codeInPagename then
if not subjectSpace or not templateTitle then
if languageObjects[1] and subpage ~= "documentation" then
return nil
local agreement = languageObjects[2] and "s" or ""
categories:insert("[[Category:Sortkey-generating modules used by " ..
#languageObjects .. " language" .. agreement .. "]]")
end
languageObjects = Array(languageObjects)
:filter(
function (lang)
local result = lang:getCode() ~= codeInPagename
codeInPagenameInList = codeInPagenameInList or result
return result
end)
end
end
local pagetype
if subjectSpace == 10 then
if subpage ~= "documentation" then
pagetype = message('template-pagetype')
for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
elseif subjectSpace == 828 then
local script = get_script(script_code)
pagetype = message('module-pagetype')
if script then
else
categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
pagetype = message('default-pagetype')
end
end
end
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:
if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
-- 'display-strange-usage-category' --> true
categories:insert("[[Category:Sortkey-generating modules without a testcases subpage]]")
-- '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
end
local subpage = title.subpageText
if message('display-strange-usage-category', nil, 'boolean')
if not languageObjects[1] then
and (
return categories:concat()
subpage == message('doc-subpage')
or subjectSpace ~= 828 and subpage == message('testcases-subpage')
)
then
return makeCategoryLink(message('strange-usage-category'))
end
end
return ''
local langs = Array(languageObjects)
:sort(
function(lang1, lang2)
return lang1:getCode() < lang2:getCode()
end)
-- This will not error because languageObjects is not empty.
:map(languageObjects[1].makeCategoryLink)
:serialCommaJoin()
return "It is " .. ( codeInPagenameInList and "also" or "" ) ..
" used to sort " .. langs .. "." .. categories:concat()
end
end


return p
return export

Latest revision as of 08:44, 29 June 2025

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

local export = {}

local array_module = "Module:array"
local debug_track_module = "Module:debug/track"
local frame_module = "Module:frame"
local fun_is_callable_module = "Module:fun/isCallable"
local languages_module = "Module:languages"
local links_module = "Module:links"
local load_module = "Module:load"
local module_categorization_module = "Module:module categorization"
local number_list_show_module = "Module:number list/show"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local scripts_module = "Module:scripts"
local string_endswith_module = "Module:string/endswith"
local string_gline_module = "Module:string/gline"
local string_insert_module = "Module:string/insert"
local string_startswith_module = "Module:string/startswith"
local string_utilities_module = "Module:string utilities"
local template_parser_module = "Module:template parser"
local title_exists_module = "Module:title/exists"
local title_new_title_module = "Module:title/newTitle"

local concat = table.concat
local error = error
local full_url = mw.uri.fullUrl
local get_current_title = mw.title.getCurrentTitle
local insert = table.insert
local ipairs = ipairs
local list_to_text = mw.text.listToText
local new_message = mw.message.new
local pcall = pcall
local require = require
local tonumber = tonumber
local tostring = tostring
local type = type
local unpack = unpack or table.unpack -- Lua 5.2 compatibility

local function Array(...)
	Array = require(array_module)
	return Array(...)
end

local function categorize_module(...)
	categorize_module = require(module_categorization_module).categorize
	return categorize_module(...)
end

local function debug_track(...)
	debug_track = require(debug_track_module)
	return debug_track(...)
end

local function endswith(...)
	endswith = require(string_endswith_module)
	return endswith(...)
end

local function expand_template(...)
	expand_template = require(frame_module).expandTemplate
	return expand_template(...)
end

local function find_templates(...)
	find_templates = require(template_parser_module).find_templates
	return find_templates(...)
end

local function full_link(...)
	full_link = require(links_module).full_link
	return full_link(...)
end

local function get_lang(...)
	get_lang = require(languages_module).getByCode
	return get_lang(...)
end

local function get_pagetype(...)
	get_pagetype = require(pages_module).get_pagetype
	return get_pagetype(...)
end

local function get_script(...)
	get_script = require(scripts_module).getByCode
	return get_script(...)
end

local function gline(...)
	gline = require(string_gline_module)
	return gline(...)
end

local function is_callable(...)
	is_callable = require(fun_is_callable_module)
	return is_callable(...)
end

local function is_documentation(...)
	is_documentation = require(pages_module).is_documentation
	return is_documentation(...)
end

local function is_sandbox(...)
	is_sandbox = require(pages_module).is_sandbox
	return is_sandbox(...)
end

local function new_title(...)
	new_title = require(title_new_title_module)
	return new_title(...)
end

local function number_list_show_table(...)
	number_list_show_table = require(number_list_show_module).table
	return number_list_show_table(...)
end

local function preprocess(...)
	preprocess = require(frame_module).preprocess
	return preprocess(...)
end

local function process_params(...)
	process_params = require(parameters_module).process
	return process_params(...)
end

local function safe_load_data(...)
	safe_load_data = require(load_module).safe_load_data
	return safe_load_data(...)
end

local function split(...)
	split = require(string_utilities_module).split
	return split(...)
end

local function startswith(...)
	startswith = require(string_startswith_module)
	return startswith(...)
end

local function string_insert(...)
	string_insert = require(string_insert_module)
	return string_insert(...)
end

local function title_exists(...)
	title_exists = require(title_exists_module)
	return title_exists(...)
end

local function ugsub(...)
	ugsub = require(string_utilities_module).gsub
	return ugsub(...)
end

local function umatch(...)
	umatch = require(string_utilities_module).match
	return umatch(...)
end

local skins = {
	["common"     ] = "";
	["vector"     ] = "Vector";
	["monobook"   ] = "Monobook";
	["cologneblue"] = "Cologne Blue";
	["modern"     ] = "Modern";
}

local function track(page)
	debug_track("documentation/" .. page)
	return true
end

local function compare_pages(page1, page2, text)
	return "[" .. tostring(
		full_url("Special:ComparePages", {page1 = page1, page2 = page2}))
		.. " " .. text .. "]"
end

-- Avoid transcluding [[Module:languages/cache]] everywhere.
local lang_cache = setmetatable({}, { __index = function (self, k)
	return require("Module:languages/cache")[k]
end })

local function zh_link(word)
	return full_link{
		lang = lang_cache.zh,
		term = word
	}
end

local function make_languages_data_documentation(title, cats, division)
	local doc_template, module_cat
	if endswith(division, "/extra") then
		division = division:sub(1, -7)
		doc_template = "language extradata documentation"
		module_cat = "Language extra data modules"
	else
		doc_template = "language data documentation"
		module_cat = "Language data modules"
	end
	local sort_key
	if division == "exceptional" then
		sort_key = "x"
	else
		sort_key = division:gsub("/", "")
	end
	cats:insert(module_cat .. "|" .. sort_key)
	return {
		title = doc_template
	}
end

local function make_Unicode_data_documentation(title, cats)
	local subpage, first_three_of_code_point
		= title.fullText:match("^Module:Unicode data/([^/]+)/(%x%x%x)$")
	if subpage == "names" or subpage == "images" or subpage == "emoji images" then
		local low, high =
			tonumber(first_three_of_code_point .. "000", 16),
			tonumber(first_three_of_code_point .. "FFF", 16)
		local text, text_type
		if subpage == "names" then
			text_type = "titles of images"
		elseif subpage == "images" then
			text_type = "titles of images"
		elseif subpage == "emoji images" then
			text_type = "emoji-style images"
		end
		text = string.format(
			"This data module contains the " .. text_type .. " of " ..
			"[[Appendix:Unicode|Unicode]] code points within the range U+%04X to U+%04X.",
			low, high)
		if subpage == "images" and safe_load_data("Module:Unicode data/emoji images/" .. first_three_of_code_point) then
			text = text .. " This list includes the text variants of emojis. For the list of emoji variants of those characters, see [[Module:Unicode data/emoji images/" .. first_three_of_code_point .. "]]."
		elseif subpage == "emoji images" then
			text = text .. " For text-style images, see [[Module:Unicode data/images/" .. first_three_of_code_point .. "]]."
		end
		return text
	end
end

local function insert_lang_data_module_cats(cats, langcode, overall_data_module_cat)
	local lang = lang_cache[langcode]
	if lang then
		local langname
		if lang._fullCode then
			langname = lang_cache[lang._fullCode]:getCanonicalName()
		else
			langname = lang:getCanonicalName()
		end
		cats:insert(overall_data_module_cat .. "|" .. langname)
		cats:insert(langname .. " modules")
		cats:insert(langname .. " data modules")
		return lang, langname
	end
end

--[=[
This provides categories and documentation for various data modules, so that [[Category:Uncategorized modules]] isn't
unnecessarily cluttered. It is a list of tables, each of which have the following possible fields:

`regex` (required): A Lua pattern to match the module's title. If it matches, the data in this entry will be used.
	Any captures in the pattern can by referenced in the `cat` field using %1 for the first capture, %2 for the
	second, etc. (often used for creating the sortkey for the category). In addition, the captures are passed to the
	`process` function as the third and subsequent parameters.

`process` (optional): This may be a function or a string. If it is a function, it is called as follows:
	   `process(TITLE, CATS, CAPTURE1, CAPTURE2, ...)`
	where:
	   * TITLE is a title object describing the module's title; see
	     [https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Title_objects].
	   * CATS is an array object (see [[Module:array]]) of categories that the module will be added to.
	   * CAPTURE1, CAPTURE2, ... contain any captures in the `regex` field.
	The return value of `process` should either be a string (which will be used as the module's documentation), or a
	table specifying the name of a template to expand to get the documentation, along with the arguments to that
	template. In the latter format, the template name (bare, without the "Template:" prefix) should be in the `title`
	field, and any arguments should be in `args; in this case, the template name will be listed above the generated
	documentation as the source of the documentation, along with an edit button to edit the template's contents.
	If, however, the return value of the `process` function is a string, any template invocations will be expanded
	using frame:preprocess(), and [[Module:documentation]] will be listed as the source of the documentation.

	If `process` itself is a string rather than a function, it should name a submodule under
	[[Module:documentation/functions/]] which returns a function, of the same type as described above. This submodule
	will be specified as the source of the documentation (unless it returns a table naming a template to expand to get
	the documentation, as described above).

	If `process` is omitted entirely, the module will have no documentation.

`cat` (optional): A string naming the category into which the module should be placed, or a list of such strings.
	Captures specified in `regex` may be referenced in this string using %1 for the first capture, %2 for the second,
	etc. It is also possible to add categories in the `process` function by inserting them into the passed-in CATS
	array (the second parameter).
]=]

local module_regex = {
	{
		regex = "^Module:languages/data/(3/%l/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(3/%l)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(2/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(2)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(exceptional/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(exceptional)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/.+$",
		cat = "Language and script modules",
	},
	{
		regex = "^Module:scripts/.+$",
		cat = "Language and script modules",
	},
	{
		regex = "^Module:data tables/data..?.?.?$",
		cat = "Reference module sharded data tables",
	},
	{
		regex = "^Module:zh/data/dial%-pron/.+$",
		cat = "Chinese dialectal pronunciation data modules",
		process = "zh dial or syn",
	},
	{
		regex = "^Module:zh/data/dial%-syn/.+$",
		cat = "Chinese dialect synonyms data modules",
		process = "zh dial or syn",
	},
	{
		regex = "^Module:zh/data/glyph%-data/.+$",
		cat = "Chinese historical character forms data modules",
		process = function(title, cats)
			local character = title.fullText:match("^Module:zh/data/glyph%-data/(.+)")
			if character then
				return ("This module contains data on historical forms of the Chinese character %s.")
					:format(zh_link(character))
			end
		end,
	},
	{
		regex = "^Module:zh/data/ltc%-pron/(.+)$",
		cat = "Middle Chinese pronunciation data modules|%1",
		process = "zh data",
	},
	{
		regex = "^Module:zh/data/och%-pron%-BS/(.+)$",
		cat = "Old Chinese (Baxter-Sagart) pronunciation data modules|%1",
		process = "zh data",
	},
	{
		regex = "^Module:zh/data/och%-pron%-ZS/(.+)$",
		cat = "Old Chinese (Zhengzhang) pronunciation data modules|%1",
		process = "zh data",
	},
	{
		-- capture rest of zh/data submodules
		regex = "^Module:zh/data/(.+)$",
		cat = "Chinese data modules|%1",
	},
	{
		regex = "^Module:mul/guoxue%-data/cjk%-?(.*)$",
		process = "guoxue-data",
	},
	{
		regex = "^Module:Unicode data/(.+)$",
		cat = "Unicode data modules|%1",
		process = make_Unicode_data_documentation,
	},
	{
		regex = "^Module:number list/data/(.+)$",
		process = function(title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "Number data modules")
			if lang then
				return ("This module contains data on various types of numbers in %s.\n%s")
					:format(lang:makeCategoryLink(), number_list_show_table() or "")
			end
		end,
	},
	{
		regex = "^Module:accel/(.+)$",
		process = function(title, cats)
			local lang_code = title.subpageText
			local lang = lang_cache[lang_code]
			if lang then
				cats:insert(lang:getCanonicalName() .. " modules|accel")
				cats:insert(("Accel submodules|%s"):format(lang:getCanonicalName()))
				return ("This module contains new entry creation rules for %s; see [[WT:ACCEL]] for an overview, and [[Module:accel]] for information on creating new rules.")
					:format(lang:makeCategoryLink())
			end
		end,
	},
	{
		regex = "^Module:inc%-ash/dial/data/(.+)$",
		cat = "Ashokan Prakrit modules|%1",
		process = function(title, cats)
			local word = title.fullText:match("^Module:inc%-ash/dial/data/(.+)$")
			if word then
				local lang = lang_cache["inc-ash"]
				return ("This module contains data on the pronunciation of %s in dialects of %s.")
					:format(full_link({ term = word, lang = lang }, "term"),
						lang:makeCategoryLink())
			end
		end,
	},
	{
		regex = "^.+%-translit$",
		process = "translit",
	},
	{
		regex = "^Module:form of/lang%-data/(.+)$",
		process = function(title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Language-specific form-of modules")
			if lang then
				-- FIXME, display more info.
				return "This module contains language-specific form-of data (tags, shortcuts, base lemma params. etc.) for " ..
					langname .. "."
			end
		end
	},
	{
		regex = "^Module:labels/data/lang/(.+)$",
		process = function(title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "Language-specific label data modules")
			if lang then
				return {
					title = "label language-specific data documentation",
					args = { [1] = lang_code },
				}
			end
		end
	},
	{
		regex = "^Module:category tree/lang/(.+)$",
		process = function(title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Category tree data modules/lang")
			if lang then
				return "This module handles generating the descriptions and categorization for " .. langname .. " category pages "
					.. "of the format \"" .. langname .. " LABEL\" where LABEL can be any text. Examples are "
					.. "[[:Category:Bulgarian conjugation 2.1 verbs]] and [[:Category:Russian velar-stem neuter-form nouns]]. "
					.. "This module is part of the category tree system, which is a general framework for generating the "
					.. "descriptions and categorization of category pages.\n\n"
					.. "For more information, see [[Module:category tree/lang/documentation]].\n\n"
					.. "'''NOTE:''' If you add a new language-specific module, you must add the language code to the "
					.. "list at the top of [[Module:category tree/lang]] in order for the module to be recognized."
			end
		end
	},
	{
		regex = "^Module:category tree/topic/(.+)$",
		process = function(title, cats, submodule)
			cats:insert("Category tree data modules/topic| ")
			return {
				title = "topic cat data submodule documentation"
			}
		end
	},
	{
		regex = "^Module:category tree/(.+)$",
		process = function(title, cats, submodule)
			cats:insert("Category tree data modules| ")
			return {
				title = "category tree data submodule documentation"
			}
		end
	},
	{
		regex = "^Module:ja/data/(.+)$",
		cat = "Japanese data modules|%1",
	},
	{
		regex = "^Module:fi%-dialects/data/feature/Kettunen1940 ([0-9]+)$",
		cat = "Finnish dialectal data atlas modules|%1",
		process = function(title, cats, shard)
			return "This module contains shard " .. shard .. " of the online version of Lauri Kettunen's 1940 work " ..
				"''Suomen murteet III A. Murrekartasto'' (\"Finnish dialects III A: Dialect atlas\"). " ..
				"It was imported and converted from urn:nbn:fi:csc-kata20151130145346403821, published by the " ..
				"''Kotimaisten kielten keskus'' under the CC BY 4.0 license."
		end
	},
	{
		regex = "^Module:fi%-dialects/data/feature/(.+)",
		cat = "Finnish dialectal data modules|%1",
	},
	{
		regex = "^Module:fi%-dialects/data/word/(.+)",
		cat = "Finnish dialectal data modules|%1",
	},
	{
		regex = "^Module:Swadesh/data/([%l-]+)$",
		process = function(title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
			if lang then
				return "This module contains the [[Swadesh list]] of basic vocabulary in " .. langname .. "."
			end
		end
	},
	{
		regex = "^Module:Swadesh/data/([%l-]+)/([^/]*)$",
		process = function(title, cats, lang_code, variety)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
			if lang then
				local prefix = "This module contains the [[Swadesh list]] of basic vocabulary in the "
				local etym_lang = get_lang(variety, nil, "allow etym")
				if etym_lang then
					return ("%s %s variety of %s."):format(prefix, etym_lang:getCanonicalName(), langname)
				end
				local script = get_script(variety)
				if script then
					return ("%s %s %s script."):format(prefix, langname, script:getCanonicalName())
				end
				return ("%s %s variety of %s."):format(prefix, variety, langname)
			end
		end
	},
	{
		regex = "^Module:typing%-aids",
		process = function(title, cats)
			local data_suffix = title.fullText:match("^Module:typing%-aids/data/(.+)$")
			local sortkey
			if data_suffix then
				if data_suffix:find "^[%l-]+$" then
					local lang = get_lang(data_suffix)
					if lang then
						sortkey = lang:getCanonicalName()
						cats:insert(sortkey .. " data modules")
					end
				elseif data_suffix:find "^%u%l%l%l$" then
					local script = get_script(data_suffix)
					if script then
						sortkey = script:getCanonicalName()
						cats:insert(script:getCategoryName())
					end
				end
				cats:insert("Character insertion data modules|" .. (sortkey or data_suffix))
			end
		end,
	},
	{
		regex = "^Module:R:([%l-]+):(.+)$",
		process = function(title, cats, lang_code, refname)
			local lang = lang_cache[lang_code]
			if lang then
				cats:insert(lang:getCanonicalName() .. " modules|" .. refname)
				cats:insert(("Reference modules|%s"):format(lang:getCanonicalName()))
				return "This module implements the reference template {{temp|R:" ..	lang_code .. ":" .. refname .. "}}."
			end
		end,
	},
	{
		regex = "^Module:Quotations/([%l-]+)/?(.*)",
		process = "Quotation",
	},
	{
		regex = "^Module:affix/lang%-data/([%l-]+)",
		process = "affix lang-data",
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)$",
		process = function(title, cats, lang_code)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				cats:insert("Dialect synonyms data modules|" .. langname)
				cats:insert(langname .. " dialect synonyms data modules| ")
				return "This module contains data on specific varieties of " .. langname .. ", for use by " ..
					"{{tl|dialect synonyms}}. The actual synonyms themselves are contained in submodules.\n\n" ..
					expand_template({ title = 'dial syn', args = { lang_code, ["demo mode"] = "y" } })
			end
		end,
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)/(.+)$",
		process = function(title, cats, lang_code, term)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				cats:insert("Dialect synonyms data modules|" .. langname)
				cats:insert(langname .. " dialect synonyms data modules|" .. term)
				return ("This module contains dialectal %s synonyms for {{m|%s|%s}}.\n\n%s"):format(langname, lang_code, term, expand_template({ title = 'dial syn', args = { lang_code, term } }))
			end
		end,
	},
	{
		regex = "^Module:bibliography/data/([%l-]+)$",
		process = function(title, cats, lang_code)
			if lang_code == "preload" then
				return 'Used as a base model for other languages when the button "create new language submodule" is clicked.'
			end
			local page = require(title.fullText).bib_page
			if not page then
				page = lang_cache[lang_code]:getCanonicalName()
				if page then
					cats:insert(page.." modules")
				end
			end
			cats:insert("Reference modules")
			return "This module holds bibliographical data for "..page..". For the formatted bibliography see '''[[Appendix:Bibliography/"..page.."]]'''."
		end,
	},
}

function export.show(frame)
	local boolean_default_false = {type = "boolean", default = false}
	local args = process_params(frame.args, {
		["hr"] = true,
		["for"] = true,
		["from"] = true,
		["allowondoc"] = boolean_default_false, -- Don't throw an error if used on a documentation subpage.
		["notsubpage"] = boolean_default_false,
		["nodoc"] = boolean_default_false,
		["nolinks"] = boolean_default_false, -- suppress all "Useful links"
		["nosandbox"] = boolean_default_false, -- supress sandbox
	})
	
	local output = Array('\n<div class="documentation" style="display:block; clear:both">\n')
	local cats = Array()
	
	local nodoc = args.nodoc
	
	if (not args.hr) or (args.hr == "above") then
		output:insert("----\n")
	end
	
	local title = args["for"] and new_title(args["for"]) or get_current_title()
	local doc_title = args.from ~= "-" and new_title(args.from or title.fullText .. '/documentation') or nil
	local contentModel = title.contentModel
	local pagetype, is_script_or_stylesheet = get_pagetype(title)
	local preload, fallback_docs, doc_content, old_doc_title, user_name, skin_name, needs_doc
	local doc_content_source = "Module:documentation"
	local auto_generated_cat_source
	local cats_auto_generated = false
	
	if not args.allowondoc and is_documentation(title) then
		-- TODO: merge with {{documentation subpage}}, and choose behaviour based on the page type.
		error("This template should not be used on a documentation page. Please use [[Template:documentation subpage]].")
	elseif is_sandbox(title) then
		local sandbox_ns = title.nsText
		preload = ("Template:documentation/preload%s%sSandbox"):format(
			sandbox_ns == "Module" and sandbox_ns or "Template",
			title.rootText:match("^[Uu]ser:(.+)") and "User" or ""
		)
	elseif pagetype:match("%f[%w]gadget%f[%W]") then
		preload = "Template:documentation/preloadGadget"
	elseif pagetype:match("%f[%w]script%f[%W]") then -- .js
		if title.nsText == "MediaWiki" then
			preload = "Template:documentation/preloadMediaWikiJavaScript"
		else
			preload  = "Template:documentation/preloadTemplate" -- XXX
			if title.nsText == "User" then
				user_name = title.rootText
			end
		end
		is_script_or_stylesheet = true
	elseif pagetype:match("%f[%w]stylesheet%f[%W]") then -- .css
		preload  = "Template:documentation/preloadTemplate" -- XXX
		if title.nsText == "User" then
			user_name = title.rootText
		end
		is_script_or_stylesheet = true
	elseif contentModel == "Scribunto" then -- Exclude pages in Module: which aren't Scribunto.
		preload  = "Template:documentation/preloadModule"
	elseif pagetype:match("%f[%w]template%f[%W]") or pagetype:match("%f[%w]project%f[%W]") then
		preload  = "Template:documentation/preloadTemplate"
	end

	if doc_title and doc_title.isRedirect then
		old_doc_title = doc_title
		doc_title = doc_title.redirectTarget
	end

	output:insert("<dl class=\"plainlinks\" style=\"font-size: smaller;\">")

	local function get_module_doc_and_cats(categories_only)
		cats_auto_generated = true
		local automatic_cats = nil
		if user_name then
			fallback_docs = "documentation/fallback/user module"
			automatic_cats = {"User sandbox modules"}
		else
			for _, data in ipairs(module_regex) do
				local captures = {umatch(title.fullText, data.regex)}
				if #captures > 0 then
					local cat, process_function
					if is_callable(data.process) then
						process_function = data.process
					elseif type(data.process) == "string" then
						doc_content_source = "Module:documentation/functions/" .. data.process
						process_function = require(doc_content_source)
					end

					if process_function then
						doc_content = process_function(title, cats, unpack(captures))
					end
					if type(doc_content) == "table" then
						doc_content_source = doc_content.title and "Template:" .. doc_content.title or doc_content_source
						doc_content = expand_template(doc_content)
					elseif doc_content ~= nil then
						doc_content = preprocess(doc_content)
					end
					cat = data.cat
					
					if cat then
						if type(cat) == "string" then
							cat = {cat}
						end
						for _, c in ipairs(cat) do
							insert(cats, (ugsub(title.fullText, data.regex, c)))
						end
					end
					break
				end
			end
		end

		if title.subpageText == "templates" then
			cats:insert("Template interface modules")
		end

		if automatic_cats then
			for _, c in ipairs(automatic_cats) do
				cats:insert(c)
			end
		end
		
		if #cats == 0 then
			local auto_cats = categorize_module(frame, "return raw", "noerror")
			if #auto_cats > 0 then
				auto_generated_cat_source = "Module:module categorization"
			end
			for _, category in ipairs(auto_cats) do
				cats:insert(category)
			end
		end

		-- meaning module is not in user’s sandbox or one of many datamodule boring series
		needs_doc = not categories_only and not (automatic_cats or doc_content or fallback_docs)
	end

	-- Override automatic documentation, if present.
	if doc_title and doc_title.exists then
		local cats_auto_generated_text = ""
		if contentModel == "Scribunto" then
			local doc_page_content = doc_title.content
			-- Track then do nothing if there are uses of includeonly. The
			-- pattern is slightly too permissive, but any false-positives are
			-- obvious typos that should be corrected.
			if doc_page_content:lower():match("</?includeonly%f[%s/>][^>]*>") then
				track("module-includeonly")
			else
				-- Check for uses of {{module cat}}. find_templates treats the
				-- input as transcluded by default (i.e. it parses the wikitext
				-- which will be transcluded through to the module page).
				local module_cat
				for template in find_templates(doc_page_content) do
					if template:get_name() == "module cat" then
						module_cat = true
						break
					end
				end
				if not module_cat then
					get_module_doc_and_cats("categories only")
					auto_generated_cat_source = auto_generated_cat_source or doc_content_source
					cats_auto_generated_text = " Categories were auto-generated by [[" .. auto_generated_cat_source .. "]]. <sup>[[" ..
						new_title(auto_generated_cat_source):fullUrl{action = "edit"} ..  " edit]]</sup>"
				end
			end
		end

		output:insert(
			"<dd><i style=\"font-size: larger;\">The following " ..
			"[[Help:Documenting templates and modules|documentation]] is located at [[" ..
			doc_title.fullText .. "]]. " .. "<sup>[[" .. doc_title:fullUrl{action = "edit"} .. " edit]]</sup>" ..
			cats_auto_generated_text .. "</i></dd>")
	else
		if contentModel == "Scribunto" then
			get_module_doc_and_cats(false)
		elseif title.nsText == "Template" then
			--cats:insert("Uncategorized templates")
			needs_doc = not (fallback_docs or nodoc)
		elseif user_name and is_script_or_stylesheet then
			skin_name = skins[title.text:sub(#title.rootText + 1):match("^/(%l+)%.[jc]ss?$")]
			if skin_name then
				fallback_docs = "documentation/fallback/user " .. contentModel
			end
		end
		
		if doc_content then
			output:insert(
				"<dd><i style=\"font-size: larger;\">The following " ..
				"[[Help:Documenting templates and modules|documentation]] is " ..
				"generated by [[" .. doc_content_source .. "]]. <sup>[[" ..
				new_title(doc_content_source):fullUrl{action = "edit"} ..
				" edit]]</sup> </i></dd>")
		elseif not nodoc then
			if doc_title then
				output:insert(
					"<dd><i style=\"font-size: larger;\">This " .. pagetype ..
					" lacks a [[Help:Documenting templates and modules|documentation subpage]]. " ..
					(fallback_docs and "You may " or "Please ") ..
					"[" .. doc_title:fullUrl{action = "edit", preload = preload}
					.. " create it].</i></dd>\n")
			else
				output:insert(
					"<dd><i style=\"font-size: larger; color: #FF0000;\">Unable to auto-generate " ..
					"documentation for this " .. pagetype ..".</i></dd>\n")
			end
		end
	end
	
	if startswith(title.fullText, "MediaWiki:Gadget-") then
		local is_gadget = false
		for line in gline(new_title("MediaWiki:Gadgets-definition").content) do
			local gadget, items = line:match("^%*%s*(%a[%w_-]*)%[.-%]|(.+)$")
			if not gadget then
				gadget, items = line:match("^%*%s*(%a[%w_-]*)|(.+)$")
			end
			if gadget then
				items = Array(split(items, "|"))
				for i, item in ipairs(items) do
					if title.fullText == ("MediaWiki:Gadget-" .. item) then
						is_gadget = true

						output:insert("<dd> ''This script is a part of the <code>")
						output:insert(gadget)
						output:insert("</code> gadget ([")
						output:insert(tostring(full_url("MediaWiki:Gadgets-definition", {action = "edit"})))
						output:insert(" edit definitions])'' <dl>")
						
						output:insert("<dd> ''Description ([")
						output:insert(tostring(full_url("MediaWiki:Gadget-" .. gadget, {action = "edit"})))
						output:insert(" edit])'': ")
						
						output:insert(preprocess(new_message('Gadget-' .. gadget):plain()))
						output:insert(" </dd>")

						items:remove(i)
						if #items > 0 then
							for j, item in ipairs(items) do
								items[j] = '[[MediaWiki:Gadget-' .. item .. '|' .. item .. ']]'
							end
							output:insert("<dd> ''Other parts'': ")
							output:insert(list_to_text(items))
							output:insert("</dd>")
						end

						output:insert("</dl></dd>")

						break
					end
				end
			end
		end
		
		if not is_gadget then
			output:insert("<dd> ''This script is not a part of any [")
			output:insert(tostring(full_url("Special:Gadgets", {uselang = "en"})))
			output:insert(' gadget] ([')
			output:insert(tostring(full_url("MediaWiki:Gadgets-definition", {action = "edit"})))
			output:insert(' edit definitions]).</dd>')
		-- else
			-- cats:insert("Wiktionary gadgets")
		end
	end
	
	if old_doc_title then
		output:insert("<dd> ''Redirected from'' [")
		output:insert(old_doc_title:fullUrl{redirect = "no"})
		output:insert(" ")
		output:insert(old_doc_title.fullText)
		output:insert("] ([")
		output:insert(old_doc_title:fullUrl{action = "edit"})
		output:insert(" edit]).</dd>\n")
	end
	
	if not args.nolinks then	
		local links = Array()

		if title.isSubpage and not args.notsubpage then
			links:insert("[[:" .. title.nsText .. ":" .. title.rootText .. "|root page]]")
			links:insert("[[Special:PrefixIndex/" .. title.nsText .. ":" .. title.rootText .. "/|root page’s subpages]]")
		else
			links:insert("[[Special:PrefixIndex/" .. title.fullText .. "/|subpage list]]")
		end
		
		links:insert(
			"[" .. tostring(full_url("Special:WhatLinksHere/" .. title.fullText, {hidetrans = true, hideredirs = true})) .. " links]")
	
		if contentModel ~= "Scribunto" then
			links:insert(
				"[" .. tostring(full_url("Special:WhatLinksHere/" .. title.fullText, {hidelinks = true, hidetrans = true})) .. " redirects]")
		end
	
		if is_script_or_stylesheet then
			if user_name then
				links:insert("[[Special:MyPage" .. title.text:sub(#title.rootText + 1) .. "|your own]]")
			end
		else
			links:insert(
				"[" .. tostring(full_url("Special:WhatLinksHere/" .. title.fullText, {hidelinks = true, hideredirs = true})) .. " transclusions]")
		end
		
		if contentModel == "Scribunto" then
			local is_testcases = title.isSubpage and title.subpageText == "testcases"
			local without_subpage = title.nsText .. ":" .. title.baseText
			if is_testcases then
				links:insert("[[:" .. without_subpage .. "|tested module]]")
			else
				links:insert("[[" .. title.fullText .. "/testcases|testcases]]")
			end
			
			if user_name then
				links:insert("[[User:" .. user_name .. "|user page]]")
				links:insert("[[User talk:" .. user_name .. "|user talk page]]")
				links:insert("[[Special:PrefixIndex/User:" .. user_name .. "/|userspace]]")
			-- If sandbox module, add a link to the module that this is a sandbox of.
			-- Exclude user sandbox modules like [[User:Dine2016/sandbox]].
			elseif title.text:find("/sandbox%d*%f[/%z]") then
				cats:insert("Sandbox modules")
				
				-- Sandbox modules don’t really need documentation.
				needs_doc = false
				
				-- Don't track user sandbox modules.
				local text_title = new_title(title.text)
				if not (text_title and text_title.namespace == 2) then
					track("sandbox to be moved")
					
					local sandbox_of, diff = title.baseText
					if title_exists(sandbox_of) then
						diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
					else
						track("no sandbox of")
					end
					
					links:insert("[[:" .. sandbox_of .. "|sandbox of]]" .. (diff or ""))
				end
			-- If not a sandbox module, add link to sandbox module.
			-- Sometimes there are multiple sandboxes for a single module:
			-- [[Module:sa-pronunc/sandbox]],  [[Module:sa-pronunc/sandbox2]].
			-- Occasionally sandbox modules have their own subpages that are also
			-- sandboxes: [[Module:grc-decl/sandbox/decl]].
			else
				local sandbox_title
				if title.rootText == "grc-decl" then
					sandbox_title = string_insert(title.fullText, 16, "/sandbox")
				elseif is_testcases then
					sandbox_title = title.fullText:gsub("/testcases", "/sandbox/testcases")
				else
					sandbox_title = title.fullText .. "/sandbox"
				end
				local sandbox_link = "[[:" .. sandbox_title .. "|sandbox]]"
				
				local diff
				if title_exists(sandbox_title) then
					diff = " (" .. compare_pages(title.fullText, sandbox_title, "diff") .. ")"
				end
				
				links:insert(sandbox_link .. (diff or ""))
			end
		end
		
		if title.nsText == "Template" then
			-- Error search: all(any namespace), hastemplate (show pages using the template), insource (show source code), incategory (any/specific error) -- [[mw:Help:CirrusSearch]], [[w:Help:Searching/Regex]]
			-- apparently same with/without: &profile=advanced&fulltext=1
			local errorq = 'searchengineselect=mediawiki&search=all: hastemplate:\"'..title.rootText..'\" insource:\"'..title.rootText..'\" incategory:'
			local eincategory = "Pages_with_module_errors|ParserFunction_errors|DisplayTitle_errors|Pages_with_ISBN_errors|Pages_with_ISSN_errors|Pages_with_reference_errors|Pages_with_syntax_highlighting_errors|Pages_with_TemplateStyles_errors"
			
			links:insert(
				'[' .. tostring(full_url('Special:Search', errorq..eincategory )) .. ' errors]'
				.. ' (' ..
				'[' .. tostring(full_url('Special:Search', errorq..'ParserFunction_errors' )) .. ' parser]'
				.. '/' ..
				'[' .. tostring(full_url('Special:Search', errorq..'Pages_with_module_errors' )) .. ' module]'
				.. ')'
			)
			
			if title.isSubpage and title.text:find("/sandbox%d*%f[/%z]") then -- This is a sandbox template.
				-- At the moment there are no user sandbox templates with subpage
				-- “/sandbox”.
				cats:insert("Sandbox templates")
				
				-- Sandbox templates don’t really need documentation.
				needs_doc = false
				
				-- Will behave badly if “/sandbox” occurs twice in title!
				local sandbox_of = title.fullText:gsub("/sandbox%d*%f[/%z]", "")
				
				local diff
				if title_exists(sandbox_of) then
					diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
				else
					track("no sandbox of")
				end
				
				links:insert("[[:" .. sandbox_of .. "|sandbox of]]" .. (diff or ""))
			-- This is a template that can have a sandbox.
			elseif not args.nosandbox then -- unless we tell it not to
				local sandbox_title = title.fullText .. "/sandbox"
			
				local diff
				if title_exists(sandbox_title) then
					diff = " (" .. compare_pages(title.fullText, sandbox_title, "diff") .. ")"
				end
			
				links:insert("[[:" .. sandbox_title .. "|sandbox]]" .. (diff or ""))
			end
		end
		
		if #links > 0 then
			output:insert("<dd> ''Useful links'': " .. links:concat(" • ") .. "</dd>")
		end
	end
	
	output:insert("</dl>\n")
	
	-- Show error from [[Module:category tree/topic cat/data]] on its submodules'
	-- documentation to, for instance, warn about duplicate labels.
	if startswith(title.fullText, "Module:category tree/topic/") then
		local ok, err = pcall(require, "Module:category tree/topic/data")
		if not ok then
			output:insert('<span class="error">' .. err .. '</span>\n\n')
		end
	end
	
	if doc_title and doc_title.exists then
		-- Override automatic documentation, if present.
		doc_content = expand_template{ title = doc_title.fullText }
	elseif not doc_content and fallback_docs then
		doc_content = expand_template{
			title = fallback_docs,
			args = {
				['user'] = user_name,
				['page'] = title.fullText,
				['skin name'] = skin_name,
			},
		}
	end

	if doc_content then
		output:insert(doc_content)
	end

	output:insert(('\n<%s style="clear: both;" />'):format(args.hr == "below" and "hr" or "br"))
	
	if cats_auto_generated and not cats[1] and (not doc_content or not doc_content:find("%[%[Category:")) then
		if contentModel == "Scribunto" then
			cats:insert("Uncategorized modules")
		-- elseif title.nsText == "Template" then
			-- cats:insert("Uncategorized templates")
		end
	end
	
	if needs_doc then
		cats:insert("Templates and modules needing documentation")
	end
	
	for _, cat in ipairs(cats) do
		output:insert("[[Category:" .. cat .. "]]")
	end
	
	output:insert("</div>\n")

	return output:concat()
end

function export.module_auto_doc_table()
	local parts = {}
	local function ins(text)
		insert(parts, text)
	end
	ins('{|class="wikitable"')
	ins("! Regex !! Category !! Handling modules")
	for _, spec in ipairs(module_regex) do
		local cat_text
		local cats = spec.cat
		if cats then
			local cat_parts = {}
			if type(cats) == "string" then
				cats = {cats}
			end
			for _, cat in ipairs(cats) do
				insert(cat_parts, ("<code>%s</code>"):format((cat:gsub("|", "&#124;"))))
			end
			cat_text = concat(cat_parts, ", ")
		else
			cat_text = "''(unspecified)''"
		end
		ins("|-")
		ins(("| <code>%s</code> || %s || %s"):format(spec.regex, cat_text,
			is_callable(spec.process) and "''(handled internally)''" or
			type(spec.process) == "string" and ("[[Module:documentation/functions/%s]]"):format(spec.process) or
			"''(no documentation generator)''"))
	end
	ins("|}")
	return concat(parts, "\n")
end

-- Used by {{translit module documentation}}.
function export.translitModuleLangList(frame)
	local pagename, subpage
	
	if frame.args[1] then
		pagename = frame.args[1]
	else
		local title = get_current_title()
		subpage = title.subpageText
		pagename = title.text
		
		if subpage ~= pagename then
			pagename = title.rootText
		end
	end
	
	local translitModule = pagename
	
	local languageObjects = require("Module:languages/byTranslitModule")(translitModule)
	local codeInPagename = pagename:match("^([%l-]+)%-.*translit$")
	
	local categories = Array()
	local codeInPagenameInList = false
	if codeInPagename then
		if languageObjects[1] and subpage ~= "documentation" then
			local agreement = languageObjects[2] and "s" or ""
			categories:insert("[[Category:Transliteration modules used by " ..
				#languageObjects .. " language" .. agreement .. "]]")
		end
		
		languageObjects = Array(languageObjects)
			:filter(
				function (lang)
					local result = lang:getCode() ~= codeInPagename
					codeInPagenameInList = codeInPagenameInList or result
					return result
				end)
	end
	
	if subpage ~= "documentation" then
		for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
			local script = get_script(script_code)
			if script then
				categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
			end
		end
	end
	
	if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
		categories:insert("[[Category:Transliteration modules without a testcases subpage]]")
	end
	
	if not languageObjects[1] then
		return categories:concat()
	end
	
	local langs = Array(languageObjects)
		:sort(
			function(lang1, lang2)
				return lang1:getCode() < lang2:getCode()
			end)
		-- This will not error because languageObjects is not empty.
		:map(languageObjects[1].makeCategoryLink)
		:serialCommaJoin()
	
	return "It is " .. ( codeInPagenameInList and "also" or "" ) ..
		" used to transliterate " .. langs .. "." .. categories:concat()
end

-- Used by {{entry name module documentation}}.
function export.entryNameModuleLangList(frame)
	local pagename, subpage
	
	if frame.args[1] then
		pagename = frame.args[1]
	else
		local title = get_current_title()
		subpage = title.subpageText
		pagename = title.text
		
		if subpage ~= pagename then
			pagename = title.rootText
		end
	end
	
	local entryNameModule = pagename
	
	local languageObjects = require("Module:languages/byEntryNameModule")(entryNameModule)
	local codeInPagename = pagename:match("^([%l-]+)%-.*entryname$")
	
	local categories = Array()
	local codeInPagenameInList = false
	if codeInPagename then
		if languageObjects[1] and subpage ~= "documentation" then
			local agreement = languageObjects[2] and "s" or ""
			categories:insert("[[Category:Entry name-generating modules used by " ..
				#languageObjects .. " language" .. agreement .. "]]")
		end
		
		languageObjects = Array(languageObjects)
			:filter(
				function (lang)
					local result = lang:getCode() ~= codeInPagename
					codeInPagenameInList = codeInPagenameInList or result
					return result
				end)
	end
	
	if subpage ~= "documentation" then
		for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
			local script = get_script(script_code)
			if script then
				categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
			end
		end
	end
	
	if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
		categories:insert("[[Category:Entry name-generating modules without a testcases subpage]]")
	end
	
	if not languageObjects[1] then
		return categories:concat()
	end
	
	local langs = Array(languageObjects)
		:sort(
			function(lang1, lang2)
				return lang1:getCode() < lang2:getCode()
			end)
		-- This will not error because languageObjects is not empty.
		:map(languageObjects[1].makeCategoryLink)
		:serialCommaJoin()
	
	return "It is " .. ( codeInPagenameInList and "also" or "" ) ..
		" used to generate entry names for " .. langs .. "." .. categories:concat()
end

-- Used by {{sortkey module documentation}}.
function export.sortkeyModuleLangList(frame)
	local pagename, subpage
	
	if frame.args[1] then
		pagename = frame.args[1]
	else
		local title = get_current_title()
		subpage = title.subpageText
		pagename = title.text
		
		if subpage ~= pagename then
			pagename = title.rootText
		end
	end
	
	local sortkeyModule = pagename
	
	local languageObjects = require("Module:languages/bySortkeyModule")(sortkeyModule)
	local codeInPagename = pagename:match("^([%l-]+)%-.*sortkey$")
	
	local categories = Array()
	local codeInPagenameInList = false
	if codeInPagename then
		if languageObjects[1] and subpage ~= "documentation" then
			local agreement = languageObjects[2] and "s" or ""
			categories:insert("[[Category:Sortkey-generating modules used by " ..
				#languageObjects .. " language" .. agreement .. "]]")
		end
		
		languageObjects = Array(languageObjects)
			:filter(
				function (lang)
					local result = lang:getCode() ~= codeInPagename
					codeInPagenameInList = codeInPagenameInList or result
					return result
				end)
	end
	
	if subpage ~= "documentation" then
		for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
			local script = get_script(script_code)
			if script then
				categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
			end
		end
	end
	
	if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
		categories:insert("[[Category:Sortkey-generating modules without a testcases subpage]]")
	end
	
	if not languageObjects[1] then
		return categories:concat()
	end
	
	local langs = Array(languageObjects)
		:sort(
			function(lang1, lang2)
				return lang1:getCode() < lang2:getCode()
			end)
		-- This will not error because languageObjects is not empty.
		:map(languageObjects[1].makeCategoryLink)
		:serialCommaJoin()
	
	return "It is " .. ( codeInPagenameInList and "also" or "" ) ..
		" used to sort " .. langs .. "." .. categories:concat()
end

return export