Module:Module introspection

Revision as of 21:07, 6 November 2025 by Ganaram inukshuk (talk | contribs) (for consistency, "debug" should be used for toggling categories; either rename this or use special:expandtemplates)
Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:Module introspection.
Module:Module introspection 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: Edge-case observation still ongoing. Currently cannot detect data provided by a module.

Introspection summary for Module:Module introspection 
Functions provided (4)
Line Function Params
9 make_dependency_table (module_deps)
49 make_function_table (module_name, module_funcs, descriptions, main_function)
127 _module_introspection (main) (args)
186 module_introspection (invokable) (frame)
Lua modules required (2)
Variable Module Functions used
getArgs Module:Arguments getArgs
iutils Module:Introspection utils get_and_preprocess_content
find_dependencies
find_functions

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 p = {}

-- Helper function
-- Given the output of the above function, create a mediawiki table
function p.make_dependency_table(module_deps)

	-- Table headers
	local lines = {}
	table.insert(lines, '{| class="wikitable sortable"')
	table.insert(lines, "|+ style=\"font-size: 105%;\" | Lua modules required " .. string.format("(%d)", #module_deps))
	table.insert(lines, "|-")
	table.insert(lines, "! Variable")
	table.insert(lines, "! Module")
	table.insert(lines, "! Functions used")

	-- Table rows (assuming they're all alphabetized)
	for _, info in ipairs(module_deps) do
		local funcs_text
		if #info.funcs == 0 then
			funcs_text = "''dependency not used''"
		else
			local func_lines = {}
			for _, func in ipairs(info.funcs) do
				table.insert(func_lines, string.format("<code>%s</code>", func))
			end
			funcs_text = table.concat(func_lines, "<br />")
		end

		table.insert(lines, "|-")
		table.insert(lines, "| " .. info.var)
		table.insert(lines, "| [[" .. info.dep .. "]]")
		table.insert(lines, "| " .. funcs_text)
	end

	-- Table footer
	table.insert(lines, "|}")

	-- Join all lines into a single string
	return table.concat(lines, "\n")
end

-- Helper function
-- Lists module's own functions; requires module name to produce links to each
-- function.
function p.make_function_table(module_name, module_funcs, descriptions, main_function)
	
	-- Check whether descriptions was passed in
	local has_descriptions = false
	if type(descriptions) == "table" then
		for _, _ in pairs(descriptions) do
			has_descriptions = true
			break
		end
	end
	
	-- Table headers
	local lines = {}
	--table.insert(lines, string.format("'''Module:%s''' provides %d function(s):", module_name, #module_funcs))
	table.insert(lines, '{| class="wikitable sortable"')
	table.insert(lines, "|+ style=\"font-size: 105%;\" | Functions&nbsp;provided&nbsp;" .. string.format("(%d)", #module_funcs))
	table.insert(lines, "|-")
	table.insert(lines, "! Line")
	table.insert(lines, "! Function")
	table.insert(lines, "! Params")
	
	-- If there are descriptions, add a column for that
	if has_descriptions then
		table.insert(lines, "! Description")
	end

	-- Table rows
	for _, info in ipairs(module_funcs) do
		
		-- Find params for that function, or say "none" if none
		local params = {}
		for _, param in ipairs(info.params) do
			table.insert(params, param)	
		end
		local params_string = ""
		if #params == 0 then
			params_string = "''none''"
		else
			params_string = string.format("<code>(%s)</code>", table.concat(params, ", "))
		end
		
		-- Create link to line for that function
		local line_num = string.format("[[Module:%s#L-%d|%d]]", module_name, info.line, info.line)
		
		-- Create text for function
		local func = ""
		if info.name then
			func = string.format("<code>%s</code>", info.name)
		else
			func = "''none''"
		end
		
		-- If the function is the main function, add "main" to that cell
		if info.name == main_function then
			func = func .. " '''(main)'''"
		end
		
		-- If the function is invokable (it has one param called "frame"), add
		-- "invokable" to that cell
		if #params == 1 and params[1] == "frame" then
			func = func .. " '''(invokable)'''"
		end
		
		table.insert(lines, "|-")
		table.insert(lines, "| " .. line_num)
		table.insert(lines, "| " .. func)
		table.insert(lines, "| " .. params_string)
		
		if has_descriptions then
			table.insert(lines, "| " .. (descriptions[info.name] or ""))
		end
	end
	table.insert(lines, "|}")
	
	return table.concat(lines, "\n")
end

-- Main function; to be called by wrapper
function p._module_introspection(args)
	local args = args or {}
	local module_name   = args["module_name"  ]
	local main_function = args["main_function"]
	local descriptions  = args["descriptions" ]
	local is_doc = args["is_doc"]
	
	-- Check whether this page is a docpage
	-- If so, don't bother
	if is_doc then
		return "''To see introspection summary, see this module's main page.''"
	end
	
	-- Preprocess module and blank-out comments
	--local title = mw.title.new('Module:' .. module_name)
	--local code = title:getContent()
	--code = iutils.preprocess_code(code)		-- Blank-out comments
	local code = iutils.get_and_preprocess_content("Module", module_name)
	
	-- Get dependencies and their functions used, then build a table
	local module_deps = iutils.find_dependencies(code)
	local dep_table = p.make_dependency_table(module_deps)

	-- Get module's functions, then build a table using that information
	local module_funcs = iutils.find_functions(code)
	local func_table = p.make_function_table(module_name, module_funcs, descriptions, main_function)

	-- Return the tables
	-- Styling may be improved at a later time
	local lines = {
		'{| class="wikitable mw-collapsible"',
		'|-',
		'! colspan="2" style="font-size: 105%;" | Introspection summary for Module:' .. module_name .. "&nbsp;",
		"|-",
		'| style="vertical-align: top; border-right: none;"; |',
		func_table,
		'| style="vertical-align: top; border-left: none;" |',
		dep_table,
		"|}"
	}

	-- Check whether descriptions was passed in
	local has_descriptions = false
	if type(descriptions) == "table" then
		for _, _ in pairs(descriptions) do
			has_descriptions = true
			break
		end
	end
	
	-- If no descriptions were provided, add text below that points to the code.
	if not has_descriptions then
		table.insert(lines, "''No function descriptions were provided. The Lua code may have further information.''")	
	end

	return table.concat(lines, "\n")
end

-- Wrapper function for modules
function p.module_introspection(frame)
	-- Extract arguments using getArgs
	local args = getArgs(frame) or {}

	-- Get module name from arguments, or default to current page
	local module_name = args["module_name"] or mw.title.getCurrentTitle().text

	-- Strip trailing "/doc" if the template is used on a documentation subpage
	--module_name = module_name:gsub("/doc$", "")
	
	-- Check whether page is the doc page
	-- To ensure proper referencing, do not display introspection on a docpage.
	local is_doc = string.find(module_name, "/doc$")
	
	-- Normalize module name so it can be used to find the main function, which
	-- is assumed to be the same name as the module. Module assumes snake_case
	-- is used for function names. (If this fails, it can be entered manually.)
	local normalized_name = module_name:gsub("[^%w]", "_"):lower()
	local main_function = args["main_function"] or "_" .. normalized_name
	
	-- Process all function descriptions, if provided as desc_function_name
	local func_descriptions = {}
	for key, val in pairs(args) do
		local func_name = key:match("^desc_([%w_]+)$")
		if func_name and val and val ~= "" then
			func_descriptions[func_name] = val
		end
	end

	-- Return
	local result = p._module_introspection({
		["module_name"  ] = module_name,
		["main_function"] = main_function,
		["descriptions" ] = func_descriptions,
		["is_doc"] = is_doc
	})
	
	return frame:preprocess(result)
end

return p