Module:Dochead: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
m remove test code
Ganaram inukshuk (talk | contribs)
autocategorize metatemplates
 
(48 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
-- Normalize corresponding template/module to include their namespaces
hatnote = "This template invokes the following functions:"
if not corr_template:match("^Template:") then
local calls = {}
corr_template = "Template:" .. corr_template
for _, call in ipairs(invokes) do
end
local module_link = iutils.make_module_link(call.module)
if not corr_module:match("^Module:") then
table.insert(calls, string.format("'''%s''' from %s", call.func, module_link))
corr_module = "Module:" .. corr_module
end
return string.format("%s %s.", hatnote, table.concat(calls, ", "))
end
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 = string.format("This module has a template that is currently missing. [[Special:EditPage/%s]]", corr_template)
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 template implementation.", corr_module)
else
result = string.format("This template is implemented by a module that is currently missing. [[Special:EditPage/%s]]", corr_module)
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 = string.format("This module implements a template that is currently missing. [[Special:EditPage/%s]]", corr_template)
)
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 or its settings 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 or its settings 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
elseif has_template and header == "" then
local corr_module  = args["mod"] or pagename
result = string.format("This module implements [[%s]].", corr_template)
local detect_corr_page = args["detect_corr_page"]
else
 
result = header
-- If header is none, skip everything
end
if header == "none" then
elseif namespace == "Template" then
--return categorize(namespace, pagename)
if has_module and header ~= ""  then
result = string.format("%s. This template is implemented by the Lua module [[%s]].", header, corr_module)
elseif has_module and header == "" then
result = string.format("This template is implemented by the Lua module [[%s]].", corr_module)
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 211: 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

Latest revision as of 23:01, 31 January 2026

Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:Dochead.
Module:Dochead is ready for use. This message indicates that a module is ready for use, or has recently been repaired. This message may be removed once this module has been used on several pages or once it is verified to work as intended.

Details: Functionally complete. Edge-case observations still ongoing. Links can now be viewed on /doc pages.

Introspection summary for Module:Dochead 
Functions provided (5)
Line Function Params
31 make_module_hatnote (header, pagename, corr_template, detect_corr_page)
170 make_template_hatnote (header, pagename, corr_module, detect_corr_page)
289 _dochead (main) (args)
318 dochead (invokable) (frame)
354 tester none
Lua modules required (3)
Variable Module Functions used
getArgs Module:Arguments getArgs
iutils Module:Introspection utils page_exists
get_and_preprocess_content
find_invokes
invocation_exists
make_module_link
yesno Module:Yesno yesno

No function descriptions were provided. The Lua code may have further information.


-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
local getArgs = require("Module:Arguments").getArgs
local iutils  = require("Module:Introspection utils")
local yesno   = require("Module:Yesno")

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
-- template or module documentation) and can autodetect and categorize:
-- - FOR MODULES:
--   - whether a module has an accompanying template (overridable/toggleable)
--   - whether a module is meant as a library for other modules
-- - FOR TEMPLATES:
--   - whether a template has an accompanying module (overridable/toggleable)
--   - 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.make_module_hatnote(header, pagename, corr_template, detect_corr_page)
	-- Check whether corresponding template exists
	-- 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 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
		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

-- 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
end

-- Helper function
-- 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

-- Helper function to handle templates
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
	
	-- 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 = ""
	if header == "dualuse" then
		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
			result = string.format(
				"This template is implemented by a module that is currently missing. [[Special:EditPage/Module:%s]]",
				corr_module:gsub("/doc$", "")
			)
		end

	elseif header == "metatemplate" then
		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
			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

	elseif header == "noinvoke" or header == "library" or header == "metamodule" or header == "data" or header == "datamodule" then
		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
			result = header
		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
		result = ": ''This documentation template is in the wrong namespace. It should be within the Template or Module namespaces.''\n"
	end

	return result
end

-- Wrapper to be invoked
function p.dochead(frame)
	local args = getArgs(frame, { removeBlanks = true }) or {}
	local title = mw.title.getCurrentTitle()

	-- Extract or parse args
	args["namespace"] = args["namespace"] or title.nsText
	args["pagename" ] = args["pagename" ] or title.text

	-- 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
	args["temp"] = args["temp"] or 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)
end

return p