Module:Module introspection: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
m forgot to remove bugfixing additions
ArrowHead294 (talk | contribs)
mNo edit summary
 
(114 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 = {}
-- Inspects a module for its functions, its dependencies, and the functions used
-- from those dependencies.


-- Helper function
-- Helper function
-- Blanks comments but preserves line numbers
-- Given the output of the above function, create a mediawiki table
function p.strip_comments(content)
function p.make_dependency_table(module_deps)
if not content then return "" end


-- Blank single-line comments
-- Table headers
content = content:gsub("%-%-[^\n]*", function(s)
local lines = {}
return string.rep(" ", #s)
table.insert(lines, '{| class="wikitable sortable"')
end)
table.insert(lines, "|+ style=\"font-size: 105%;\" | " .. string.format("Lua modules required (%d)", #module_deps))
table.insert(lines, "|-")
table.insert(lines, "! Variable")
table.insert(lines, "! Module")
table.insert(lines, "! Functions used")


-- Blank multi-line comments (handles --[[...]] and --[=[...]=])
-- Table rows (assuming they're all alphabetized)
content = content:gsub("%-%-%[%[.-%]%]", function(s)
for _, info in ipairs(module_deps) do
local blank = s:gsub("[^\n]", " ")
local funcs_text
return blank
if #info.funcs == 0 then
end)
funcs_text = "''dependency not used''"
content = content:gsub("%-%-%[%=[=]*%[.-%][=]*%]", function(s)
else
local blank = s:gsub("[^\n]", " ")
local func_lines = {}
return blank
for _, func in ipairs(info.funcs) do
end)
table.insert(func_lines, string.format("<code>%s</code>", func))
end
funcs_text = table.concat(func_lines, "<br />")
end


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


-- Helper function
-- Table footer
-- List dependencies for a module
table.insert(lines, "|}")
function p.list_dependencies(module_name)
local module_name = module_name --or "MOS" -- Test arg; comment out when not testing
local title = mw.title.new('Module:' .. module_name)
local content = title:getContent()
-- Blank-out comments
content = p.strip_comments(content)


-- Get dependencies for that module
-- Join all lines into a single string
-- It's the text from each "require('Module:aaaaa')" line.
return table.concat(lines, "\n")
local deps = {}
for dep in content:gmatch("require%s*%(%s*['\"]Module:(.-)['\"]%s*%)") do
table.insert(deps, dep)
end
return deps
end
end


-- Helper function
-- Helper function
-- List functions used from each dependency
-- Lists module's own functions; requires module name to produce links to each
-- This assumes dependency functions are used in the format of: var.func(),
-- function.
-- or var() if the module is included as var = require("Module:Dep") if it
function p.make_function_table(module_name, module_funcs, descriptions, main_function)
-- returns a function or var = require("Module:Dep").func_name if a specific
-- function from that module is used.
-- Optionally accepts a table of its dependencies; if not provided, it will find
-- them itself by calling list_dependencies
function p.list_dependency_functions(module_name, deps)
local module_name = module_name --or "MOS" -- Test arg; comment out when not testing
 
-- Get dependencies
local deps = p.list_dependencies(module_name)
-- Load module
-- Check whether descriptions was passed in
local title = mw.title.new('Module:' .. module_name)
local has_descriptions = false
local content = title:getContent()
if type(descriptions) == "table" then
 
for _, _ in pairs(descriptions) do
-- Step 1: Find all require statements with optional .function
has_descriptions = true
local results = {}
break
for var, dep, entry in content:gmatch(
"local%s+([%w_]+)%s*=%s*require%s*%(%s*['\"]Module:(.-)['\"]%s*%)%.?([%w_]*)"
) do
local used = {}
 
-- Step 2: Check how it's used
if entry ~= "" then
-- The module returned a single function: var() usage
for _ in content:gmatch(var .. "%s*%(") do
used[entry] = true
end
else
-- The variable is a module table: var.func() usage
for func in content:gmatch(var .. "%.(%w+)%s*%(") do
used[func] = true
end
end
end
-- Step 3: Build result entry
local funcList = {}
for f in pairs(used) do table.insert(funcList, f) end
table.sort(funcList)
results[dep] = results[dep] or {}
table.insert(results[dep], {
variable = var,
entry = (entry ~= "" and entry or nil),
functions = funcList
})
end
end
return results
end
-- Helper function
-- List functions provided by a module
-- Only returned functions are considered, not local functions
function p.list_functions(module_name)
local module_name = module_name --or "MOS" -- Test arg; comment out when not testing
-- Load module
-- Table headers
local title = mw.title.new('Module:' .. module_name)
local lines = {}
local content = title:getContent()
--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%;\" | " .. string.format("Functions&nbsp;provided&nbsp;(%d)", #module_funcs))
table.insert(lines, "|-")
table.insert(lines, "! Line")
table.insert(lines, "! Function")
table.insert(lines, "! Params")
-- Blank-out comments
-- If there are descriptions, add a column for that
content = p.strip_comments(content)
if has_descriptions then
table.insert(lines, "! Description")
end


-- Iterate through file and find each function and the line found at
-- Table rows
local funcs = {}
for _, info in ipairs(module_funcs) do
if content then
local lineNumber = 0
-- Find params for that function, or say "none" if none
for line in content:gmatch("[^\n]+") do
local params = {}
lineNumber = lineNumber + 1
for _, param in ipairs(info.params) do
 
table.insert(params, param)
-- Match functions defined as function p.name(
end
local name = line:match("function%s+[%w_]+%.([%w_]+)%s*%(")
local params_string = ""
if name then
if #params == 0 then
table.insert(funcs, {name = name, line = lineNumber})
params_string = "''none''"
end
else
 
params_string = string.format("<code>(%s)</code>", table.concat(params, ", "))
-- Match functions defined as p.name = function(
end
name = line:match("[%w_]+%.([%w_]+)%s*=%s*function%s*%(")
if name then
-- Create link to line for that function
table.insert(funcs, {name = name, line = lineNumber})
local line_num = string.format("[[Module:%s#L-%d|%d]]", module_name, info.line, info.line)
end
-- 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
end
end
 
table.insert(lines, "|}")
return funcs
return table.concat(lines, "\n")
end
end


-- Main function; to be called by wrapper
-- Main function; to be called by wrapper
function p._module_introspection(args)
function p._module_introspection(args)
local args = args or {}
local module_name  = args["module_name"  ]
local module_name  = args["module_name"  ]
local main_function = args["main_function"]
local main_function = args["main_function"]
 
local descriptions  = args["descriptions" ]
    -- Get module functions
local is_doc = args["is_doc"]
    local module_functions = p.list_functions(module_name)
 
-- Check whether this page is a docpage
    -- Get dependencies and functions used from each
-- If so, don't bother
    local dep_functions = p.list_dependency_functions(module_name)
if is_doc then
 
return "''To see introspection summary, see this module's main page.''"
    -- Build MediaWiki table for dependencies
    local dep_lines = {}
table.insert(dep_lines, '{| class="wikitable sortable"')
table.insert(dep_lines, '|+ Dependencies and functions used')
table.insert(dep_lines, "! Dependency")
table.insert(dep_lines, "! Variable")
table.insert(dep_lines, "! Function(s) used")
 
    -- Include all dependencies even if no usage is detected
local all_deps = p.list_dependencies(module_name)
for _, dep in ipairs(all_deps) do
    local dep_link = string.format("[[Module: %s]]", dep)
    local usages = dep_functions[dep]
    if usages and #usages > 0 then
        for _, usage in ipairs(usages) do
            local func_str = table.concat(usage.functions, ", ")
            if usage.entry then
                func_str = usage.entry .. (func_str ~= "" and (", " .. func_str) or "")
            end
            table.insert(dep_lines, "|-")
            table.insert(dep_lines, "| " .. dep_link)
            table.insert(dep_lines, "| " .. usage.variable)
            table.insert(dep_lines, "| " .. (func_str ~= "" and func_str or "''dependency not used''"))
        end
    else
        table.insert(dep_lines, "|-")
        table.insert(dep_lines, "| " .. dep_link)
        table.insert(dep_lines, "| -")
        table.insert(dep_lines, "| ''dependency not used''")
    end
end
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)


    table.insert(dep_lines, '|}')
-- 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)


    -- Build MediaWiki table for module's own functions
-- Return the tables
local func_lines = {}
-- Styling may be improved at a later time
local func_class = "wikitable sortable mw-collapsible"
local lines = {
if #module_functions > 20 then
'{| class="wikitable mw-collapsible"',
    func_class = func_class .. " mw-collapsed"
'|-',
end
'! colspan="2" style="font-size: 105%;" | ' .. string.format("Introspection summary for Module:%s&nbsp;", module_name),
table.insert(func_lines, "{| class=\"" .. func_class .. "\"")
"|-",
table.insert(func_lines, "|+ Functions provided by this module")
'| style="vertical-align: top; border-right: none;" | ',
table.insert(func_lines, "! Function")
func_table,
table.insert(func_lines, "! Line")
'| style="vertical-align: top; border-left: none;" | ',
dep_table,
"|}"
}


    for _, f in ipairs(module_functions) do
-- Check whether descriptions was passed in
        local link = string.format("[[Module:%s#L-%d|%s]]", module_name, f.line, f.name)
local has_descriptions = false
       
if type(descriptions) == "table" then
-- If the function is the main function, add "main" to that cell
for _, _ in pairs(descriptions) do
if f.name == main_function then
has_descriptions = true
link = link .. " '''(main)'''"
break
end
end
       
        table.insert(func_lines, "|-")
        table.insert(func_lines, "| " .. link)
        table.insert(func_lines, "| " .. f.line)
end
end
table.insert(func_lines, "|}")
-- 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 the tables as strings
return table.concat(lines, "\n")
return table.concat(dep_lines, "\n"), table.concat(func_lines, "\n")
end
end


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


-- Get module name from arguments, or default to current page
-- Get module name from arguments, or default to current page
Line 224: Line 193:


-- Strip trailing "/doc" if the template is used on a documentation subpage
-- Strip trailing "/doc" if the template is used on a documentation subpage
module_name = module_name:gsub("/doc$", "")
--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
-- Normalize module name so it can be used to find the main function, which
Line 232: Line 205:
local main_function = args["main_function"] or "_" .. normalized_name
local main_function = args["main_function"] or "_" .. normalized_name
-- Call the introspection function
-- Process all function descriptions, if provided as desc_function_name
local dep_table, func_table = p._module_introspection({
local func_descriptions = {}
["module_name"]   = module_name,
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,
["main_function"] = main_function,
["descriptions" ] = func_descriptions,
["is_doc"] = is_doc
})
})


-- Return combined tables
    -- Debugger option to show generated WikiText
return dep_table .. "\n" .. func_table .. "\n"
    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
end


return p
return p

Latest revision as of 20:23, 3 December 2025

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
10 make_dependency_table (module_deps)
50 make_function_table (module_name, module_funcs, descriptions, main_function)
128 _module_introspection (main) (args)
187 module_introspection (invokable) (frame)
Lua modules required (3)
Variable Module Functions used
getArgs Module:Arguments getArgs
iutils Module:Introspection utils get_and_preprocess_content
find_dependencies
find_functions
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 = {}

-- 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%;\" | " .. string.format("Lua&nbsp;modules&nbsp;required&nbsp;(%d)", #module_deps))
	table.insert(lines, "|-")
	table.insert(lines, "! Variable")
	table.insert(lines, "! Module")
	table.insert(lines, "! Functions&nbsp;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%;\" | " .. string.format("Functions&nbsp;provided&nbsp;(%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%;" | ' .. string.format("Introspection summary for Module:%s&nbsp;", module_name),
		"|-",
		'| 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
	})

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

return p