Module:Dochead: Difference between revisions

Ganaram inukshuk (talk | contribs)
m comments
Ganaram inukshuk (talk | contribs)
autocategorize metatemplates
 
(53 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


-- Options for specific types of templates and modules include:
-- Helper function: categorize modules
-- - (TEMPLATES) whether it's a metatemplate
local function categorize_module(pagename)
-- - (MODULES) whether it's a metamodule (used the same way as a metatemplate,
local cats = ""
--  but direct use of module code is allowed)
if pagename:match("/doc$") then
cats = "[[Category:Module documentation]]"
else
cats = "[[Category:Lua modules]]"
end
return cats
end


-- Helper function
-- Helper function to handle modules
-- Detect whether a page exists, where fullpagename is "Namespace:Title"
function p.make_module_hatnote(header, pagename, corr_template, detect_corr_page)
local function page_exists(fullpagename)
-- Check whether corresponding template exists
local title = mw.title.new(fullpagename)
-- Then check whether that template invokes the module
local has_template = iutils.page_exists("Template:" .. corr_template:gsub("/doc$", ""))
-- 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 title and title.exists then
-- If the module has a template, check for whether it invokes it.
return true
if has_template then
else
local wikitext = iutils.get_and_preprocess_content("Template", corr_template:gsub("/doc$", ""))
return false
local invokes = iutils.find_invokes(wikitext)
has_template = has_template and iutils.invocation_exists(invokes, pagename:gsub("/doc$", ""))
end
end
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


-- Helper function
elseif header == "metatemplate" then
-- Categorizes pages if they're in the right namespace, and based on the nature
if has_template then
-- of the page
result = string.format(
local function categorize(namespace, pagename, is_lua_based_template)
"This module implements a metatemplate, and may be invoked by templates using its corresponding template [[Template:%s]], or used directly from other modules.",
local is_lua_based_template = is_lua_based_template or false
corr_template:gsub("/doc$", "")
local cats = ""
)
else
result = "This module is used to create other Lua-based templates and has no corresponding template."
end


if pagename:sub(-4) == "/doc" then
elseif header == "noinvoke" then
-- Documentation subpages
if has_template then
if namespace == "Template" then
result = string.format(
cats = "[[Category:Template documentation]]"
"This module should not be invoked directly; use its corresponding template instead: [[Template:%s]].",
elseif namespace == "Module" then
corr_template:gsub("/doc$", "")
cats = "[[Category:Module documentation]]"
)
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
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
-- Main pages
if has_template and header ~= "" then
if namespace == "Template" then
result = string.format("%s This module implements [[Template:%s]].", header, corr_template:gsub("/doc$", ""))
cats = "[[Category:Templates]]"
elseif has_template and header == "" then
if is_lua_based_template then
result = string.format("This module implements [[Template:%s]].", corr_template:gsub("/doc$", ""))
cats = cats .. " [[Category:Lua-based templates]]"
end
elseif namespace == "Module" then
cats = "[[Category:Lua modules]]"
else
else
--cats = "[[Category:Templates in the incorrect namespace]]"
result = header
end
end
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


-- Helper function: categorize template
local function categorize_template(pagename, has_invoke, is_metatemplate)
local cats = ""
if pagename:match("/doc$") then
cats = "[[Category:Template documentation]]"
else
cats = "[[Category:Templates]]"
if has_invoke then
cats = cats .. " [[Category:Lua-based templates]]"
end
if is_metatemplate then
cats = cats .. " [[Category:Metatemplates]]"
end
end
return cats
return cats
end
end


-- Main function
-- Helper function
function p._dochead(args)
-- Creates an additional hatnote denoting which functions are invoked from which
local namespace = args["namespace"]
-- modules, if a has more than one invoke.
local pagename  = args["pagename"]
local function make_invocation_hatnote(invokes)
local header    = args["header"] or "none"
local hatnote = ""
local corr_template = args["temp"] or ("Template:" .. pagename)
if #invokes == 0 then
local corr_module  = args["mod"]  or ("Module:"   .. pagename)
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 none, don't bother
-- Helper function to handle templates
if header == "none" then
function p.make_template_hatnote(header, pagename, corr_module, detect_corr_page)
return categorize(namespace, pagename)
-- 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
-- Remove doc subpage if present
-- Check for whether the template invokes that module, regardless of whether
corr_template = corr_template:gsub("/doc$", "")
-- it uses the corresponding module.
corr_module  = corr_module :gsub("/doc$", "")
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 = ""
local has_template = page_exists(corr_template)
local has_module  = page_exists(corr_module)
-- Handle each header type first, then namespace, then if applicable,
-- presence of a corresponding template or module.
if header == "dualuse" then
if header == "dualuse" then
-- A dual-use module MUST have a corresponding template; if not, say
if has_module and is_module_invoked then
-- it's missing.
result = string.format(
-- A dual-use template MUST link to its corresponding module; if not,
"This template is implemented by the Lua module [[Module:%s]]. See its module page for Lua-based template implementation.",
-- say it's missing (even though it wouldn't work without its module).
corr_module:gsub("/doc$", "")
if namespace == "Module" then
)
if has_template then
elseif has_module and not is_module_invoked then
result = string.format("This module may be invoked by templates using its corresponding [[%s|template]], or used directly from other modules.", corr_template)
result = string.format(
else
"This template has a corresponding Lua module [[Module:%s]], but does not invoke its functions.",
result = "This module has a template that is currently missing. [[create]]"
corr_module:gsub("/doc$", "")
end
)
elseif namespace == "Template" then
if has_module then
result = string.format("This template is implemented by the Lua module [[%s]]. See its module page for Lua-based implementation.", corr_module)
else
result = "This template is implemented by a module that is currently missing. [[create]]"
end
else
else
result = "This template is in the wrong namespace. It should be within the Template or Module namespaces."
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
-- A metatemplate-implementing module does not necessarly need to have a
if has_module and is_module_invoked then
-- template (as infobox was this way before).
result = string.format(
-- A metatemplate does not need to be made using Lua.
"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]].",
if namespace == "Module" then
corr_module:gsub("/doc$", "")
if has_template then
)
result = string.format("This module implements a metatemplate, and may be invoked by templates using its corresponding [[%s|template]], or used directly from other modules.", corr_template)
elseif has_module and not is_module_invoked then
else
result = string.format(
result = "This module is used to create other Lua-based templates and has no corresponding template."
"This metatemplate has a corresponding Lua module [[Module:%s]], but does not invoke its functions.",
end
corr_module:gsub("/doc$", "")
elseif namespace == "Template" then
)
if has_module 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 [[%s]].", corr_module)
else
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
else
else
result = "This template is in the wrong namespace. It should be within the Template or Module namespaces."
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
-- A noinvoke module MUST have a corresponding template. Since this is
result = "This template has a header option in the wrong namespace. It should be used within the Module namespace."
-- the default for modules, it should say it's missing a template if it
 
-- doesn't exist.
else
-- This type doesn't work on templates, as those are non-Lua templates.
if has_module and header ~= "" and is_module_invoked then
if namespace == "Module" then
result = string.format(
if has_template then
"%s. This template is implemented by the Lua module [[Module:%s]].",
result = string.format("This module should not be invoked directly; use its corresponding [[%s|template]] instead.", corr_template)
header,
else
corr_module:gsub("/doc$", "")
result = "This module implements a template that is currently missing. [[create]]"
)
end
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 = "This template is in the wrong namespace. It should be used within the Module namespace."
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


elseif header == "library" or header == "metamodule" then
-- A metamodule should NOT have a corresponding template. This type only
-- works on modules.
if namespace == "Module" then
result = "This module primarily serves as a library for other modules and has no corresponding template."
else
result = "This template is in the wrong namespace. It should be used within the Module namespace."
end


else
-- Main function
-- For any other case, just say that the module implements a template,
function p._dochead(args)
-- or a template invokes a module's function.  
local namespace = args["namespace"]
if namespace == "Module" then
local pagename  = args["pagename"]
if has_template and header ~= "" then
local header    = args["header"] or "none"
result = string.format("%s. This module implements [[%s]].", header, corr_template)
local corr_template = args["temp"] or pagename
else
local corr_module  = args["mod"] or pagename
result = header
local detect_corr_page = args["detect_corr_page"]
end
 
elseif namespace == "Template" then
-- If header is none, skip everything
if has_module and header ~= ""  then
if header == "none" then
result = string.format("%s. This template is implemented by the Lua module [[%s]].", header, corr_module)
--return categorize(namespace, pagename)
else
result = header
end
else
result = "This template is in the wrong namespace. It should be within the Template or Module namespaces."
end
end
end


-- Append categorization info to hatnote
-- Call appropriate hatnote-making function
-- Check whether hatnote is blank; if so, don't bother with hatnote, just
-- Remove /doc suffix if this is on a /doc page to ensure links are made
-- output categories
-- properly. :gsub("/doc$", "")
local has_link = namespace == "Template" and has_module
local result = ""
if result == "" then
if namespace == "Module" then
return categorize(namespace, pagename, has_link)
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
return string.format(":''%s'' %s\n", result, categorize(namespace, pagename, has_link))
result = ": ''This documentation template is in the wrong namespace. It should be within the Template or Module namespaces.''\n"
end
end
return result
end
end


Line 199: Line 334:
-- Extract template/module names, or autogenerate them
-- Extract template/module names, or autogenerate them
args["temp"] = args["temp"] or ("Template:" .. args["pagename"])
args["temp"] = args["temp"] or args["pagename"]
args["mod" ] = args["mod" ] or ("Module:"   .. args["pagename"])
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