Module:Module introspection: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
add table titles and autocollapse for functions table
Ganaram inukshuk (talk | contribs)
link to dependencies
Line 127: Line 127:


     -- Include all dependencies even if no usage is detected
     -- Include all dependencies even if no usage is detected
    local all_deps = p.list_dependencies(module_name)
for _, dep in ipairs(all_deps) do
    for _, dep in ipairs(all_deps) do
    local dep_link = string.format("[[Module:%s]]", dep)
        local usages = dep_functions[dep]
    local usages = dep_functions[dep]
        if usages and #usages > 0 then
    if usages and #usages > 0 then
            for _, usage in ipairs(usages) do
        for _, usage in ipairs(usages) do
                local func_str = table.concat(usage.functions, ", ")
            local func_str = table.concat(usage.functions, ", ")
                if usage.entry then
            if usage.entry then
                    func_str = usage.entry .. (func_str ~= "" and (", " .. func_str) or "")
                func_str = usage.entry .. (func_str ~= "" and (", " .. func_str) or "")
                end
            end
                table.insert(dep_lines, "|-")
            table.insert(dep_lines, "|-")
                table.insert(dep_lines, "| " .. dep)
            table.insert(dep_lines, "| " .. dep_link)
                table.insert(dep_lines, "| " .. usage.variable)
            table.insert(dep_lines, "| " .. usage.variable)
                table.insert(dep_lines, "| ".. (func_str ~= "" and func_str or "''dependency not used''"))
            table.insert(dep_lines, "| " .. (func_str ~= "" and func_str or "''dependency not used''"))
            end
        end
        else
    else
            -- No functions used from this dependency
        table.insert(dep_lines, "|-")
            table.insert(dep_lines, "|-")
        table.insert(dep_lines, "| " .. dep_link)
            table.insert(dep_lines, "| " .. dep)
        table.insert(dep_lines, "| -")
            table.insert(dep_lines, "| -")
        table.insert(dep_lines, "| ''dependency not used''")
            table.insert(dep_lines, "| ''dependency not used''")
    end
        end
end
    end


     table.insert(dep_lines, '|}')
     table.insert(dep_lines, '|}')

Revision as of 04:07, 25 October 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.

Lua error in ...ribunto/includes/Engines/LuaCommon/lualib/mwInit.lua at line 23: bad argument #1 to 'old_ipairs' (table expected, got nil).


-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
local getArgs = require("Module:Arguments").getArgs
local p = {}

-- Inspects a module for its functions, its dependencies, and the functions used
-- from those dependencies.

-- List dependencies for a module
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()

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

-- List functions used from each dependency
-- This assumes dependency functions are used in the format of: var.func(),
-- or var() if the module is included as var = require("Module:Dep") if it
-- 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
	local title = mw.title.new('Module:' .. module_name)
	local content = title:getContent()

	-- Step 1: Find all require statements with optional .function
	local results = {}
	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

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

	return results
end

-- 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
	local title = mw.title.new('Module:' .. module_name)
	local content = title:getContent()
	
	-- Iterate through file and find each function and the line found at
	local funcs = {}
	if content then
		local lineNumber = 0
		for line in content:gmatch("[^\n]+") do
			lineNumber = lineNumber + 1

			-- Match functions defined as function p.name(
			local name = line:match("function%s+[%w_]+%.([%w_]+)%s*%(")
			if name then
				table.insert(funcs, {name = name, line = lineNumber})
			end

			-- Match functions defined as p.name = function(
			name = line:match("[%w_]+%.([%w_]+)%s*=%s*function%s*%(")
			if name then
				table.insert(funcs, {name = name, line = lineNumber})
			end
		end
	end

	return funcs
end

-- Main function; to be called by wrapper
function p._module_introspection(args)
	local module_name = args["module_name"]

    -- Get module functions
    local module_functions = p.list_functions(module_name)

    -- Get dependencies and functions used from each
    local dep_functions = p.list_dependency_functions(module_name)

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

    table.insert(dep_lines, '|}')

    -- Build MediaWiki table for module's own functions
	local func_lines = {}
	local func_class = "wikitable sortable"
	if #module_functions > 20 then
	    func_class = func_class .. " collapsible autocollapse"
	end
	table.insert(func_lines, "{| class=\"" .. func_class .. "\"")
	table.insert(func_lines, "|+ Functions provided by this module")
	table.insert(func_lines, "! Function")
	table.insert(func_lines, "! Line")

    for _, f in ipairs(module_functions) do
        local link = string.format("[[Module:%s#%d|%s]]", module_name, f.line, f.name)
        table.insert(func_lines, "|-")
        table.insert(func_lines, "| " .. link)
        table.insert(func_lines, "| " .. f.line)
    end
    table.insert(func_lines, "|}")

    -- Return the tables as strings
    return table.concat(dep_lines, "\n"), table.concat(func_lines, "\n")
end


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

    -- 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$", "")

    -- Call the introspection function
    local dep_table, func_table = p._module_introspection({["module_name"] = module_name})

    -- Return combined tables
    return dep_table .. '\n' .. func_table
end


return p