This wiki has had no edits or log actions made within the last 45 days and has been automatically marked as inactive. If you would like to prevent this wiki from being closed, please start showing signs of activity here. If there are no signs of this wiki being used within the next 15 days, this wiki will be closed in accordance to the Dormancy Policy (which all wiki founders accept when requesting a wiki). If this wiki is closed and no one reopens it 135 days from now, this wiki will become eligible for deletion. Note: If you are a bureaucrat, you can go to Special:ManageWiki and uncheck "inactive" yourself.

Module:DependencyList

From Persist Online Wiki

Documentation for this module may be created at Module:DependencyList/doc

-- <nowiki>
local p = {}
local libraryUtil = require( 'libraryUtil' )
local enum = require( 'Module:Enum' )
local yn = require( 'Module:Yesno' )
local param = require( 'Module:Paramtest' )
local dpl = require( 'Module:DPLlua' )
local moduleIsUsed = false
local COLLAPSE_LIST_LENGTH_THRESHOLD = 5
local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30
local dynamicRequireListQueryCache = {}

--- 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 search in
---@param varName string
---@return string
local function substVarValue( content, varName )
    local res = content:match( varName .. '%s*=%s*(%b""%s-%.*)' ) or content:match( varName .. "%s*=%s*(%b''%s-%.*)" ) or ''
    if res:find( '^(["\'])[Mm]odule:[%S]+%1' ) and not res:find( '%.%.' ) and not res:find( '%%%a' ) then
        return mw.text.trim( res )
    else
        return ''
    end
end

---@param capture string
---@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

    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

--- Used in case a construct like 'require( "Module:wowee/" .. isTheBest )' is found.
--- Will return a list of pages which satisfy this pattern where 'isTheBest' can take any value.
---@param query string
---@return string[]     Sequence of strings
local function getDynamicRequireList( query )
    if query:find( '%.%.' ) then
        query = mw.text.split( query, '..', true )
        query = enum.map( query, function(x) return mw.text.trim(x) end )
        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:', '' )

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

    if dynamicRequireListQueryCache[ query ] then
        return dynamicRequireListQueryCache[ query ]
    end

    local list = dpl.ask{
        namespace = 'Module',
        titlematch = query,
        nottitlematch = '%/doc|'..query..'/%',
        distinct = 'strict',
        ignorecase = true,
        ordermethod = 'title',
        count = MAX_DYNAMIC_REQUIRE_LIST_LENGTH + 1,
        skipthispage = 'no',
        allowcachedresults = true,
        cacheperiod = 604800 -- One week
    }

    if #list > MAX_DYNAMIC_REQUIRE_LIST_LENGTH then
        list = { 'Module:' .. query }
    end

    dynamicRequireListQueryCache[ query ] = list

    return list
end

--- Returns a list of modules loaded and required by module 'moduleName'.
---@param moduleName string
---@param searchForUsedTemplates boolean
---@return string[], string[], string[]
local function getRequireList( moduleName, searchForUsedTemplates )
    local content = mw.title.new( moduleName ):getContent()
    local requireList = {}
    local loadDataList = {}
    local usedTemplateList = {}
    local dynamicRequirelist = {}
    local dynamicLoadDataList = {}

    assert( param.has_content( content ), string.format( '%s does not exist', moduleName ) )

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

    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 = extractModuleName( match, content )

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

            if match == 'LibraryUtil' then
                match = 'Module:LibraryUtil'
            end

            table.insert( requireList, match )
        end
    end

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

        if match:find( '%.%.' ) or match:find( '%%%a' ) then
            for _, x in ipairs( getDynamicRequireList( match ) ) do
                table.insert( dynamicLoadDataList, x )
            end
        elseif match ~= '' then
            match = formatModuleName( match )
            table.insert( loadDataList, match )
        end
    end

    for func, match in string.gmatch( content, 'pcall%s*%(([^,]+),([^%),]+)' ) do
        func = mw.text.trim( func )
        match = mw.text.trim( match )

        if func == 'require' then
            for _, x in ipairs( getDynamicRequireList( match ) ) do
                table.insert( dynamicRequirelist, x )
            end
        elseif func == 'mw.loadData' then
            for _, x in ipairs( getDynamicRequireList( match ) ) do
                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', '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

    dynamicRequirelist = enum.reject( dynamicRequirelist, function(x) return (enum.contains(requireList, x) or enum.contains(loadDataList, x)) end )
    dynamicLoadDataList = enum.reject( dynamicLoadDataList, function(x) return (enum.contains(requireList, x) or enum.contains(loadDataList, x)) end )
    requireList = enum.insert( requireList, dynamicRequirelist )
    requireList = enum.unique( requireList )
    loadDataList = enum.insert( loadDataList, dynamicLoadDataList )
    loadDataList = enum.unique( loadDataList )
    usedTemplateList = enum.unique( usedTemplateList )
    table.sort( requireList )
    table.sort( loadDataList )
    table.sort( usedTemplateList )

    return requireList, loadDataList, usedTemplateList
end

--- Returns a list with module and function names used in all '{{#Invoke:moduleName|funcName}}' found on page 'templateName'.
---@param templateName string
---@return table<string, string>[]
local function getInvokeCallList( templateName )
    local content = mw.title.new( templateName ):getContent()
    local invokeList = {}

    assert( param.has_content( content ), string.format( '%s does not exist', templateName ) )

    for moduleName, funcName in string.gmatch( content, '{{[{|safeubt:}]-#[Ii]nvoke:([^|]+)|([^}|]+)[^}]*}}' ) do
        moduleName = string.format( 'Module:%s', mw.text.trim( moduleName ) )
        moduleName = formatModuleName( moduleName )
        funcName = mw.text.trim( funcName )
        table.insert( invokeList, {moduleName=moduleName, funcName=funcName} )
    end

    invokeList = enum.unique( invokeList, function(x) return x.moduleName..x.funcName end )
    table.sort( invokeList, function(x, y) return x.moduleName..x.funcName < y.moduleName..y.funcName end )

    return invokeList
end

--- Returns a list with TemplateStyles found on page 'templateName'.
---@param templateName string
---@return table<string>[]
local function getTemplateStylesList( templateName )
    local content = mw.title.new( templateName ):getContent()
    local styleList = {}

    assert( param.has_content( content ), string.format( '%s does not exist', templateName ) )

    for styleName in string.gmatch( content, '<templatestyles src="(.+)"' ) do
    	if string.find( styleName, ':', 1, true ) then
    		styleName = mw.text.trim( styleName )
    	else
    		styleName = string.format( 'Template:%s', mw.text.trim( styleName ) )
    	end
        styleName = formatModuleName( styleName )
        table.insert( styleList, {styleName=styleName} )
    end

    styleList = enum.unique( styleList, function(x) return x.styleName end )
    table.sort( styleList, function(x, y) return x.styleName < y.styleName end )

    return styleList
end

---@param pageName string
---@param addCategories boolean
---@return string
local function messageBoxUnused( pageName, addCategories )
    local html = mw.html.create( 'div' ):addClass( 'mbox mbox-med mbox-unusedmodule' ):attr( 'role', 'presentation')
    html:tag( 'span' )
            :addClass( 'mbox-title' )
            :tag( 'span' )
                :addClass( 'mbox-icon metadata' )
                :wikitext( '[[File:WikimediaUI-Alert.svg|14px|link=]]' )
                :done()
            :wikitext( 'This module is unused.' )
            :done()
        :tag( 'span' )
            :addClass( 'mbox-text' )
            :wikitext(
                string.format( 'This module is neither invoked by a template nor required/loaded by 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 )
end

local function collapseList( list, id, listType )
    local text = string.format( '%d %s', #list, listType )
    local button = '<span>' .. text .. ':</span>'
    list = enum.map( list, function(x) return '\n# '..x end )
    local content = '\n'..table.concat( list )..'\n\n'

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

--- Creates a link to [[Special:Search]] showing all pages found by getDynamicRequireList() in case it found more than MAX_DYNAMIC_REQUIRE_LIST_LENGTH pages.
---@param query string      @This will be in a format like 'Module:Wowee/%' or 'Module:Wowee/%/data'
---@return string
local function formatDynamicQueryLink( query )
    local prefix = query:match( '^([^/]+)' )
    local linkText = query:gsub( '%%', '&lt; ... &gt;' )

    query = query:gsub( '^Module:',  '' )

    query = query:gsub( '([^/]+)/?', function ( match )
        if match == '%' then
            return '\\/[^\\/]+'
        else
            return '\\/"' .. match .. '"'
        end
    end )

    query = query:gsub( '^\\/', '' )

    query = string.format(
        'intitle:/%s%s/i -intitle:/%s\\/""/i -intitle:doc prefix:"%s"',
        query,
        query:find( '"$' ) and '' or '""',
        query,
        prefix
    )

    return string.format( '<span class="plainlinks">[%s %s]</span>', tostring( mw.uri.fullUrl( 'Special:Search', { search = query } ) ), linkText )
end

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

    for _, item in ipairs( invokeList ) do
        table.insert( res, string.format(
            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' invokes function '''%s''' in [[%s]] using [[Star Citizen:Lua|Lua]].</div></div>",
            templateName,
            item.funcName,
            item.moduleName
        ) )
    end

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

    return table.concat( res )
end

---@param moduleName string
---@param addCategories boolean
---@param whatLinksHere string    @A list generated by a dpl of pages in the Template namespace which link to moduleName.
---@return string
local function formatInvokedByList( moduleName, addCategories, whatLinksHere )
    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:lower() == moduleName:lower()
        end )
    end )

    local invokedByList = {}

    for _, template in ipairs( templateData ) do
        for _, invoke in ipairs( template.invokeList ) do
            table.insert( invokedByList, string.format( "function '''%s''' is invoked by [[%s]]", invoke.funcName, template.templateName ) )
        end
    end

    table.sort( invokedByList)

    local res = {}

    if #invokedByList > COLLAPSE_LIST_LENGTH_THRESHOLD then
        table.insert( res, string.format(
            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' is invoked by %s.</div></div>",
            moduleName,
            collapseList( invokedByList, 'invokedBy', 'templates' )[1]
        ) )
    else
	    for _, item in ipairs( invokedByList ) do
	        table.insert( res, string.format(
	            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' is invoked by %s.</div></div>",
	            moduleName,
	            item
	        ) )
	    end
    end

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

    return table.concat( res )
end

---@param moduleName string
---@param addCategories boolean
---@param whatLinksHere string      @A list generated by a dpl of pages in the Module namespace which link to moduleName.
---@return string
local function formatRequiredByList( moduleName, addCategories, whatLinksHere )
    local childModuleData = enum.map( whatLinksHere, function ( title )
        local requireList, loadDataList = getRequireList( title )
        return {name=title, requireList=requireList, loadDataList=loadDataList}
    end )

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

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

    if #requiredByList > 0 or #loadedByList > 0 then
        moduleIsUsed  = true
    end

    if #requiredByList > COLLAPSE_LIST_LENGTH_THRESHOLD then
        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 role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' is required by %s.</div></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 role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' is loaded by %s.</div></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 #requireList > COLLAPSE_LIST_LENGTH_THRESHOLD then
       requireList = collapseList( requireList, 'require', 'modules' )
    end

    for _, requiredModuleName in ipairs( requireList ) do
        table.insert( res, string.format(
            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' requires %s.</div></div>",
            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 #loadDataList > COLLAPSE_LIST_LENGTH_THRESHOLD then
        loadDataList = collapseList( loadDataList, 'loadData', 'modules' )
    end

    for _, loadedModuleName in ipairs( loadDataList ) do
        table.insert( res, string.format(
            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' loads data from %s.</div></div>",
            currentPageName,
            loadedModuleName
        ) )
    end

    if #loadDataList > 0 then
        table.insert( res, (addCategories and '[[Category:Modules using data]]' or '') )
    end

    return table.concat( res )
end

local function formatUsedTemplatesList( currentPageName, addCategories, usedTemplateList )
    local res = {}

    if #usedTemplateList > COLLAPSE_LIST_LENGTH_THRESHOLD then
        usedTemplateList = collapseList( usedTemplateList, 'usedTemplates', 'templates' )
    end

    for _, templateName in ipairs( usedTemplateList ) do
        table.insert( res, string.format(
            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' transcludes  [[%s]] using <samp>frame:preprocess()</samp>.</div></div>",
            currentPageName,
            templateName
        ) )
    end

    return table.concat( res )
end

---@param templateName string
---@param addCategories boolean
---@param styleList table<string>[]    @This is the list returned by getTemplateStylesList()
---@return string
local function formatTemplateStylesList( templateName, addCategories, styleList )
    local category = addCategories and '[[Category:Templates using TemplateStyles]]' or ''
    local res = {}

    for _, item in ipairs( styleList ) do
        table.insert( res, string.format(
            "<div role='note' class='hatnote'><div class=hatnote-container navigation-not-searchable'><span class='hatnote-icon metadata'>[[File:WikimediaUI-Code.svg|14px|link=]]</span>'''%s''' invokes [[%s]] using [[Star Citizen:TemplateStyles|TemplateStyles]].</div></div>",
            templateName,
            item.styleName
        ) )
    end

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

    return table.concat( res )
end

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

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

    local title = mw.title.getCurrentTitle()

    -- Leave early if not in module, template namespace or if module is part of exchange or data groups
    if param.is_empty( currentPageName ) and (
        ( not enum.contains( {'Module', 'Template'}, title.nsText ) ) or
        ( title.nsText == 'Module' and ( enum.contains( {'Exchange', 'Exchange historical', 'Data'}, title.text:match( '^(.-)/' ) ) ) )
    ) then
        return ''
    end
    
    currentPageName = param.default_to( currentPageName, title.fullText )
    currentPageName = string.gsub( currentPageName, '/[Dd]oc$', '' )
    currentPageName = formatModuleName( currentPageName )
    addCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) )
    moduleIsUsed = yn( param.default_to( isUsed, false ) )
    
    if title.text:match( '^(.-)/' ) == 'Sandbox' then
    	moduleIsUsed = true -- Don't show sandbox modules as unused
    end

    if currentPageName:find( '^Template:' ) then
        local invokeList = getInvokeCallList( currentPageName )
        local styleList = getTemplateStylesList( currentPageName )
        return formatInvokeCallList( currentPageName, addCategories, invokeList ) .. formatTemplateStylesList( currentPageName, addCategories, styleList )
    end

    local whatTemplatesLinkHere, whatModulesLinkHere = dpl.ask( {
        namespace = 'Template',
        linksto = currentPageName,
        distinct = 'strict',
        ignorecase = true,
        ordermethod = 'title',
        allowcachedresults = true,
        cacheperiod = 604800 -- One week
    }, {
        namespace = 'Module',
        linksto = currentPageName,
        nottitlematch = '%/doc|Exchange/%|Data/%|' .. currentPageName:gsub( 'Module:', '' ),
        distinct = 'strict',
        ignorecase = true,
        ordermethod = 'title',
        allowcachedresults = true,
        cacheperiod = 604800 -- One week
    } )

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

    requireList = enum.map( requireList, function ( moduleName )
        if moduleName:find( '%%' ) then
            return formatDynamicQueryLink( moduleName )
        else
            return '[[' .. moduleName .. ']]'
        end
    end )

    loadDataList = enum.map( loadDataList, function ( moduleName )
        if moduleName:find( '%%' ) then
            return formatDynamicQueryLink( moduleName )
        else
            return '[[' .. moduleName .. ']]'
        end
    end )

    usedTemplateList = enum.map( usedTemplateList, function( templateName )
        if string.find( templateName, ':' ) then -- Real templates are prefixed by a namespace, magic words are not
            return '[['..templateName..']]'
        else
            return "'''&#123;&#123;"..templateName.."&#125;&#125;'''" -- Magic words don't have a page so make them bold instead
        end
    end )

    local res = {}

    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 ) )

    if not moduleIsUsed then
        table.insert( res, 1, messageBoxUnused( currentPageName:gsub( 'Module:', '' ), addCategories ) )
    end

    return table.concat( res )
end

return p
-- </nowiki>
Cookies help us deliver our services. By using our services, you agree to our use of cookies.