Module:Module introspection: Difference between revisions

Ganaram inukshuk (talk | contribs)
fix issue with block comments prematurely halting introspection
Ganaram inukshuk (talk | contribs)
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
-- 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
-- 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
-- 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
-- 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
-- List dependencies for a module
-- Find dependencies for a module, given a preprocessed module's code
function p.list_dependencies(module_name)
function p.find_dependencies(preprocessed_module)
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 = {}
local deps = {}
for dep in content:gmatch("require%s*%(%s*['\"]Module:(.-)['\"]%s*%)") do
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
-- List functions used from each dependency
-- Find functions used by each dependency
-- This assumes dependency functions are used in the format of: var.func(),
function p.find_dependency_functions(preprocessed_module)
-- 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
-- Step 1: Find all require statements with optional .function
local results = {}
local results = {}
for var, dep, entry in content:gmatch(
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 content:gmatch(var .. "%s*%(") do
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 content:gmatch(var .. "%.(%w+)%s*%(") do
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 funcList = {}
local func_list = {}
for f in pairs(used) do table.insert(funcList, f) end
for f in pairs(used) do table.insert(func_list, f) end
table.sort(funcList)
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 = funcList
functions = func_list
})
})
end
end
Line 127: Line 103:


-- Helper function
-- Helper function
-- List functions provided by a module
-- Find functions provided by a module, ignoring nested/local functions
-- Only returned functions are considered, not local functions
function p.find_functions(preprocessed_content)
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
-- Iterate through file and find each function and the line found at
local funcs = {}
local funcs = {}
if content then
if preprocessed_content then
local line_num = 0
local line_num = 0
for line in content:gmatch("([^\n]*)\n?") do -- Make sure gmatch does not skip blank lines
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


-- Main function; to be called by wrapper
-- Helper function; builds the table of dependencies
function p._module_introspection(args)
-- All dependencies are included regardless of use
local args = args or {}
function p.make_dependency_table(deps, dep_functions)
local module_name  = args["module_name"  ] or "Numlinks"
local main_function = args["main_function"]
local dep_lines = {}
 
    -- 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, '{| 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")
 
    -- Include all dependencies even if no usage is detected
for _, dep in ipairs(deps) do
local all_deps = p.list_dependencies(module_name)
local dep_link = string.format("[[Module: %s]]", dep)
for _, dep in ipairs(all_deps) do
local usages = dep_functions[dep]
    local dep_link = string.format("[[Module: %s]]", dep)
if usages and #usages > 0 then
    local usages = dep_functions[dep]
for _, usage in ipairs(usages) do
    if usages and #usages > 0 then
local func_str = table.concat(usage.functions, ", ")
        for _, usage in ipairs(usages) do
if usage.entry then
            local func_str = table.concat(usage.functions, ", ")
func_str = usage.entry .. (func_str ~= "" and (", " .. func_str) or "")
            if usage.entry then
end
                func_str = usage.entry .. (func_str ~= "" and (", " .. func_str) or "")
table.insert(dep_lines, "|-")
            end
table.insert(dep_lines, "| " .. dep_link)
            table.insert(dep_lines, "|-")
table.insert(dep_lines, "| " .. usage.variable)
            table.insert(dep_lines, "| " .. dep_link)
table.insert(dep_lines, "| " .. (func_str ~= "" and func_str or "''dependency not used''"))
            table.insert(dep_lines, "| " .. usage.variable)
end
            table.insert(dep_lines, "| " .. (func_str ~= "" and func_str or "''dependency not used''"))
else
        end
table.insert(dep_lines, "|-")
    else
table.insert(dep_lines, "| " .. dep_link)
        table.insert(dep_lines, "|-")
table.insert(dep_lines, "| -")
        table.insert(dep_lines, "| " .. dep_link)
table.insert(dep_lines, "| ''dependency not used''")
        table.insert(dep_lines, "| -")
end
        table.insert(dep_lines, "| ''dependency not used''")
    end
end
end


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


    -- Build MediaWiki table for module's own functions
-- 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"
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
for _, f in ipairs(module_functions) do
        local link = string.format("[[Module:%s#L-%d|%s]]", module_name, f.line, f.name)
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, "|-")
        table.insert(func_lines, "| " .. link)
table.insert(func_lines, "| " .. link)
        table.insert(func_lines, "| " .. f.line)
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