Module:Module introspection: Difference between revisions
fix issue with block comments prematurely halting introspection |
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) |
||
| Line 19: | Line 19: | ||
if in_multiline then | if in_multiline then | ||
-- | -- Check for end of multi-line comment first | ||
local s, e = processed:find(end_pattern) | local s, e = processed:find(end_pattern) | ||
if s then | if s then | ||
in_multiline = false | in_multiline = false | ||
-- | -- Replace only the comment part with spaces | ||
processed = string.rep(" ", e) .. processed:sub(e + 1) | processed = string.rep(" ", e) .. processed:sub(e + 1) | ||
else | else | ||
-- | -- Entire line is inside comment | ||
processed = processed:gsub(".", " ") | processed = processed:gsub(".", " ") | ||
end | end | ||
| Line 34: | Line 34: | ||
in_multiline = true | in_multiline = true | ||
end_pattern = "%]" .. start_eq .. "%]" | end_pattern = "%]" .. start_eq .. "%]" | ||
-- | -- Blank from the start of comment to the end of line | ||
local s, e = processed:find("%-%-%[" .. start_eq .. "%[") | local s, e = processed:find("%-%-%[" .. start_eq .. "%[") | ||
if s then | if s then | ||
| Line 53: | Line 53: | ||
-- Helper function | -- Helper function | ||
-- | -- Find dependencies for a module, given a preprocessed module's code | ||
function p. | function p.find_dependencies(preprocessed_module) | ||
local deps = {} | local deps = {} | ||
for dep in | for dep in preprocessed_module:gmatch("require%s*%(%s*['\"]Module:(.-)['\"]%s*%)") do | ||
table.insert(deps, dep) | table.insert(deps, dep) | ||
end | end | ||
| Line 73: | Line 63: | ||
-- Helper function | -- Helper function | ||
-- | -- Find functions used by each dependency | ||
function p.find_dependency_functions(preprocessed_module) | |||
function p. | |||
-- Step 1: Find all require statements with optional .function | -- Step 1: Find all require statements with optional .function | ||
local results = {} | local results = {} | ||
for var, dep, entry in | for var, dep, entry in preprocessed_module:gmatch( | ||
"local%s+([%w_]+)%s*=%s*require%s*%(%s*['\"]Module:(.-)['\"]%s*%)%.?([%w_]*)" | "local%s+([%w_]+)%s*=%s*require%s*%(%s*['\"]Module:(.-)['\"]%s*%)%.?([%w_]*)" | ||
) do | ) do | ||
| Line 100: | Line 76: | ||
if entry ~= "" then | if entry ~= "" then | ||
-- The module returned a single function: var() usage | -- The module returned a single function: var() usage | ||
for _ in | for _ in preprocessed_module:gmatch(var .. "%s*%(") do | ||
used[entry] = true | used[entry] = true | ||
end | end | ||
else | else | ||
-- The variable is a module table: var.func() usage | -- The variable is a module table: var.func() usage | ||
for func in | for func in preprocessed_module:gmatch(var .. "%.(%w+)%s*%(") do | ||
used[func] = true | used[func] = true | ||
end | end | ||
| Line 111: | Line 87: | ||
-- Step 3: Build result entry | -- Step 3: Build result entry | ||
local | local func_list = {} | ||
for f in pairs(used) do table.insert( | for f in pairs(used) do table.insert(func_list, f) end | ||
table.sort( | table.sort(func_list) | ||
results[dep] = results[dep] or {} | results[dep] = results[dep] or {} | ||
| Line 119: | Line 95: | ||
variable = var, | variable = var, | ||
entry = (entry ~= "" and entry or nil), | entry = (entry ~= "" and entry or nil), | ||
functions = | functions = func_list | ||
}) | }) | ||
end | end | ||
| Line 127: | Line 103: | ||
-- Helper function | -- Helper function | ||
-- | -- Find functions provided by a module, ignoring nested/local functions | ||
function p.find_functions(preprocessed_content) | |||
function p. | |||
-- Iterate through file and find each function and the line found at | -- Iterate through file and find each function and the line found at | ||
local funcs = {} | local funcs = {} | ||
if | if preprocessed_content then | ||
local line_num = 0 | local line_num = 0 | ||
for line in | for line in preprocessed_content:gmatch("([^\n]*)\n?") do -- Make sure gmatch does not skip blank lines | ||
line_num = line_num + 1 | line_num = line_num + 1 | ||
| Line 163: | Line 130: | ||
end | end | ||
-- | -- Helper function; builds the table of dependencies | ||
-- All dependencies are included regardless of use | |||
function p.make_dependency_table(deps, dep_functions) | |||
local dep_lines = {} | |||
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') | ||
| Line 182: | Line 140: | ||
table.insert(dep_lines, "! Variable") | table.insert(dep_lines, "! Variable") | ||
table.insert(dep_lines, "! Function(s) used") | table.insert(dep_lines, "! Function(s) used") | ||
for _, dep in ipairs(deps) do | |||
local dep_link = string.format("[[Module: %s]]", dep) | |||
for _, dep in ipairs( | 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 | ||
table.insert(dep_lines, '|}') | |||
return dep_lines | |||
end | |||
-- Helper function | |||
-- Lists module's own functions | |||
function p.make_function_table(module_name, module_functions) | |||
local func_lines = {} | local func_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" | |||
end | end | ||
table.insert(func_lines, "{| class=\"" .. func_class .. "\"") | table.insert(func_lines, "{| class=\"" .. func_class .. "\"") | ||
| Line 220: | Line 183: | ||
table.insert(func_lines, "! Line") | 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 the function is the main function, add "main" to that cell | ||
if f.name == main_function then | if f.name == main_function then | ||
link = link .. " '''(main)'''" | link = link .. " '''(main)'''" | ||
end | end | ||
table.insert(func_lines, "|-") | |||
table.insert(func_lines, "| " .. link) | |||
table.insert(func_lines, "| " .. f.line) | |||
end | end | ||
table.insert(func_lines, "|}") | 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(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 the tables as strings | ||