Module:DependencyList: Difference between revisions

mNo edit summary
mNo edit summary
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- <nowiki>
local p = {}
local p = {}
local libraryUtil = require( 'libraryUtil' )
local libraryUtil = require( 'libraryUtil' )
Line 4: Line 5:
local yn = require( 'Module:Yesno' )
local yn = require( 'Module:Yesno' )
local param = require( 'Module:Paramtest' )
local param = require( 'Module:Paramtest' )
local dpl = require( 'Module:DPLlua' )
local tooltip = require( 'Module:Tooltip' )
local moduleIsUsed = false
local moduleIsUsed = false
local COLLAPSE_LIST_LENGTH_THRESHOLD = 5
local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30
local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30
local dynamicRequireListQueryCache = {}
local dynamicRequireListQueryCache = {}


--- Used in case 'require( varName )' is found. Attempts to find a string value stored in 'varName'.
--- Used in case 'require( varName )' is found. Attempts to find a string value stored in 'varName'.
---@param content string    @The content of the module to look in as a string
---@param content string    The content of the module to search in
---@param varName string
---@param varName string
---@return string
---@return string
local function substVarValue( content, varName )
local function substVarValue( content, varName )
     if varName:find( "%b''" ) or varName:find( '%b""' ) then -- Look for balanced quotes
     local res = content:match( varName .. '%s*=%s*(%b""%s-%.*)' ) or content:match( varName .. "%s*=%s*(%b''%s-%.*)" ) or ''
         return varName -- Is already a string
    if res:find( '^(["\'])[Mm]odule:[%S]+%1' ) and not res:find( '%.%.' ) and not res:find( '%%%a' ) then
         return mw.text.trim( res )
     else
     else
    local res = content:match( varName .. '%s*=%s*(%b""%s-%.*)' ) or content:match( varName .. "%s*=%s*(%b''%s-%.*)" ) or ''
        return ''
    if res:find( '^[Mm]odule:[%w%s_]+$' ) and not res:find( '%.' ) then
    end
    return mw.text.trim( res )
end
    else
 
        return ''
---@param capture string
        end
---@param content string    The content of the module to search in
---@return string
local function extractModuleName( capture, content )
    capture = capture:gsub( '^%(%s*(.-)%s*%)$', '%1' )
 
    if capture:find( '^(["\']).-%1$' ) then -- Check if it is already a pure string
        return capture
    elseif capture:find( '^[%a_][%w_]*$' ) then -- Check if if is a single variable
        return substVarValue( content, capture )
     end
     end
    return capture
end
---@param str string
---@return string
local function formatModuleName( str )
    return (str:gsub( '^([\'\"])(.-)%1$', function(_, x) return x end ) -- Only remove quotes at start and end of string if both are the same type
        :gsub( '_', ' ' )
        :gsub( '^.', string.upper )
        :gsub( ':(.)', function(x) return ':'..x:upper() end ))
end
end


Line 28: Line 52:
--- Will return a list of pages which satisfy this pattern where 'isTheBest' can take any value.
--- Will return a list of pages which satisfy this pattern where 'isTheBest' can take any value.
---@param query string
---@param query string
---@return string[]    @Sequence of strings
---@return string[]    Sequence of strings
local function getDynamicRequireList( query )
local function getDynamicRequireList( query )
     query = mw.text.split( query, '..', true )
     if query:find( '%.%.' ) then
    query = enum.map( query, function(x) return mw.text.trim(x) end )
        query = mw.text.split( query, '..', true )
    query = enum.map( query, function(x) return (x:match('^[\'\"]([^\'\"]+)[\'\"]$') or '%') end )
        query = enum.map( query, function(x) return mw.text.trim(x) end )
    query = table.concat( query )
        query = enum.map( query, function(x) return (x:match('^[\'\"](.-)[\'\"]$') or '%') end )
        query = table.concat( query )
    else
        _, query = query:match( '(["\'])(.-)%1' )
        query = query:gsub( '%%%a', '%%' )
    end
     query = query:gsub( '^[Mm]odule:', '' )
     query = query:gsub( '^[Mm]odule:', '' )


     if query:lower():find( '^exchange/' ) then
     if query:find( '^[Ee]xchange/' ) or query:find( '^[Dd]ata/' ) then
         return { 'Module:' .. query }  -- This format will later be used by formatDynamicQueryLink()
         return { 'Module:' .. query }  -- This format will later be used by formatDynamicQueryLink()
     end
     end
Line 44: Line 73:
     end
     end


     local getDynamicRequireListDpl =
     local list = dpl.ask{
[=[
        namespace = 'Module',
{{#dpl:
        titlematch = query,
|namespace = Module
        nottitlematch = '%/doc|'..query..'/%',
|titlematch = %s
        distinct = 'strict',
|nottitlematch = %%/doc¦%%sandbox%%¦%s/%%
        ignorecase = true,
|format = ,@@%%PAGE%%,,
        ordermethod = 'title',
|noresultsheader = @@
        count = MAX_DYNAMIC_REQUIRE_LIST_LENGTH + 1,
|distinct = strict
        skipthispage = 'no',
|ignorecase = true
        allowcachedresults = true,
|ordermethod = title
        cacheperiod = 604800 -- One week
|allowcachedresults = true
    }
|count = %d
|skipthispage = no
}}]=]
 
    getDynamicRequireListDpl = getDynamicRequireListDpl:format( query, query, MAX_DYNAMIC_REQUIRE_LIST_LENGTH + 1 )
    local list = mw.getCurrentFrame():preprocess( getDynamicRequireListDpl )
    list = mw.text.split( list, '@@', true )
    list = enum.reject( list, function(x) return x=='' end )


     if #list > MAX_DYNAMIC_REQUIRE_LIST_LENGTH then
     if #list > MAX_DYNAMIC_REQUIRE_LIST_LENGTH then
Line 76: Line 97:
--- Returns a list of modules loaded and required by module 'moduleName'.
--- Returns a list of modules loaded and required by module 'moduleName'.
---@param moduleName string
---@param moduleName string
---@return string[], string[]
---@param searchForUsedTemplates boolean
local function getRequireList( moduleName )
---@return string[], string[], string[]
local function getRequireList( moduleName, searchForUsedTemplates )
     local content = mw.title.new( moduleName ):getContent()
     local content = mw.title.new( moduleName ):getContent()
     local requireList = {}
     local requireList = {}
     local loadDataList = {}
     local loadDataList = {}
    local usedTemplateList = {}
     local dynamicRequirelist = {}
     local dynamicRequirelist = {}
     local dynamicLoadDataList = {}
     local dynamicLoadDataList = {}
Line 86: Line 109:
     assert( param.has_content( content ), string.format( '%s does not exist', moduleName ) )
     assert( param.has_content( content ), string.format( '%s does not exist', moduleName ) )


     content = content:gsub( '%-%-%[(=-)%[.-%]%1%]', '' ):gsub( '%-%-[^\n]*', '' )
     content = content:gsub( '%-%-%[(=-)%[.-%]%1%]', '' ):gsub( '%-%-[^\n]*', '' ) -- Strip comments


     for match in string.gmatch( content, 'require%s*%(([^%)]+)' ) do
    local function dualGmatch( str, pat1, pat2 )
        local f1 = string.gmatch( str, pat1 )
        local f2 = string.gmatch( str, pat2 )
        return function()
            return f1() or f2()
        end
    end
 
     for match in dualGmatch( content, 'require%s*(%b())', 'require%s*((["\'])%s*[Mm]odule:.-%2)' ) do
         match = mw.text.trim( match )
         match = mw.text.trim( match )
         match = substVarValue( content, match )
         match = extractModuleName( match, content )


         if match:find( '%.%.' ) then
         if match:find( '%.%.' ) or match:find( '%%%a' ) then
             for _, x in ipairs( getDynamicRequireList( match ) ) do
             for _, x in ipairs( getDynamicRequireList( match ) ) do
                 table.insert( dynamicRequirelist, x )
                 table.insert( dynamicRequirelist, x )
             end
             end
         elseif match ~= '' then
         elseif match ~= '' then
             match = match:gsub( '[\"\']', '' ):gsub( '_', ' ' )
             match = formatModuleName( match )


             if match == 'libraryUtil' then
             if match == 'LibraryUtil' then
                 match = 'Module:LibraryUtil'
                 match = 'Module:LibraryUtil'
             end
             end
Line 107: Line 138:
     end
     end


     for match in string.gmatch( content, 'mw%.loadData%s*%(([^%)]+)' ) do
     for match in dualGmatch( content, 'mw%.loadData%s*(%b())', 'mw%.loadData%s*((["\'])%s*[Mm]odule:.-%2)' ) do
         match = mw.text.trim( match )
         match = mw.text.trim( match )
         match = substVarValue( content, match )
         match = extractModuleName( match, content )


         if match:find( '%.%.' ) then
         if match:find( '%.%.' ) or match:find( '%%%a' ) then
             for _, x in ipairs( getDynamicRequireList( match ) ) do
             for _, x in ipairs( getDynamicRequireList( match ) ) do
                 table.insert( dynamicLoadDataList, x )
                 table.insert( dynamicLoadDataList, x )
             end
             end
         elseif match ~= '' then
         elseif match ~= '' then
             match = match:gsub( '[\"\']', '' ):gsub( '_', ' ' )
             match = formatModuleName( match )
             table.insert( loadDataList, match )
             table.insert( loadDataList, match )
         end
         end
Line 132: Line 163:
             for _, x in ipairs( getDynamicRequireList( match ) ) do
             for _, x in ipairs( getDynamicRequireList( match ) ) do
                 table.insert( dynamicLoadDataList, x )
                 table.insert( dynamicLoadDataList, x )
            end
        end
    end
    if searchForUsedTemplates then
        for preprocess in string.gmatch( content, ':preprocess%s*(%b())' ) do
            local function recursiveGMatch( str, pat )
                local list = {}
                local i = 0
                repeat
                    for match in string.gmatch( list[i] or str, pat ) do
                        table.insert( list, match )
                    end
                    i =  i + 1
                until i > #list or i > 100
                i = 0
                return function()
                    i = i + 1
                    return list[i]
                end
            end
            for template in recursiveGMatch( preprocess, '{(%b{})}' ) do
                local name = string.match( template, '{(.-)[|{}]' )
                if name ~= '' then
                    if name:find( ':' ) then
                        local ns = name:match( '^(.-):' )
                        if enum.contains( {'', 'template', 'calculator', 'user'}, ns:lower() ) then
                            table.insert( usedTemplateList, name )
                        elseif ns == ns:upper() then
                            table.insert( usedTemplateList, ns ) -- Probably a magic word
                        end
                    else
                        if name:match( '^%u+$' ) or name == '!' then
                            table.insert( usedTemplateList, name ) -- Probably a magic word
                        else
                            table.insert( usedTemplateList, 'Template:'..name )
                        end
                    end
                end
             end
             end
         end
         end
Line 142: Line 214:
     loadDataList = enum.insert( loadDataList, dynamicLoadDataList )
     loadDataList = enum.insert( loadDataList, dynamicLoadDataList )
     loadDataList = enum.unique( loadDataList )
     loadDataList = enum.unique( loadDataList )
    usedTemplateList = enum.unique( usedTemplateList )
     table.sort( requireList )
     table.sort( requireList )
     table.sort( loadDataList )
     table.sort( loadDataList )
    table.sort( usedTemplateList )


     return requireList, loadDataList
     return requireList, loadDataList, usedTemplateList
end
 
--- Makes the first letter of the input string upper case
---@param str string
---@return string
local function ucfirst( str )
    return str:gsub( '^.', function(c) return c:upper() end )
end
end


Line 165: Line 232:


     for moduleName, funcName in string.gmatch( content, '{{[{|safeubt:}]-#[Ii]nvoke:([^|]+)|([^}|]+)[^}]*}}' ) do
     for moduleName, funcName in string.gmatch( content, '{{[{|safeubt:}]-#[Ii]nvoke:([^|]+)|([^}|]+)[^}]*}}' ) do
        moduleName = ucfirst( moduleName )
         moduleName = string.format( 'Module:%s', mw.text.trim( moduleName ) )
         moduleName = string.format( 'Module:%s', moduleName )
         moduleName = formatModuleName( moduleName )
         moduleName = moduleName:gsub( '_', ' ' )
        funcName = mw.text.trim( funcName )
         table.insert( invokeList, {moduleName=moduleName, funcName=funcName} )
         table.insert( invokeList, {moduleName=moduleName, funcName=funcName} )
     end
     end
Line 180: Line 247:
---@param addCategories boolean
---@param addCategories boolean
---@return string
---@return string
local function messageBoxUnused( pageName, addCategories )
local function collapseList( list, id, listType )
     local html = mw.html.create( 'table' ):addClass( 'messagebox obsolete plainlinks' )
     local text = string.format( '%d %s', #list, listType )
     html:tag( 'td' )
     local button = tooltip._span{ name=id, alt=text }
        :attr( 'width', '40xp' )
     list = enum.map( list, function(x) return '\n# '..x end )
        :wikitext( '[[File:Iron full helm detail old.png|center|30px|link=]]' )
    local content = tooltip._div{ name=id, content='\n'..table.concat( list )..'\n\n' }
     :done()
    :tag( 'td' )
        :wikitext( "'''This module is unused.'''" )
        :tag( 'div' )
            :css{ ['font-size']='0.85em', ['line-height']='1.45em' }
            :wikitext( string.format( 'This module is neither invoked by a template nor required/loaded be another module. If this is in error make sure to add <code>{{[[Template:Documentation|Documentation]]}}</code>/<code>{{[[Template:No documentation|No&nbsp;documentation]]}}</code> to the calling template\'s or parent\'s module documentation.', pageName ) )
            :wikitext( addCategories and '[[Category:Unused modules]]' or '' )
        :done()
    :done()


     return tostring( html )
     return { tostring( button ) .. tostring( content ) }
end
end


Line 229: Line 287:


---@param templateName string
---@param templateName string
---@param compact boolean
---@param addCategories boolean
---@param addCategories boolean
---@param invokeList table<string, string>[]    @This is the list returned by getInvokeCallList()
---@param invokeList table<string, string>[]    @This is the list returned by getInvokeCallList()
---@return string
---@return string
local function formatInvokeCallList( templateName, compact, addCategories, invokeList )
local function formatInvokeCallList( templateName, addCategories, invokeList )
     local category = addCategories and '[[Category:Lua-based templates]]' or ''
     local category = addCategories and '[[Category:Lua-based templates]]' or ''
     local res = {}
     local res = {}


     if compact then
     for _, item in ipairs( invokeList ) do
        invokeList = enum.map( invokeList, function(x) return '[['..x.moduleName..']]' end )
         table.insert( res, string.format(
 
             "<div class='seealso'>'''''%s''' invokes function '''%s''' in [[%s]] using [[GSWiki:Lua|Lua]]''.</div>",
         table.insert( string.format(
             "<div class='seealso'>'''''%s''' invokes %s using [[GeminiStation:Lua|Lua]]''.</div>%s",
             templateName,
             templateName,
             mw.text.listToText( invokeList, ', ', ' and ' ),
             item.funcName,
             category
             item.moduleName
         ) )
         ) )
     else
     end
        for _, item in ipairs( invokeList ) do
            table.insert( res, string.format(
                "<div class='seealso'>'''''%s''' invokes function '''%s''' in [[%s]] using [[GeminiStation:Lua|Lua]]''.</div>",
                templateName,
                item.funcName,
                item.moduleName
            ) )
        end


        if #invokeList > 0 then
    if #invokeList > 0 then
            table.insert( res, category )
        table.insert( res, category )
        end
     end
     end


Line 265: Line 311:


---@param moduleName string
---@param moduleName string
---@param compact boolean
---@param addCategories boolean
---@param addCategories boolean
---@param whatLinksHere string    @A list generated by a dpl of pages in the Template or Calculator namespace which link to moduleName. Pages are delimited by @@.
---@param whatLinksHere string    @A list generated by a dpl of pages in the Template or Calculator namespace which link to moduleName.
---@return string
---@return string
local function formatInvokedByList( moduleName, compact, addCategories, whatLinksHere )
local function formatInvokedByList( moduleName, addCategories, whatLinksHere )
    local category = addCategories and '[[Category:Template invoked modules]]' or ''
    whatLinksHere = mw.text.split( whatLinksHere, '@@' )
    whatLinksHere = enum.reject( whatLinksHere, function(x) return not x:find('%S') end )
    whatLinksHere = enum.reject( whatLinksHere, function(x) return x:find('/doc$') end )
     local templateData = enum.map( whatLinksHere, function(x) return {templateName=x, invokeList=getInvokeCallList(x)} end )
     local templateData = enum.map( whatLinksHere, function(x) return {templateName=x, invokeList=getInvokeCallList(x)} end )
     templateData = enum.filter( templateData, function(x) return enum.any( x.invokeList, function(y) return y.moduleName==moduleName end ) end )
     templateData = enum.filter( templateData, function(x)
        return enum.any( x.invokeList, function(y)
            return y.moduleName:lower() == moduleName:lower()
        end )
    end )


     if #templateData > 0 then
     local invokedByList = {}
        moduleIsUsed = true


         if compact then
    for _, template in ipairs( templateData ) do
             templateData = enum.map( templateData, function(x) return ('[['..x.templateName..']]') end )
         for _, invoke in ipairs( template.invokeList ) do
            templateData = enum.unique( templateData )
             table.insert( invokedByList, string.format( "function '''%s''' is invoked by [[%s]]", invoke.funcName, template.templateName ) )
        end
    end


            return string.format(
    table.sort( invokedByList)
                "<div class='seealso'>'''''%s''' is invoked by %s''.</div>%s",
                moduleName,
                mw.text.listToText( whatLinksHere, ', ', ' and ' ),
                category
            )
        else
            local res = {}


            for _, template in ipairs( templateData ) do
    local res = {}
                for _, invoke in ipairs( template.invokeList ) do
                    table.insert( res, string.format(
                        "<div class='seealso'>'''''%s's''' function '''%s''' is invoked by [[%s]]''.</div>",
                        moduleName,
                        invoke.funcName,
                        template.templateName
                    ) )
                end
            end


            table.sort( res )
    if #invokedByList > COLLAPSE_LIST_LENGTH_THRESHOLD then
             table.insert( res, category )
        table.insert( res, string.format(
            "<div class='seealso'>'''''%s''' is invoked by %s''.</div>",
            moduleName,
            collapseList( invokedByList, 'invokedBy', 'templates' )[1]
        ) )
    else
        for _, item in ipairs( invokedByList ) do
             table.insert( res, string.format(
                "<div class='seealso'>'''''%s's''' %s''.</div>",
                moduleName,
                item
            ) )
        end
    end


            return table.concat( res )
    if #templateData > 0 then
        end
        moduleIsUsed = true
        table.insert( res, (addCategories and '[[Category:Template invoked modules]]' or '') )
     end
     end


     return ''
     return table.concat( res )
end
end


---@param moduleName string
---@param moduleName string
---@param compact boolean
---@param addCategories boolean
---@param addCategories boolean
---@param whatLinksHere string      @A list generated by a dpl of pages in the Module namespace which link to moduleName. Pages are delimited by @@.
---@param whatLinksHere string      @A list generated by a dpl of pages in the Module namespace which link to moduleName.
---@return string
---@return string
local function formatRequiredByList( moduleName, compact, addCategories, whatLinksHere )
local function formatRequiredByList( moduleName, addCategories, whatLinksHere )
    whatLinksHere = mw.text.split( whatLinksHere, '@@' )
    whatLinksHere = enum.reject( whatLinksHere, function(x) return not x:find('%S') end )
 
     local childModuleData = enum.map( whatLinksHere, function ( title )
     local childModuleData = enum.map( whatLinksHere, function ( title )
         local requireList, loadDataList = getRequireList( title )
         local requireList, loadDataList = getRequireList( title )
Line 329: Line 369:


     local requiredByList = enum.map( childModuleData, function ( item )
     local requiredByList = enum.map( childModuleData, function ( item )
         if enum.any( item.requireList, function(x) return x==moduleName end ) then
         if enum.any( item.requireList, function(x) return x:lower()==moduleName:lower() end ) then
             if item.name:find( '%%' ) then
             if item.name:find( '%%' ) then
                 return formatDynamicQueryLink( item.name )
                 return formatDynamicQueryLink( item.name )
Line 339: Line 379:


     local loadedByList = enum.map( childModuleData, function ( item )
     local loadedByList = enum.map( childModuleData, function ( item )
         if enum.any( item.loadDataList, function(x) return x==moduleName end ) then
         if enum.any( item.loadDataList, function(x) return x:lower()==moduleName:lower() end ) then
             if item.name:find( '%%' ) then
             if item.name:find( '%%' ) then
                 return formatDynamicQueryLink( item.name )
                 return formatDynamicQueryLink( item.name )
Line 352: Line 392:
     end
     end


     if compact then
     if #requiredByList > COLLAPSE_LIST_LENGTH_THRESHOLD then
         local res = ''
         requiredByList = collapseList( requiredByList, 'requiredBy', 'modules' )
    end
 
    if #loadedByList > COLLAPSE_LIST_LENGTH_THRESHOLD then
        loadedByList = collapseList( loadedByList, 'loadedBy', 'modules' )
    end
 
    local res = {}
 
    for _, requiredByModuleName in ipairs( requiredByList ) do
        table.insert( res, string.format(
            "<div class='seealso'>'''''%s''' is required by %s''.</div>",
            moduleName,
            requiredByModuleName
        ) )
    end
 
    if #requiredByList > 0 then
        table.insert( res, (addCategories and '[[Category:Modules required by modules]]' or '') )
    end
 
    for _, loadedByModuleName in ipairs( loadedByList ) do
        table.insert( res, string.format(
            "<div class='seealso'>'''''%s''' is loaded by %s''.</div>",
            moduleName,
            loadedByModuleName
        ) )
    end
 
    if #loadedByList > 0 then
        table.insert( res, (addCategories and '[[Category:Module data]]' or '') )
    end
 
    return table.concat( res )
end
 
local function formatRequireList( currentPageName, addCategories, requireList )
    local res = {}


        if #requiredByList > 0 then
    if #requireList > COLLAPSE_LIST_LENGTH_THRESHOLD then
            res = string.format(
        requireList = collapseList( requireList, 'require', 'modules' )
                "<div class='seealso'>'''''%s''' is required by %s''.</div>%s",
    end
                moduleName,
 
                mw.text.listToText( requiredByList, ', ', ' and ' ),
    for _, requiredModuleName in ipairs( requireList ) do
                (addCategories and '[[Category:Modules required by modules]]' or '')
        table.insert( res, string.format(
            )
            "<div class='seealso'>'''''%s''' requires %s.''</div>",
        end
            currentPageName,
            requiredModuleName
        ) )
    end
 
    if #requireList > 0 then
        table.insert( res, (addCategories and '[[Category:Modules requiring modules]]' or '') )
    end
 
    return table.concat( res )
end
 
local function formatLoadDataList( currentPageName, addCategories, loadDataList )
    local res = {}


        if #loadedByList > 0 then
    if #loadDataList > COLLAPSE_LIST_LENGTH_THRESHOLD then
            res = res .. string.format(
        loadDataList = collapseList( loadDataList, 'loadData', 'modules' )
                "<div class='seealso'>'''''%s''' is loaded by %s''.</div>%s",
    end
                moduleName,
                mw.text.listToText( loadedByList, ', ', ' and ' ),
                (addCategories and '[[Category:Module data]]' or '')
            )
        end


         return res
    for _, loadedModuleName in ipairs( loadDataList ) do
    else
         table.insert( res, string.format(
         local res = {}
            "<div class='seealso'>'''''%s''' loads data from %s.''</div>",
            currentPageName,
            loadedModuleName
         ) )
    end


         for _, requiredByModuleName in ipairs( requiredByList ) do
    if #loadDataList > 0 then
            table.insert( res, string.format(
         table.insert( res, (addCategories and '[[Category:Modules using data]]' or '') )
                "<div class='seealso'>'''''%s''' is required by %s''.</div>",
    end
                moduleName,
                requiredByModuleName
            ) )
        end


        if #requiredByList > 0 then
    return table.concat( res )
            table.insert( res, (addCategories and '[[Category:Modules required by modules]]' or '') )
end
        end


        for _, loadedByModuleName in ipairs( loadedByList ) do
local function formatUsedTemplatesList( currentPageName, addCategories, usedTemplateList )
            table.insert( res, string.format(
    local res = {}
                "<div class='seealso'>'''''%s''' is loaded by %s''.</div>",
                moduleName,
                loadedByModuleName
            ) )
        end


        if #loadedByList > 0 then
    if #usedTemplateList > COLLAPSE_LIST_LENGTH_THRESHOLD then
            table.insert( res, (addCategories and '[[Category:Module data]]' or '') )
        usedTemplateList = collapseList( usedTemplateList, 'usedTemplates', 'templates' )
        end
    end


         return table.concat( res )
    for _, templateName in ipairs( usedTemplateList ) do
         table.insert( res, string.format(
            "<div class='seealso'>'''''%s''' transcludes %s using <samp>frame:preprocess()</samp>.''</div>",
            currentPageName,
            templateName
        ) )
     end
     end


     return ''
     return table.concat( res )
end
end


function p.main( frame )
function p.main( frame )
     local args = frame:getParent().args
     local args = frame:getParent().args
     return p._main( args[1], args.category, args.compact, args.isUsed )
     return p._main( args[1], args.category, args.isUsed )
end
end


---@param currentPageName string|nil
---@param currentPageName string|nil
---@param addCategories boolean|string|nil
---@param addCategories boolean|string|nil
---@param compact boolean|string|nil
---@return string
---@return string
function p._main( currentPageName, addCategories, compact, isUsed )
function p._main( currentPageName, addCategories, isUsed )
     libraryUtil.checkType( 'Module:RequireList._main', 1, currentPageName, 'string', true )
     libraryUtil.checkType( 'Module:RequireList._main', 1, currentPageName, 'string', true )
     libraryUtil.checkTypeMulti( 'Module:RequireList._main', 2, addCategories, {'boolean', 'string', 'nil'} )
     libraryUtil.checkTypeMulti( 'Module:RequireList._main', 2, addCategories, {'boolean', 'string', 'nil'} )
     libraryUtil.checkTypeMulti( 'Module:RequireList._main', 3, compact, {'boolean', 'string', 'nil'} )
     libraryUtil.checkTypeMulti( 'Module:RequireList._main', 3, isUsed, {'boolean', 'string', 'nil'} )
    libraryUtil.checkTypeMulti( 'Module:RequireList._main', 4, isUsed, {'boolean', 'string', 'nil'} )


     local title = mw.title.getCurrentTitle()
     local title = mw.title.getCurrentTitle()


    -- Leave early if not in module, template or calculator namespace or if module is part of exchange or data groups
     if param.is_empty( currentPageName ) and (
     if param.is_empty( currentPageName ) and (
    ( title.nsText ~= 'Module' and title.nsText ~= 'Template' and title.nsText ~= 'Calculator') or
        ( not enum.contains( {'Module', 'Template', 'Calculator'}, title.nsText ) ) or
    ( title.nsText == 'Module' and ( title.text:find( '^Exchange/' ) or title.text:find( '^Exchange historical/' ) or title.text:find( '^Sandbox/' ) ) ) ) then
        ( title.nsText == 'Module' and ( enum.contains( {'Exchange', 'Exchange historical', 'Data'}, title.text:match( '^(.-)/' ) ) ) )
    ) then
         return ''
         return ''
     end
     end
 
   
     currentPageName = param.default_to( currentPageName, title.fullText )
     currentPageName = param.default_to( currentPageName, title.fullText )
     currentPageName = string.gsub( currentPageName, '/[Dd]oc$', '' )
     currentPageName = string.gsub( currentPageName, '/[Dd]oc$', '' )
     currentPageName = string.gsub( currentPageName, '_', ' ' )
     currentPageName = formatModuleName( currentPageName )
    currentPageName = ucfirst( currentPageName:gsub( ':(.)', function(c) return ':'..c:upper() end ) )
     addCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) )
     addCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) )
    compact = yn( param.default_to( compact, false ) )
     moduleIsUsed = yn( param.default_to( isUsed, false ) )
     moduleIsUsed = yn( param.default_to( isUsed, false ) )


     if currentPageName:find( '^Template:' ) or currentPageName:find( '^Calculator:' ) then
     if currentPageName:find( '^Template:' ) or currentPageName:find( '^Calculator:' ) then
         local invokeList = getInvokeCallList( currentPageName )
         local invokeList = getInvokeCallList( currentPageName )
         return formatInvokeCallList( currentPageName, compact, addCategories, invokeList )
         return formatInvokeCallList( currentPageName, addCategories, invokeList )
     end
     end


     local invokedByDpl =
     local whatTemplatesLinkHere, whatModulesLinkHere = dpl.ask( {
[=[
        namespace = 'Template|Calculator',
{{#dpl:
        linksto = currentPageName,
|namespace = Template¦Calculator
        distinct = 'strict',
|linksto = %s
        ignorecase = true,
|format = ,@@%%PAGE%%,,
        ordermethod = 'title',
|noresultsheader = @@
        allowcachedresults = true,
|distinct = strict
        cacheperiod = 604800 -- One week
|ignorecase = true
     }, {
|ordermethod = title
        namespace = 'Module',
|allowcachedresults = true
        linksto = currentPageName,
}}]=]
        nottitlematch = '%/doc|Exchange/%|Exchange historical/%|Data/%|' .. currentPageName:gsub( 'Module:', '' ),
 
        distinct = 'strict',
     local requiredByDpl =
        ignorecase = true,
[=[
        ordermethod = 'title',
{{#dpl:
        allowcachedresults = true,
|namespace = Module
        cacheperiod = 604800 -- One week
|linksto = %s
     } )
|nottitlematch = %%/doc¦%%sandbox%%¦Exchange/%%¦Exchange historical/%%¦%s
|format = ,@@%%PAGE%%,,
|noresultsheader = @@
|distinct = strict
|ignorecase = true
|ordermethod = title
|allowcachedresults = true
}}]=]
 
    invokedByDpl = invokedByDpl:format( currentPageName )
    requiredByDpl = requiredByDpl:format( currentPageName, currentPageName:gsub( 'Module:', '' ) )
 
    local data = mw.getCurrentFrame():preprocess( invokedByDpl .. '$$' .. requiredByDpl )
    local invokedByList, requiredByList = unpack( mw.text.split( data, '$$', true ) )
    invokedByList = formatInvokedByList( currentPageName, compact, addCategories, invokedByList )
     requiredByList = formatRequiredByList( currentPageName, compact, addCategories, requiredByList )


     local requireList, loadDataList = getRequireList( currentPageName )
     local requireList, loadDataList, usedTemplateList = getRequireList( currentPageName, true )


     requireList = enum.map( requireList, function ( moduleName )
     requireList = enum.map( requireList, function ( moduleName )
Line 496: Line 562:
     end )
     end )


     local res = {}
     usedTemplateList = enum.map( usedTemplateList, function( templateName )
 
        if string.find( templateName, ':' ) then -- Real templates are prefixed by a namespace, magic words are not
    if not moduleIsUsed then
            return '[['..templateName..']]'
        table.insert( res, messageBoxUnused( currentPageName:gsub( 'Module:', '' ), addCategories ) )
         else
    end
             return "'''&#123;&#123;"..templateName.."&#125;&#125;'''" -- Magic words don't have a page so make them bold instead
 
    table.insert( res, invokedByList )
 
    if compact then
         if #requireList > 0 then
             table.insert( res, string.format(
                "<div class='seealso'>'''''%s''' requires %s.''</div>%s",
                currentPageName,
                mw.text.listToText( requireList, ', ', ' and ' ),
                (addCategories and '[[Category:Modules requiring modules]]' or '')
            ) )
         end
         end
    end )


        if #loadDataList > 0 then
     local res = {}
            table.insert( res, string.format(
                "<div class='seealso'>'''''%s''' loads data from %s.''</div>%s",
                currentPageName,
                mw.text.listToText( loadDataList, ', ', ' and ' ),
                (addCategories and '[[Category:Modules using data]]' or '')
            ) )
        end
     else
        for _, requiredModuleName in ipairs( requireList ) do
            table.insert( res, string.format(
                "<div class='seealso'>'''''%s''' requires %s.''</div>",
                currentPageName,
                requiredModuleName
            ) )
        end
 
        if #requireList > 0 then
            table.insert( res, (addCategories and '[[Category:Modules requiring modules]]' or '') )
        end
 
        for _, loadedModuleName in ipairs( loadDataList ) do
            table.insert( res, string.format(
                "<div class='seealso'>'''''%s''' loads data from %s.''</div>",
                currentPageName,
                loadedModuleName
            ) )
        end
 
        if #loadDataList > 0 then
            table.insert( res, (addCategories and '[[Category:Modules using data]]' or '') )
        end
    end


     table.insert( res, requiredByList )
     table.insert( res, formatInvokedByList( currentPageName, addCategories, whatTemplatesLinkHere ) )
    table.insert( res, formatRequireList( currentPageName, addCategories, requireList ) )
    table.insert( res, formatLoadDataList( currentPageName, addCategories, loadDataList ) )
    table.insert( res, formatUsedTemplatesList( currentPageName, addCategories, usedTemplateList ) )
    table.insert( res, formatRequiredByList( currentPageName, addCategories, whatModulesLinkHere ) )


     return table.concat( res )
     return table.concat( res )
Line 554: Line 582:


return p
return p
-- </nowiki>