Module:Dochead: Difference between revisions
strip /doc in the main function if present |
autocategorize metatemplates |
||
| (59 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]] | -- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]] | ||
local getArgs = require("Module:Arguments").getArgs | local getArgs = require("Module:Arguments").getArgs | ||
local iutils = require("Module:Introspection utils") | |||
local yesno = require("Module:Yesno") | |||
local p = {} | local p = {} | ||
-- TODO: (Low-ish priority): rewrite to eliminate redundant code | |||
-- Produces a hatnote that is placed on the top of a documentation page (either | -- Produces a hatnote that is placed on the top of a documentation page (either | ||
-- template or module documentation) and can autodetect and categorize: | -- template or module documentation) and can autodetect and categorize: | ||
-- - FOR MODULES: | -- - FOR MODULES: | ||
-- - whether a module has an accompanying template (overridable) | -- - whether a module has an accompanying template (overridable/toggleable) | ||
-- - whether a module is meant as a library for other modules | -- - whether a module is meant as a library for other modules | ||
-- - FOR TEMPLATES: | -- - FOR TEMPLATES: | ||
-- - whether a template has an accompanying module (overridable) | -- - whether a template has an accompanying module (overridable/toggleable) | ||
-- - what functions from which modules are invoked | -- - what functions from which modules are invoked | ||
-- | -- Helper function: categorize modules | ||
local function categorize_module(pagename) | |||
local cats = "" | |||
if pagename:match("/doc$") then | |||
cats = "[[Category:Module documentation]]" | |||
else | |||
cats = "[[Category:Lua modules]]" | |||
end | |||
return cats | |||
end | |||
-- | -- Helper function to handle modules | ||
function p. | function p.make_module_hatnote(header, pagename, corr_template, detect_corr_page) | ||
local | -- Check whether corresponding template exists | ||
-- Then check whether that template invokes the module | |||
local has_template = iutils.page_exists("Template:" .. corr_template:gsub("/doc$", "")) | |||
if | -- Check whether to use detect_corr_page option | ||
-- If the header option is dualuse, metatemplate, or noinvoke, this option | |||
-- is ignored. For any other option, it's not. | |||
if header ~= "dualuse" and header ~= "metatemplate" and header ~= "noinvoke" then | |||
has_template = has_template and detect_corr_page | |||
end | |||
-- If the module has a template, check for whether it invokes it. | |||
if has_template then | |||
local wikitext = iutils.get_and_preprocess_content("Template", corr_template:gsub("/doc$", "")) | |||
local invokes = iutils.find_invokes(wikitext) | |||
has_template = has_template and iutils.invocation_exists(invokes, pagename:gsub("/doc$", "")) | |||
end | |||
-- Heading meanings and usage on module | |||
-- - dualuse means a template is implement by a module, but its template may | |||
-- be bypassed by using its module directly from other modules. | |||
-- - metatemplate is a special case of dualuse; the template-module pair is | |||
-- for a metatemplate, a template used to build other templates. | |||
-- - noinvoke indicates a lua-based template, but the module's code is so | |||
-- specialized that its code should not be used by other modules or | |||
-- invoked by other templates. | |||
-- - metamodule and library indicate a module whose code is used mainly for | |||
-- other modules. Such modules generally don't have a corresponding | |||
-- template. | |||
-- - data/datamodule indicates a module whose purpose is to provide data | |||
-- values to other modules. Like library modules, these don't have a | |||
-- corresponding template. | |||
-- Does the module have a template? | |||
-- - dualuse, metatemplate, and noinvoke: YES (REQUIRED!!) | |||
-- - metamodule/library and data: GENERALLY NO | |||
local result = "" | |||
if header == "dualuse" then | |||
if has_template then | |||
result = string.format( | |||
"This module may be invoked by templates using its corresponding template [[Template:%s]], or used directly from other modules.", | |||
corr_template:gsub("/doc$", "") | |||
) | |||
else | |||
result = string.format( | |||
"This module has a corresponding template that is currently missing or does not use this module. ([[Special:EditPage/Template:%s|edit template]])", | |||
corr_template:gsub("/doc$", "") | |||
) | |||
end | |||
elseif header == "metatemplate" then | |||
if has_template then | |||
result = string.format( | |||
"This module implements a metatemplate, and may be invoked by templates using its corresponding template [[Template:%s]], or used directly from other modules.", | |||
corr_template:gsub("/doc$", "") | |||
) | |||
else | |||
result = "This module is used to create other Lua-based templates and has no corresponding template." | |||
end | |||
elseif header == "noinvoke" then | |||
if has_template then | |||
result = string.format( | |||
"This module should not be invoked directly; use its corresponding template instead: [[Template:%s]].", | |||
corr_template:gsub("/doc$", "") | |||
) | |||
else | |||
result = string.format( | |||
"This module implements a template that is currently missing or does not use this module. ([[Special:EditPage/Template:%s|edit template]])", | |||
corr_template:gsub("/doc$", "") | |||
) | |||
end | |||
elseif header == "library" or header == "metamodule" then | |||
result = "This module primarily serves as a library for other modules and has no corresponding template." | |||
elseif header == "data" or pagename:gsub("/doc$", ""):match("/data$") then | |||
result = "This module primarily serves to provide data values for other modules and has no corresponding template." | |||
elseif header == "none" then | |||
result = "" | |||
else | else | ||
return | if has_template and header ~= "" then | ||
result = string.format("%s This module implements [[Template:%s]].", header, corr_template:gsub("/doc$", "")) | |||
elseif has_template and header == "" then | |||
result = string.format("This module implements [[Template:%s]].", corr_template:gsub("/doc$", "")) | |||
else | |||
result = header | |||
end | |||
end | |||
-- Produce categories | |||
local cats = categorize_module(pagename) | |||
-- Return | |||
if result == "" then | |||
return "" .. cats | |||
else | |||
return string.format(": ''%s''%s\n", result, cats) | |||
end | end | ||
end | end | ||
function | -- Helper function: categorize template | ||
local function categorize_template(pagename, has_invoke, is_metatemplate) | |||
local cats = "" | local cats = "" | ||
if pagename:match("/doc$") then | |||
if pagename: | cats = "[[Category:Template documentation]]" | ||
else | |||
cats = "[[Category:Templates]]" | |||
if has_invoke then | |||
cats = cats .. " [[Category:Lua-based templates]]" | |||
cats = "[[Category: | |||
end | end | ||
if is_metatemplate then | |||
cats = cats .. " [[Category:Metatemplates]]" | |||
if | |||
end | end | ||
end | end | ||
return cats | return cats | ||
end | end | ||
function | -- Helper function | ||
local | -- Creates an additional hatnote denoting which functions are invoked from which | ||
-- modules, if a has more than one invoke. | |||
local function make_invocation_hatnote(invokes) | |||
local hatnote = "" | |||
if #invokes == 0 then | |||
return "" | |||
else | |||
hatnote = "This template invokes the following functions:" | |||
local calls = {} | |||
for _, call in ipairs(invokes) do | |||
local module_link = iutils.make_module_link(call.module) | |||
table.insert(calls, string.format("'''%s''' from %s", call.func, module_link)) | |||
end | |||
return string.format("%s %s.", hatnote, table.concat(calls, ", ")) | |||
end | |||
end | |||
-- If header is | -- Helper function to handle templates | ||
if header == " | function p.make_template_hatnote(header, pagename, corr_module, detect_corr_page) | ||
-- Check whether corresponding module exists | |||
-- Then check whether this template invokes the module | |||
local has_module = iutils.page_exists("Module:" .. corr_module:gsub("/doc$", "")) | |||
-- Check whether to use detect_corr_page option | |||
-- If the header option is dualuse or metatemplate, this option is ignored. | |||
-- For any other option, it's not (although it also has no effect on | |||
-- noinvoke or library/metamodule). | |||
if header ~= "dualuse" and header ~= "metatemplate" then | |||
has_module = has_module and detect_corr_page | |||
end | end | ||
-- | -- Check for whether the template invokes that module, regardless of whether | ||
-- it uses the corresponding module. | |||
local wikitext = iutils.get_and_preprocess_content("Template", pagename:gsub("/doc$", "")) | |||
local invokes = iutils.find_invokes(wikitext) | |||
local is_module_invoked = iutils.invocation_exists(invokes, corr_module:gsub("/doc$", "")) | |||
-- Heading meanings and usage on templates | |||
-- - dualuse means a template is implement by a module, but its template may | |||
-- be bypassed by using its module directly from other modules. | |||
-- - metatemplate is a special case of dualuse; the template-module pair is | |||
-- for a metatemplate, a template used to build other templates. | |||
-- - noinvoke and metamodule/library don't have use for templates. | |||
-- Does the template have a module? | |||
-- - dualuse and metatemplate: YES (REQUIRED!!) | |||
-- - noinvoke, metamodule/library, and data/datamodule: option's don't apply | |||
-- to templates. | |||
-- RATIONALE FOR NOINVOKE NOT APPLYING: a template may invoke functions from | |||
-- more than one module, but one of them must be the "main" module. Since | |||
-- there's no way to autodetect that, that info must be manually entered. | |||
-- If that doesn't happen | |||
local result = "" | local result = "" | ||
if header == "dualuse" then | if header == "dualuse" then | ||
if | if has_module and is_module_invoked then | ||
result = string.format( | |||
"This template is implemented by the Lua module [[Module:%s]]. See its module page for Lua-based template implementation.", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
elseif has_module and not is_module_invoked then | |||
result = string.format( | |||
"This template has a corresponding Lua module [[Module:%s]], but does not invoke its functions.", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
else | else | ||
result = "This template | result = string.format( | ||
"This template is implemented by a module that is currently missing. [[Special:EditPage/Module:%s]]", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
end | end | ||
elseif header == "metatemplate" then | elseif header == "metatemplate" then | ||
if | if has_module and is_module_invoked then | ||
result = string.format( | |||
"This template is a metatemplate. It is used to build other templates and should not be used standalone, except for testing or simple usage. This template is implemented by the Lua module [[Module:%s]].", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
elseif has_module and not is_module_invoked then | |||
result = string.format( | |||
"This metatemplate has a corresponding Lua module [[Module:%s]], but does not invoke its functions.", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
else | else | ||
result = "This template | result = "This template is a metatemplate. It is used to build other templates and should not be used standalone, except for testing or simple usage." | ||
end | end | ||
elseif header == "noinvoke" then | elseif header == "noinvoke" or header == "library" or header == "metamodule" or header == "data" or header == "datamodule" then | ||
if | result = "This template has a header option in the wrong namespace. It should be used within the Module namespace." | ||
else | |||
if has_module and header ~= "" and is_module_invoked then | |||
result = string.format( | |||
"%s. This template is implemented by the Lua module [[Module:%s]].", | |||
header, | |||
corr_module:gsub("/doc$", "") | |||
) | |||
elseif has_module and header ~= "" and not is_module_invoked then | |||
result = string.format( | |||
"%s. This template has a corresponding Lua module [[Module:%s]], but does not invoke its functions.", | |||
header, | |||
corr_module:gsub("/doc$", "") | |||
) | |||
elseif has_module and header == "" and is_module_invoked then | |||
result = string.format( | |||
"This template is implemented by the Lua module [[Module:%s]].", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
elseif has_module and header == "" and not is_module_invoked then | |||
result = string.format( | |||
"This template has a corresponding Lua module [[Module:%s]], but does not invoke its functions.", | |||
corr_module:gsub("/doc$", "") | |||
) | |||
else | else | ||
result = | result = header | ||
end | end | ||
end | |||
-- Find all invocations for the template and add it as an addl hatnote. | |||
local invocation_hatnote = make_invocation_hatnote(invokes) | |||
-- Categorize | |||
local cats = categorize_template(pagename, #invokes > 0, header == "metatemplate") | |||
if header == "none" then | |||
return cats | |||
elseif result == "" and invocation_hatnote == "" then | |||
return "" | |||
elseif result == "" and invocation_hatnote ~= "" then | |||
return string.format(": ''%s''%s\n", invocation_hatnote, cats) | |||
elseif result ~= "" and invocation_hatnote == "" then | |||
return string.format(": ''%s''%s\n", result, cats) | |||
else | |||
return string.format(": ''%s''\n: ''%s'' %s\n", result, invocation_hatnote, cats) | |||
end | |||
end | |||
-- Main function | |||
function p._dochead(args) | |||
local namespace = args["namespace"] | |||
local pagename = args["pagename"] | |||
local header = args["header"] or "none" | |||
local corr_template = args["temp"] or pagename | |||
local corr_module = args["mod"] or pagename | |||
local detect_corr_page = args["detect_corr_page"] | |||
-- If header is none, skip everything | |||
if header == "none" then | |||
--return categorize(namespace, pagename) | |||
end | |||
-- Call appropriate hatnote-making function | |||
-- Remove /doc suffix if this is on a /doc page to ensure links are made | |||
-- properly. :gsub("/doc$", "") | |||
local result = "" | |||
if namespace == "Module" then | |||
result = p.make_module_hatnote(header, pagename, corr_template, detect_corr_page) | |||
elseif namespace == "Template" then | |||
result = p.make_template_hatnote(header, pagename, corr_module, detect_corr_page) | |||
else | else | ||
result = ": ''This documentation template is in the wrong namespace. It should be within the Template or Module namespaces.''\n" | |||
end | end | ||
return result | |||
return | |||
end | end | ||
-- Wrapper to be invoked | |||
function p.dochead(frame) | function p.dochead(frame) | ||
local args = getArgs(frame, { removeBlanks = true }) or {} | local args = getArgs(frame, { removeBlanks = true }) or {} | ||
| Line 152: | Line 323: | ||
args["namespace"] = args["namespace"] or title.nsText | args["namespace"] = args["namespace"] or title.nsText | ||
args["pagename" ] = args["pagename" ] or title.text | args["pagename" ] = args["pagename" ] or title.text | ||
args["header" | |||
-- Extract header or set it to defaults | |||
if args["header"] == nil or args["header"] == "" then | |||
if args["namespace"] == "Module" then | |||
args["header"] = "noinvoke" | |||
else | |||
args["header"] = "" | |||
end | |||
end | |||
-- Extract template/module names, or autogenerate them | -- Extract template/module names, or autogenerate them | ||
args["temp"] = args["temp"] or | args["temp"] = args["temp"] or args["pagename"] | ||
args[" | args["mod" ] = args["mod" ] or args["pagename"] | ||
-- Option to detect corresponding page (default true) | |||
args["detect_corr_page"] = args["detect_corr_page"] == nil and true or yesno(args["detect_corr_page"]) | |||
local result = p._dochead(args) | |||
-- Debugger option to show generated WikiText | |||
local wtext = yesno(frame.args["wtext"] or args["wtext"]) | |||
if wtext == true then | |||
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>" | |||
end | |||
return frame:preprocess(result) | |||
end | |||
function p.tester() | |||
local args = {} | |||
args["namespace"] = "Template" | |||
args["pagename" ] = "Module introspection" | |||
args["temp"] = args["pagename"] | |||
args["mod" ] = args["pagename"] | |||
args["header"] = "" | |||
return p._dochead(args) | return p._dochead(args) | ||
end | end | ||
return p | return p | ||