Module:Module introspection: Difference between revisions
Jump to navigation
Jump to search
refactor so main function preprocesses module's code and passes it to all necessary helper functions, then uses that info to build tables (which are now done by helper functions) |
mNo edit summary |
||
| Line 132: | Line 132: | ||
-- Helper function; builds the table of dependencies | -- Helper function; builds the table of dependencies | ||
-- All dependencies are included regardless of use | -- All dependencies are included regardless of use | ||
function p.make_dependency_table(deps, dep_functions) | function p.make_dependency_table(module_name, deps, dep_functions) | ||
local dep_lines = {} | local dep_lines = {} | ||
table.insert(dep_lines, string.format("'''Module:%s''' requires the following dependencies:", module_name)) | |||
table.insert(dep_lines, '{| class="wikitable sortable"') | table.insert(dep_lines, '{| class="wikitable sortable"') | ||
table.insert(dep_lines, '|+ Dependencies and functions used') | --table.insert(dep_lines, '|+ Dependencies and functions used') | ||
table.insert(dep_lines, "! Dependency") | table.insert(dep_lines, "! Dependency") | ||
table.insert(dep_lines, "! Variable") | table.insert(dep_lines, "! Variable") | ||
| Line 172: | Line 173: | ||
function p.make_function_table(module_name, module_functions) | function p.make_function_table(module_name, module_functions) | ||
-- Collapse table if it's larger than 20 lines | |||
local func_class = "wikitable sortable mw-collapsible" | local func_class = "wikitable sortable mw-collapsible" | ||
if #module_functions > 20 then | if #module_functions > 20 then | ||
func_class = func_class .. " mw-collapsed" | func_class = func_class .. " mw-collapsed" | ||
end | end | ||
local func_lines = {} | |||
table.insert(func_lines, string.format("'''Module:%s''' provides %d function(s):", module_name, #module_functions)) | |||
table.insert(func_lines, "{| class=\"" .. func_class .. "\"") | table.insert(func_lines, "{| class=\"" .. func_class .. "\"") | ||
table.insert(func_lines, "|+ Functions provided by this module") | --table.insert(func_lines, "|+ Functions provided by this module") | ||
table.insert(func_lines, "! Function") | table.insert(func_lines, "! Function") | ||
table.insert(func_lines, "! Line") | table.insert(func_lines, "! Line") | ||
| Line 214: | Line 217: | ||
local deps = p.find_dependencies(preprocessed_content) | local deps = p.find_dependencies(preprocessed_content) | ||
local dep_functions = p.find_dependency_functions(preprocessed_content) | local dep_functions = p.find_dependency_functions(preprocessed_content) | ||
local dep_lines = p.make_dependency_table(deps, dep_functions) | local dep_lines = p.make_dependency_table(module_name, deps, dep_functions) | ||
-- Get module's functions, then build a table using that information | -- Get module's functions, then build a table using that information | ||
Revision as of 08:17, 25 October 2025
- This module should not be invoked directly; use its corresponding template instead: Template:Module introspection.
Module:Module introspection requires the following dependencies:
| Dependency | Variable | Function(s) used |
|---|---|---|
| Module: Arguments | getArgs | getArgs, getArgs |
Module:Module introspection provides 8 function(s):
| Function | Line |
|---|---|
| strip_comments | 10 |
| find_dependencies | 56 |
| find_dependency_functions | 66 |
| find_functions | 106 |
| make_dependency_table | 134 |
| make_function_table | 173 |
| _module_introspection | 206 |
| module_introspection | 231 |
-- 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.
-- Helper function
-- Blanks comments but preserves line numbers
function p.strip_comments(content)
if not content then return "" end
local lines = {}
local in_multiline = false
local end_pattern
for line in content:gmatch("([^\n]*)\n") do
local processed = line
if in_multiline then
-- Check for end of multi-line comment first
local s, e = processed:find(end_pattern)
if s then
in_multiline = false
-- Replace only the comment part with spaces
processed = string.rep(" ", e) .. processed:sub(e + 1)
else
-- Entire line is inside comment
processed = processed:gsub(".", " ")
end
else
local start_eq = processed:match("%-%-%[(=*)%[")
if start_eq then
in_multiline = true
end_pattern = "%]" .. start_eq .. "%]"
-- Blank from the start of comment to the end of line
local s, e = processed:find("%-%-%[" .. start_eq .. "%[")
if s then
processed = string.rep(" ", #processed)
end
else
processed = processed:gsub("%-%-.*", function(s)
return string.rep(" ", #s)
end)
end
end
table.insert(lines, processed)
end
return table.concat(lines, "\n")
end
-- Helper function
-- Find dependencies for a module, given a preprocessed module's code
function p.find_dependencies(preprocessed_module)
local deps = {}
for dep in preprocessed_module:gmatch("require%s*%(%s*['\"]Module:(.-)['\"]%s*%)") do
table.insert(deps, dep)
end
return deps
end
-- Helper function
-- Find functions used by each dependency
function p.find_dependency_functions(preprocessed_module)
-- Step 1: Find all require statements with optional .function
local results = {}
for var, dep, entry in preprocessed_module: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 preprocessed_module:gmatch(var .. "%s*%(") do
used[entry] = true
end
else
-- The variable is a module table: var.func() usage
for func in preprocessed_module:gmatch(var .. "%.(%w+)%s*%(") do
used[func] = true
end
end
-- Step 3: Build result entry
local func_list = {}
for f in pairs(used) do table.insert(func_list, f) end
table.sort(func_list)
results[dep] = results[dep] or {}
table.insert(results[dep], {
variable = var,
entry = (entry ~= "" and entry or nil),
functions = func_list
})
end
return results
end
-- Helper function
-- Find functions provided by a module, ignoring nested/local functions
function p.find_functions(preprocessed_content)
-- Iterate through file and find each function and the line found at
local funcs = {}
if preprocessed_content then
local line_num = 0
for line in preprocessed_content:gmatch("([^\n]*)\n?") do -- Make sure gmatch does not skip blank lines
line_num = line_num + 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 = line_num})
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 = line_num})
end
end
end
return funcs
end
-- Helper function; builds the table of dependencies
-- All dependencies are included regardless of use
function p.make_dependency_table(module_name, deps, dep_functions)
local dep_lines = {}
table.insert(dep_lines, string.format("'''Module:%s''' requires the following dependencies:", module_name))
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")
for _, dep in ipairs(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, '|}')
return dep_lines
end
-- Helper function
-- Lists module's own functions
function p.make_function_table(module_name, module_functions)
-- Collapse table if it's larger than 20 lines
local func_class = "wikitable sortable mw-collapsible"
if #module_functions > 20 then
func_class = func_class .. " mw-collapsed"
end
local func_lines = {}
table.insert(func_lines, string.format("'''Module:%s''' provides %d function(s):", module_name, #module_functions))
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#L-%d|%s]]", module_name, f.line, f.name)
-- If the function is the main function, add "main" to that cell
if f.name == main_function then
link = link .. " '''(main)'''"
end
table.insert(func_lines, "|-")
table.insert(func_lines, "| " .. link)
table.insert(func_lines, "| " .. f.line)
end
table.insert(func_lines, "|}")
return func_lines
end
-- Main function; to be called by wrapper
function p._module_introspection(args)
local args = args or {}
local module_name = args["module_name" ] or "Numlinks"
local main_function = args["main_function"]
-- Preprocess module and blank-out comments
local title = mw.title.new('Module:' .. module_name)
local preprocessed_content = title:getContent()
preprocessed_content = p.strip_comments(preprocessed_content) -- Blank-out comments
-- Get dependencies, their functions used, then build a table
local deps = p.find_dependencies(preprocessed_content)
local dep_functions = p.find_dependency_functions(preprocessed_content)
local dep_lines = p.make_dependency_table(module_name, deps, dep_functions)
-- Get module's functions, then build a table using that information
local module_functions = p.find_functions(preprocessed_content)
local func_lines = p.make_function_table(module_name, module_functions)
-- 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$", "")
-- 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
-- Call the introspection function
local dep_table, func_table = p._module_introspection({
["module_name"] = module_name,
["main_function"] = main_function,
})
-- Return combined tables
return dep_table .. "\n" .. func_table .. "\n"
end
return p