Module:Module introspection: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
try to bugfix line count again |
||
| Line 11: | Line 11: | ||
if not content then return "" end | 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 | |||
return | if in_multiline then | ||
-- inside a multi-line comment: replace everything with spaces | |||
processed = processed:gsub(".", " ") | |||
-- check for end of multi-line comment | |||
if processed:find(end_pattern) then | |||
in_multiline = false | |||
end | |||
else | |||
-- check for start of multi-line comment | |||
local start_eq = processed:match("%-%-%[(=*)%[") | |||
if start_eq then | |||
in_multiline = true | |||
end_pattern = "%]" .. start_eq .. "%]" | |||
processed = processed:gsub(".", " ") | |||
else | |||
-- replace single-line comments | |||
processed = processed:gsub("%-%-.*", function(s) | |||
return string.rep(" ", #s) | |||
end) | |||
end | |||
end | |||
table.insert(lines, processed) | |||
end | |||
-- handle last line if it doesn’t end with newline | |||
local last_line = content:match("([^\n]+)$") | |||
if last_line then | |||
local processed = last_line | |||
if in_multiline then | |||
processed = processed:gsub(".", " ") | |||
else | |||
processed = processed:gsub("%-%-.*", function(s) return string.rep(" ", #s) end) | |||
end | |||
table.insert(lines, processed) | |||
end | |||
return table.concat(lines, "\n") | |||
end | end | ||
| Line 116: | Line 148: | ||
local funcs = {} | local funcs = {} | ||
if content then | if content then | ||
local | local line_num = 0 | ||
for line in content:gmatch("[^\n] | for line in 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( | -- Match functions defined as function p.name( | ||
local name = line:match("function%s+[%w_]+%.([%w_]+)%s*%(") | local name = line:match("function%s+[%w_]+%.([%w_]+)%s*%(") | ||
if name then | if name then | ||
table.insert(funcs, {name = name, line = | table.insert(funcs, {name = name, line = line_num}) | ||
end | end | ||
| Line 129: | Line 161: | ||
name = line:match("[%w_]+%.([%w_]+)%s*=%s*function%s*%(") | name = line:match("[%w_]+%.([%w_]+)%s*=%s*function%s*%(") | ||
if name then | if name then | ||
table.insert(funcs, {name = name, line = | table.insert(funcs, {name = name, line = line_num}) | ||
end | end | ||
end | end | ||
| Line 139: | Line 171: | ||
-- 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 module_name = args["module_name" ] | local args = args or {} | ||
local module_name = args["module_name" ] or "MOS" | |||
local main_function = args["main_function"] | local main_function = args["main_function"] | ||
Revision as of 06:29, 25 October 2025
- This module should not be invoked directly; use its corresponding template instead: Template:Module introspection.
| Dependency | Variable | Function(s) used |
|---|---|---|
| Module: Arguments | getArgs | getArgs, getArgs |
| Function | Line |
|---|---|
| strip_comments | 10 |
| list_dependencies | 62 |
| list_dependency_functions | 88 |
| list_functions | 137 |
| _module_introspection (main) | 172 |
| module_introspection | 248 |
-- 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
-- inside a multi-line comment: replace everything with spaces
processed = processed:gsub(".", " ")
-- check for end of multi-line comment
if processed:find(end_pattern) then
in_multiline = false
end
else
-- check for start of multi-line comment
local start_eq = processed:match("%-%-%[(=*)%[")
if start_eq then
in_multiline = true
end_pattern = "%]" .. start_eq .. "%]"
processed = processed:gsub(".", " ")
else
-- replace single-line comments
processed = processed:gsub("%-%-.*", function(s)
return string.rep(" ", #s)
end)
end
end
table.insert(lines, processed)
end
-- handle last line if it doesn’t end with newline
local last_line = content:match("([^\n]+)$")
if last_line then
local processed = last_line
if in_multiline then
processed = processed:gsub(".", " ")
else
processed = processed:gsub("%-%-.*", function(s) return string.rep(" ", #s) end)
end
table.insert(lines, processed)
end
return table.concat(lines, "\n")
end
-- Helper function
-- 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()
-- Blank-out comments
content = p.strip_comments(content)
-- 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
-- Helper function
-- 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
-- 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
local title = mw.title.new('Module:' .. module_name)
local content = title:getContent()
-- Blank-out comments
content = p.strip_comments(content)
-- Iterate through file and find each function and the line found at
local funcs = {}
if content then
local line_num = 0
for line in 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
-- Main function; to be called by wrapper
function p._module_introspection(args)
local args = args or {}
local module_name = args["module_name" ] or "MOS"
local main_function = args["main_function"]
-- 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
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
table.insert(dep_lines, '|}')
-- Build MediaWiki table for module's own functions
local func_lines = {}
local func_class = "wikitable sortable mw-collapsible"
if #module_functions > 20 then
func_class = func_class .. " mw-collapsed"
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#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 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