Открыть меню
Открыть персональное меню
Вы не представились системе
Your IP address will be publicly visible if you make any edits.

Модуль:DependencyList: различия между версиями

Материал из Энциклопедия Тренерон
м (1 версия импортирована)
ru>Alistair3149
Нет описания правки
Строка 1: Строка 1:
--- Based on Module:DependencyList from RuneScape Wiki
--- Modified to use SMW instead of DPL
--- @see https://runescape.wiki/w/Module:DependencyList
require("strict");
require("strict");


Строка 6: Строка 10:
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 userError = require("Module:User error")
local userError = require("Module:User error")
local mHatnote = require('Module:Hatnote')
local hatnote = require('Module:Hatnote')._hatnote
local mHatlist = require('Module:Hatnote list')
local mHatlist = require('Module:Hatnote list')
local mbox = require( 'Module:Mbox' )._mbox
local TNT = require( 'Module:Translate' ):new()
local moduleIsUsed = false
local moduleIsUsed = false
local COLLAPSE_LIST_LENGTH_THRESHOLD = 1
local shouldAddCategories = false
local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30
local COLLAPSE_LIST_LENGTH_THRESHOLD = 5
local dynamicRequireListQueryCache = {}
local dynamicRequireListQueryCache = {}
local NS_MODULE_NAME =  mw.site.namespaces[ 828 ].name
local NS_TEMPLATE_NAME = mw.site.namespaces[ 10 ].name
--- FIXME: This should go to somewhere else, like Module:Common
--- Calls TNT with the given key
---
--- @param key string The translation key
--- @return string If the key was not found in the .tab page, the key is returned
local function translate( key, ... )
    local success, translation = pcall( TNT.format, 'Module:DependencyList/i18n.json', key or '', ... )
    if not success or translation == nil then
        return key
    end
    return translation
end


local builtins = {
local builtins = {
--[[
    ["libraryUtil"] = {
["libraryUtil"] = {
        link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#libraryUtil",
link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#libraryUtil",
        categories = {},
categories = {},
    },
}
[ "strict" ] = {
]]
["strict"] = {
link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#strict",
link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#strict",
categories = { "Strict mode modules" },
categories = { translate( 'category_strict_mode_modules' ) },
},
},
};
}
 


--- 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'.
Строка 34: Строка 59:
local function substVarValue( content, varName )
local function substVarValue( content, varName )
     local res = content:match( varName .. '%s*=%s*(%b""%s-%.*)' ) or content:match( varName .. "%s*=%s*(%b''%s-%.*)" ) or ''
     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
     if res:find( '^(["\'])[Mm]odule?:[%S]+%1' ) and not res:find( '%.%.' ) and not res:find( '%%%a' ) then
         return mw.text.trim( res )
         return mw.text.trim( res )
     else
     else
Строка 40: Строка 65:
     end
     end
end
end


---@param capture string
---@param capture string
Строка 55: Строка 81:
     return capture
     return capture
end
end


---@param str string
---@param str string
---@return string
---@return string
local function formatPageName( str )
local function formatPageName( str )
     local name = mw.text.trim(str)
     local name = mw.text.trim( str )
         :gsub( '^([\'\"])(.-)%1$', function(_, x) return x end ) -- Only remove quotes at start and end of string if both are the same type
         :gsub( '^([\'\"])(.-)%1$', function( _, x ) return x end ) -- Only remove quotes at start and end of string if both are the same type
         :gsub( '_', ' ' )
         :gsub( '_', ' ' )
         :gsub( '^.', string.upper )
         :gsub( '^.', mw.ustring.upper )
         :gsub( ':.', string.upper )
         :gsub( ':.', mw.ustring.upper )


     return name
     return name
end
end


---@param str string
---@param str string
---@param allowBuiltins? boolean
---@return string
---@return string
local function formatModuleName( str, allowBuiltins )
local function formatModuleName( str, allowBuiltins )
if allowBuiltins then
if allowBuiltins then
local name = mw.text.trim(str)
local name = mw.text.trim( str )
-- Only remove quotes at start and end of string if both are the same type
-- Only remove quotes at start and end of string if both are the same type
:gsub([[^(['"])(.-)%1$]], function(_, x) return x end);
            :gsub([[^(['"])(.-)%1$]], '%2')


local builtin = builtins[name];
        if builtins[name] then
if builtin then
            return name
return builtin.link .. "|" .. name, builtin;
        end
end
end
end


     local module = formatPageName( str )
     local module = formatPageName( str )


     if not string.find( module, '^[Mm]odule:' ) then
     if not mw.ustring.find( module, '^[Mm]odule?:' ) then
         module = 'Module:' .. module
         module = NS_MODULE_NAME .. ':' .. module
     end
     end


     return module
     return module
end
end


local function dualGmatch( str, pat1, pat2 )
local function dualGmatch( str, pat1, pat2 )
     local f1 = string.gmatch( str, pat1 )
     local f1 = mw.ustring.gmatch( str, pat1 )
     local f2 = string.gmatch( str, pat2 )
     if pat2 then
    return function()
        local f2 = mw.ustring.gmatch( str, pat2 )
        return f1() or f2()
        return function()
            return f1() or f2()
        end
    else
        return f1
     end
     end
end
end
local function isDynamicPath( str )
    return mw.ustring.find( str, '%.%.' ) or mw.ustring.find( str, '%%%a' )
end


--- Used in case a construct like 'require( "Module:wowee/" .. isTheBest )' is found.
--- 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.
--- 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[]
local function getDynamicRequireList( query )
local function getDynamicRequireList( query )
local isDynamic = true;
     if query:find( '%.%.' ) then
     if query:find( '%.%.' ) then
         query = mw.text.split( query, '..', true )
         query = mw.text.split( query, '..', true )
         query = arr.map( query, function(x) return mw.text.trim(x) end )
         query = arr.map( query, function( x ) return mw.text.trim( x ) end )
         query = arr.map( query, function(x) return (x:match('^[\'\"](.-)[\'\"]$') or '%') end )
         query = arr.map( query, function( x ) return ( x:match('^[\'\"](.-)[\'\"]$') or '%') end )
         query = table.concat( query )
         query = table.concat( query )
     else
     else
         local _; _, query = query:match( '(["\'])(.-)%1' )
         local _, _query = query:match( '(["\'])(.-)%1' )
 
         query = _query:gsub( '%%%a', '%%' )
         if query == nil then
return {}, isDynamic
        end
       
        local replacements;
        query, replacements = query:gsub( '%%%a', '%%' )
if replacements == 0 then
isDynamic = false;
end
     end
     end
     query = query:gsub( '^[Mm]odule:', '' )
     query = query:gsub( '^[Mm]odule:', '' )
   
    if query:find( '^[Dd]ata/' ) then
        return { 'Module:' .. query }, isDynamic;  -- This format will later be used by formatDynamicQueryLink()
    end


     if dynamicRequireListQueryCache[ query ] then
     if dynamicRequireListQueryCache[ query ] then
         return dynamicRequireListQueryCache[ query ], isDynamic;
         return dynamicRequireListQueryCache[ query ];
     end
     end


     local list = dpl.ask{
     return {};
        namespace = 'Module',
end
        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, isDynamic;
end


--- 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
---@param searchForUsedTemplates boolean
---@param searchForUsedTemplates boolean|nil
---@return string[], string[], string[], string[]
---@return table<string, string[]>
local function getRequireList( moduleName, searchForUsedTemplates )
local function getRequireList( moduleName, searchForUsedTemplates )
     local content = mw.title.new( moduleName ):getContent()
     local content = mw.title.new( moduleName ):getContent()
     local requireList = arr{}
     local requireList = arr{}
     local loadDataList = arr{}
     local loadDataList = arr{}
    local loadJsonDataList = arr{}
     local usedTemplateList = arr{}
     local usedTemplateList = arr{}
     local dynamicRequirelist = arr{}
     local dynamicRequirelist = arr{}
     local dynamicLoadDataList = arr{}
     local dynamicLoadDataList = arr{}
    local dynamicLoadJsonDataList = arr{}
     local extraCategories = arr{}
     local extraCategories = arr{}


     assert( content ~= nil, string.format( '%s does not exist', moduleName ) )
     assert( content ~= nil, translate( 'message_not_exists', moduleName ) )


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


     for match in dualGmatch( content, 'require%s*(%b())', 'require%s*((["\'])%s*[Mm]odule:.-%2)' ) do
     local function getList( pat1, pat2, list, dynList )
        match = mw.text.trim( match )
         for match in dualGmatch( content, pat1, pat2 ) do
        match = extractModuleName( match, content )
            match = mw.text.trim( match )
 
            local name = 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
        local builtin;
            match, builtin = formatModuleName( match, true )
            table.insert( requireList, match )
           
if builtin then
local builtinCategories = builtin.categories;
if type(builtinCategories) == "table" then
for _, x in ipairs(builtinCategories) do
table.insert(extraCategories, x);
end
end
end
         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
            if isDynamicPath( name ) then
            for _, x in ipairs( getDynamicRequireList( match ) ) do
                dynList:insert( getDynamicRequireList( name ), true )
                table.insert( dynamicLoadDataList, x )
             elseif name ~= '' then
             end
                name = formatModuleName( name, true )
        elseif match ~= '' then
                table.insert( list, name )
            match = formatModuleName( match, true )
            table.insert( loadDataList, match )
        end
    end
   
    for match in dualGmatch( content, 'mw%.loadJsonData%s*(%b())', 'mw%.loadJsonData%s*((["\'])%s*[Mm]odule:.-%2)' ) do
        match = mw.text.trim( match )
        match = extractModuleName( match, content )


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


     for func, match in string.gmatch( content, 'pcall%s*%(([^,]+),([^%),]+)' ) do
     getList( 'require%s*(%b())', 'require%s*((["\'])%s*[Mm]odule:.-%2)', requireList, dynamicRequirelist )
        func = mw.text.trim( func )
    getList( 'mw%.loadData%s*(%b())', 'mw%.loadData%s*((["\'])%s*[Mm]odule:.-%2)', loadDataList, dynamicLoadDataList )
        match = mw.text.trim( match )
    getList( 'mw%.loadJsonData%s*(%b())', 'mw%.loadJsonData%s*((["\'])%s*[Mm]odule:.-%2)', loadJsonDataList, dynamicLoadJsonDataList )
 
    getList( 'pcall%s*%(%s*require%s*,([^%),]+)', nil, requireList, dynamicRequirelist )
local dynList, isDynamic;
    getList( 'pcall%s*%(%s*mw%.loadData%s*,([^%),]+)', nil, loadDataList, dynamicLoadDataList )
        if func == 'require' then
    getList( 'pcall%s*%(%s*mw%.loadJsonData%s*,([^%),]+)', nil, loadJsonDataList, dynamicLoadJsonDataList )
dynList, isDynamic = getDynamicRequireList(match);
if (isDynamic == false and #dynList == 1) then
table.insert(requireList, dynList[1]);
else for _, x in ipairs(dynList) do
                table.insert( dynamicRequirelist, x )
end end
        elseif func == 'mw.loadData' then
dynList, isDynamic = getDynamicRequireList(match);
if (isDynamic == false and #dynList == 1) then
table.insert(loadDataList, dynList[1]);
else for _, x in ipairs(dynList) do
                table.insert( dynamicLoadDataList, x )
end end
        end
    end


     if searchForUsedTemplates then
     if searchForUsedTemplates then
         for preprocess in string.gmatch( content, ':preprocess%s*(%b())' ) do
         for preprocess in mw.ustring.gmatch( content, ':preprocess%s*(%b())' ) do
             local function recursiveGMatch( str, pat )
             local function recursiveGMatch( str, pat )
                 local list = {}
                 local list = {}
                 local i = 0
                 local i = 0
                 repeat
                 repeat
                     for match in string.gmatch( list[i] or str, pat ) do
                     for match in mw.ustring.gmatch( list[ i ] or str, pat ) do
                         table.insert( list, match )
                         table.insert( list, match )
                     end
                     end
Строка 262: Строка 221:
                 return function()
                 return function()
                     i = i + 1
                     i = i + 1
                     return list[i]
                     return list[ i ]
                 end
                 end
             end
             end


             for template in recursiveGMatch( preprocess, '{(%b{})}' ) do
             for template in recursiveGMatch( preprocess, '{(%b{})}' ) do
                 local name = string.match( template, '{(.-)[|{}]' )
                 local name = mw.ustring.match( template, '{(.-)[|{}]' )
                 if name ~= '' then
                 if name ~= '' then
                     if name:find( ':' ) then
                     if name:find( ':' ) then
                         local ns = name:match( '^(.-):' )
                         local ns = name:match( '^(.-):' )
                         if arr.contains( {'', 'template', 'user'}, ns:lower() ) then
                         if arr.contains( { '', 'template', 'user' }, ns:lower() ) then
                             table.insert( usedTemplateList, name )
                             table.insert( usedTemplateList, name )
                         elseif ns == ns:upper() then
                         elseif ns == ns:upper() then
Строка 288: Строка 247:
     end
     end


     requireList = requireList .. dynamicRequirelist:reject( loadDataList )
     requireList = requireList .. dynamicRequirelist
     requireList = requireList:unique()
     requireList = requireList:unique()
     loadDataList = loadDataList .. dynamicLoadDataList:reject( requireList )
     loadDataList = loadDataList .. dynamicLoadDataList
     loadDataList = loadDataList:unique()
     loadDataList = loadDataList:unique()
    loadJsonDataList = loadJsonDataList .. dynamicLoadJsonDataList
    loadJsonDataList = loadJsonDataList:unique()
     usedTemplateList = usedTemplateList:unique()
     usedTemplateList = usedTemplateList:unique()
     extraCategories = extraCategories:unique()
     extraCategories = extraCategories:unique()
    table.sort( requireList )
    table.sort( loadDataList )
    table.sort( usedTemplateList )
     table.sort( extraCategories )
     table.sort( extraCategories )


     return requireList, loadDataList, usedTemplateList, extraCategories
     return {
        requireList = requireList,
        loadDataList = loadDataList,
        loadJsonDataList = loadJsonDataList,
        usedTemplateList = usedTemplateList,
        extraCategories = extraCategories
    }
end
end


--- Returns a list with module and function names used in all '{{#Invoke:moduleName|funcName}}' found on page 'templateName'.
--- Returns a list with module and function names used in all '{{#Invoke:moduleName|funcName}}' found on page 'templateName'.
Строка 309: Строка 274:
     local invokeList = {}
     local invokeList = {}


     assert( content ~= nil, string.format( '%s does not exist', templateName ) )
     assert( content ~= nil, translate( 'message_not_exists', templateName ) )


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


     invokeList = arr.unique( invokeList, function(x) return x.moduleName..x.funcName end )
     invokeList = arr.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 )
     table.sort( invokeList, function( x, y ) return x.moduleName..x.funcName < y.moduleName..y.funcName end )


     return invokeList
     return invokeList
end
end


---@param pageName string
---@param addCategories boolean
---@return string
---@return string
local function messageBoxUnused( pageName, addCategories )
local function messageBoxUnused()
local mbox = require( 'Module:Mbox' )._mbox
local category = shouldAddCategories and '[[Category:' .. translate( 'category_unused_module' ) .. ']]' or ''
local category = addCategories and '[[Category:Unused modules]]' or ''
 
return mbox(
return mbox(
'This module is unused.',
translate( 'message_unused_module_title' ),
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.',
translate( 'message_unused_module_desc' ),
pageName
),
{ icon = 'WikimediaUI-Alert.svg' }
{ icon = 'WikimediaUI-Alert.svg' }
) .. category
) .. category
end
end


local function collapseList( list, id, listType )
--- Returns the wikitext for the message template (mbox/hatnote)
     local text = string.format( '%d %s', #list, listType )
---@param msgKey string message key in /i18n.json
     local button = '<span>' .. text .. ':</span>&nbsp;'
---@param pageName string page name used for the message
    local content = mHatlist.andList( list, false )
---@param list table
---@param listType string type of the page list used for the message
---@return string
local function getDependencyListWikitext( msgKey, pageName, list, listType )
     local listLabel = mw.ustring.format( '%d %s', #list, listType )
     local listContent = mHatlist.andList( list, false )


     return { tostring( button ) .. tostring( content ) }
     --- Return mbox
    if #list > COLLAPSE_LIST_LENGTH_THRESHOLD then
        return mbox(
            translate( msgKey, pageName, listLabel ),
            listContent,
            { icon = 'WikimediaUI-Code.svg' }
        )
    --- Return hatnote
    else
        return hatnote(
            translate( msgKey, pageName, listContent ),
            { icon='WikimediaUI-Code.svg' }
        )
    end
end
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.
--- Creates a link to [[Special:Search]] showing all pages found by getDynamicRequireList() in case it found more than MAX_DYNAMIC_REQUIRE_LIST_LENGTH pages.
Строка 356: Строка 336:
     local linkText = query:gsub( '%%', '&lt; ... &gt;' )
     local linkText = query:gsub( '%%', '&lt; ... &gt;' )


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


     query = query:gsub( '([^/]+)/?', function ( match )
     query = query:gsub( '([^/]+)/?', function ( match )
Строка 368: Строка 348:
     query = query:gsub( '^\\/', '' )
     query = query:gsub( '^\\/', '' )


     query = string.format(
     query = mw.ustring.format(
         'intitle:/%s%s/i -intitle:/%s\\/""/i -intitle:doc prefix:"%s"',
         'intitle:/%s%s/i -intitle:/%s\\/""/i -intitle:doc prefix:"%s"',
         query,
         query,
Строка 376: Строка 356:
     )
     )


     return string.format( '<span class="plainlinks">[%s %s]</span>', tostring( mw.uri.fullUrl( 'Special:Search', { search = query } ) ), linkText )
     return mw.ustring.format( '<span class="plainlinks">[%s %s]</span>', tostring( mw.uri.fullUrl( 'Special:Search', { search = query } ) ), linkText )
end
 
--- Helper function to return the wikitext of the templates and categories
---@param currentPageName string
---@param pageList table|nil
---@param pageType string
---@param message string
---@param category string|nil
---@return string
local function formatDependencyList( currentPageName, pageList, pageType, message, category )
    local res = {}
 
    if type( pageList ) == 'table' and #pageList > 0 then
        table.sort( pageList )
        table.insert( res, getDependencyListWikitext( message, currentPageName, pageList, pageType ) )
 
        if shouldAddCategories and category then
            table.insert( res, mw.ustring.format( '[[Category:%s]]', category ) )
        end
    end
 
    return table.concat( res )
end
end


---@param templateName string
---@param templateName string
---@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, addCategories, invokeList )
local function formatInvokeCallList( templateName, invokeList )
     local category = addCategories and '[[Category:Lua-based templates]]' or ''
     local category = shouldAddCategories and '[[Category:' .. translate( 'category_lua_based_template' ) .. ']]' or ''
     local res = {}
     local res = {}


     for _, item in ipairs( invokeList ) do
     for _, item in ipairs( invokeList ) do
    local msg = string.format(
        local msg = translate(
    "'''%s''' invokes function '''%s''' in [[%s]] using [[Star Citizen:Lua|Lua]].",
                'message_invokes_function',
     templateName,
     templateName,
     item.funcName,
     item.funcName,
     item.moduleName
     item.moduleName
     )
     )
         table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
         table.insert( res, hatnote( msg, { icon = 'WikimediaUI-Code.svg' } ) )
     end
     end


Строка 403: Строка 405:
     return table.concat( res )
     return table.concat( res )
end
end


---@param moduleName string
---@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.
---@param whatLinksHere string    @A list generated by a dpl of pages in the Template namespace which link to moduleName.
---@return string
---@return string
local function formatInvokedByList( moduleName, addCategories, whatLinksHere )
local function formatInvokedByList( moduleName, whatLinksHere )
     local function lcfirst( str )
     local function lcfirst( str )
return string.gsub( str, '^[Mm]odule:.', string.lower )
return mw.ustring.gsub( str, '^[Mm]odule?:.', mw.ustring.lower )
end
end


     local templateData = arr.map( whatLinksHere, function(x) return {templateName=x, invokeList=getInvokeCallList(x)} end )
     local templateData = arr.map( whatLinksHere, function( x ) return { templateName = x, invokeList = getInvokeCallList( x ) } end )
     templateData = arr.filter( templateData, function(x)
     templateData = arr.filter( templateData, function( x )
         return arr.any( x.invokeList, function(y)
         return arr.any( x.invokeList, function( y )
             return lcfirst(y.moduleName) == lcfirst(moduleName)
             return lcfirst( y.moduleName ) == lcfirst( moduleName )
         end )
         end )
     end )
     end )
Строка 424: Строка 426:
     for _, template in ipairs( templateData ) do
     for _, template in ipairs( templateData ) do
         for _, invoke in ipairs( template.invokeList ) do
         for _, invoke in ipairs( template.invokeList ) do
             table.insert( invokedByList, string.format( "function '''%s''' is invoked by [[%s]]", invoke.funcName, template.templateName ) )
            --- NOTE: Somehow only templates aren't linked properly, not sure why
             table.insert( invokedByList, translate( 'message_function_invoked_by', invoke.funcName, '[[' .. template.templateName .. ']]' ) )
         end
         end
     end
     end


    table.sort( invokedByList)
     if #invokedByList > 0 then
 
    local res = {}
 
     if #invokedByList > COLLAPSE_LIST_LENGTH_THRESHOLD then
    local msg = string.format(
    "'''%s''' is invoked by %s.",
    moduleName,
    collapseList( invokedByList, 'invokedBy', 'templates' )[1]
    )
        table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
    else
    for _, item in ipairs( invokedByList ) do
    local msg = string.format(
    "'''%s's''' %s.",
    moduleName,
    item
    )
        table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
    end
    end
 
    if #templateData > 0 then
         moduleIsUsed = true
         moduleIsUsed = true
        table.insert( res, (addCategories and '[[Category:Template invoked modules]]' or '') )
     end
     end


     return table.concat( res )
     return formatDependencyList(
        moduleName,
        invokedByList,
        translate( 'list_type_templates' ),
        'message_module_functions_invoked_by',
        translate( 'category_template_invoked_modules' )
    )
end
end


---@param moduleName string
---@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.
---@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, addCategories, whatLinksHere )
local function formatRequiredByList( moduleName, whatLinksHere )
     local childModuleData = arr.map( whatLinksHere, function ( title )
     local childModuleData = arr.map( whatLinksHere, function ( title )
         local requireList, loadDataList = getRequireList( title )
         local lists = getRequireList( title )
         return {name=title, requireList=requireList, loadDataList=loadDataList}
         return { name = title, requireList = lists.requireList, loadDataList = lists.loadDataList .. lists.loadJsonDataList }
     end )
     end )


     local requiredByList = arr.map( childModuleData, function ( item )
     local requiredByList = arr.map( childModuleData, function ( item )
         if arr.any( item.requireList, function(x) return x:lower()==moduleName:lower() end ) then
         if arr.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 )
Строка 479: Строка 465:


     local loadedByList = arr.map( childModuleData, function ( item )
     local loadedByList = arr.map( childModuleData, function ( item )
         if arr.any( item.loadDataList, function(x) return x:lower()==moduleName:lower() end ) then
         if arr.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 )
Строка 489: Строка 475:


     if #requiredByList > 0 or #loadedByList > 0 then
     if #requiredByList > 0 or #loadedByList > 0 then
         moduleIsUsed = true
         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
     end


     local res = {}
     local res = {}


     for _, requiredByModuleName in ipairs( requiredByList ) do
     table.insert( res,
    local msg = string.format(
        formatDependencyList(
    "'''%s''' is required by %s.",
            moduleName,
    moduleName,
            requiredByList,
    requiredByModuleName
            translate( 'list_type_modules' ),
    )
            'message_required_by',
        table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
            translate( 'category_modules_required_by_modules' )
     end
        )
     )


     if #requiredByList > 0 then
     table.insert( res,
        table.insert( res, (addCategories and '[[Category:Modules required by modules]]' or '') )
        formatDependencyList(
    end
            moduleName,
 
            loadedByList,
    for _, loadedByModuleName in ipairs( loadedByList ) do
            translate( 'list_type_modules' ),
    local msg = string.format(
            'message_loaded_by',
    "'''%s''' is loaded by %s.",
            translate( 'category_module_data' )
    moduleName,
         )
    requiredByModuleName
     )
    )
        table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
    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
    local msg = string.format(
    "'''%s''' requires %s.",
    currentPageName,
    requiredModuleName
    )
         table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
     end
 
    if #requireList > 0 then
        table.insert( res, (addCategories and '[[Category:Modules requiring modules]]' or '') )
    end


     return table.concat( res )
     return table.concat( res )
end
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
    local msg = string.format(
    "'''%s''' loads data from %s.",
    currentPageName,
    loadedModuleName
    )
        table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
    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
    local msg = string.format(
    "'''%s''' transcludes  [[%s]] using <samp>frame:preprocess()</samp>.",
    currentPageName,
    templateName
    )
        table.insert( res, mHatnote._hatnote( msg, { icon='WikimediaUI-Code.svg' } ) )
    end
    return table.concat( res )
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.isUsed )
     return p._main( args[ 1 ], args.category, args.isUsed )
end
end


---@param currentPageName string|nil
---@param currentPageName string|nil
Строка 606: Строка 515:
function p._main( currentPageName, addCategories, 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, isUsed, {'boolean', 'string', 'nil'} )
     libraryUtil.checkTypeMulti( 'Module:RequireList._main', 3, isUsed, { 'boolean', 'string', 'nil' } )


     local title = mw.title.getCurrentTitle()
     local title = mw.title.getCurrentTitle()
Строка 613: Строка 522:
     -- Leave early if not in module or template namespace
     -- Leave early if not in module or template namespace
     if param.is_empty( currentPageName ) and
     if param.is_empty( currentPageName ) and
         ( not arr.contains( {'Module', 'Template'}, title.nsText ) ) then
         ( not arr.contains( { NS_MODULE_NAME, NS_TEMPLATE_NAME }, title.nsText ) ) 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 = mw.ustring.gsub( currentPageName, '/[Dd]o[ck]u?$', '' )
     currentPageName = formatPageName( currentPageName )
     currentPageName = formatPageName( currentPageName )
    addCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) )
     moduleIsUsed = yn( param.default_to( isUsed, false ) )
     moduleIsUsed = yn( param.default_to( isUsed, false ) )
    shouldAddCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) )


     if title.text:lower():find( 'sandbox' ) then
    -- Don't show sandbox and testcases modules as unused
     moduleIsUsed = true -- Don't show sandbox modules as unused
     if title.text:lower():find( 'sandbox' ) or title.text:lower():find( 'testcases' ) then
     moduleIsUsed = true
     end
     end


     if currentPageName:find( '^Template:' ) then
     if currentPageName:find( '^' .. NS_TEMPLATE_NAME .. ':' ) then
         local ok, invokeList = pcall( getInvokeCallList, currentPageName )
         local ok, invokeList = pcall( getInvokeCallList, currentPageName )
if ok then
if ok then
         return formatInvokeCallList( currentPageName, addCategories, invokeList )
         return formatInvokeCallList( currentPageName, invokeList )
         else
         else
return userError(invokeList)
return userError( invokeList )
end
end
     end
     end


     local whatTemplatesLinkHere, whatModulesLinkHere = dpl.ask( {
     local whatTemplatesLinkHere = {}
        namespace = 'Template',
    local whatModulesLinkHere = {}
        linksto = currentPageName,
 
        distinct = 'strict',
     local function cleanFrom( from )
        ignorecase = true,
         from = from or ''
        ordermethod = 'title',
         local parts = mw.text.split( from, '|', true )
        allowcachedresults = true,
        cacheperiod = 604800 -- One week
     }, {
         namespace = 'Module',
         linksto = currentPageName,
        nottitlematch = '%/doc%|' .. currentPageName:gsub( 'Module:', '' ),
        distinct = 'strict',
        ignorecase = true,
        ordermethod = 'title',
        allowcachedresults = true,
        cacheperiod = 604800 -- One week
    } )


    local requireList, loadDataList, usedTemplateList, extraCategories;
        if #parts == 2 then
    do
            local name = mw.ustring.gsub( parts[ 1 ], '%[%[:', '' )
    local ok;
            name = mw.ustring.gsub( name, '/[Dd]o[ck]u?', '' )
    ok, requireList, loadDataList, usedTemplateList, extraCategories = pcall(getRequireList, currentPageName, true);
 
    if not ok then
            return name
    return userError(requireList);
        end
    end
 
        return nil
     end
     end


     requireList = arr.map( requireList, function ( moduleName )
     local templatesRes = mw.smw.ask({
        '[[Links to::' .. currentPageName .. ']]',
        '[[Template:+]]',
        'sort=Links to',
        'order=asc',
        'mainlabel=from'
    }) or {}
 
    whatTemplatesLinkHere = arr.new( arr.condenseSparse( arr.map( templatesRes, function ( link )
        return cleanFrom( link[ 'from' ] )
    end ) ) ):unique()
 
    local moduleRes = mw.smw.ask( {
        '[[Links to::' .. currentPageName .. ']]',
        '[[Module:+]]',
        'sort=Links to',
        'order=asc',
        'mainlabel=from'
    } ) or {}
 
    whatModulesLinkHere = arr.new( arr.condenseSparse( arr.map( moduleRes, function ( link )
        return cleanFrom( link[ 'from' ] )
    end ) ) ):unique():reject( { currentPageName } )
 
    local ok, lists = pcall( getRequireList, currentPageName, true )
    if not ok then
        return userError( lists )
    end
 
    local requireList = arr.map( lists.requireList, function ( moduleName )
         if moduleName:find( '%%' ) then
         if moduleName:find( '%%' ) then
             return formatDynamicQueryLink( moduleName )
             return formatDynamicQueryLink( moduleName )
        elseif builtins[moduleName] then
            return '[[' .. builtins[moduleName].link .. '|' .. moduleName .. ']]'
         else
         else
             return '[[' .. moduleName .. ']]'
             return '[[' .. moduleName .. ']]'
Строка 672: Строка 602:
     end )
     end )


     loadDataList = arr.map( loadDataList, function ( moduleName )
     local loadDataList = arr.map( lists.loadDataList, function ( moduleName )
         if moduleName:find( '%%' ) then
         if moduleName:find( '%%' ) then
             return formatDynamicQueryLink( moduleName )
             return formatDynamicQueryLink( moduleName )
Строка 680: Строка 610:
     end )
     end )


     usedTemplateList = arr.map( usedTemplateList, function( templateName )
     local loadJsonDataList = arr.map( lists.loadJsonDataList, function ( moduleName )
         if string.find( templateName, ':' ) then -- Real templates are prefixed by a namespace, magic words are not
        if moduleName:find( '%%' ) then
            return formatDynamicQueryLink( moduleName )
        else
            return '[[' .. moduleName .. ']]'
        end
    end )
 
    local usedTemplateList = arr.map( lists.usedTemplateList, function( templateName )
         if mw.ustring.find( templateName, ':' ) then -- Real templates are prefixed by a namespace, magic words are not
             return '[['..templateName..']]'
             return '[['..templateName..']]'
         else
         else
Строка 690: Строка 628:
     local res = {}
     local res = {}


     table.insert( res, formatInvokedByList( currentPageName, addCategories, whatTemplatesLinkHere ) )
     table.insert( res, formatInvokedByList( currentPageName, whatTemplatesLinkHere ) )
     table.insert( res, formatRequireList( currentPageName, addCategories, requireList ) )
     table.insert( res, formatDependencyList( currentPageName, requireList, translate( 'list_type_modules' ), 'message_requires', translate( 'category_modules_required_by_modules' ) ) )
     table.insert( res, formatLoadDataList( currentPageName, addCategories, loadDataList ) )
     table.insert( res, formatDependencyList( currentPageName, loadDataList, translate( 'list_type_modules' ), 'message_loads_data_from', translate( 'category_modules_using_data' ) ) )
     table.insert( res, formatUsedTemplatesList( currentPageName, addCategories, usedTemplateList ) )
     table.insert( res, formatDependencyList( currentPageName, loadJsonDataList, translate( 'list_type_modules' ), 'message_loads_data_from', translate( 'category_modules_using_data' ) ) )
     table.insert( res, formatRequiredByList( currentPageName, addCategories, whatModulesLinkHere ) )
    table.insert( res, formatDependencyList( currentPageName, usedTemplateList, translate( 'list_type_templates' ), 'message_transcludes', nil ) )
     table.insert( res, formatRequiredByList( currentPageName, whatModulesLinkHere ) )


if addCategories then
if shouldAddCategories then
extraCategories = arr.map(extraCategories, function(categoryName)
local extraCategories = arr.map( lists.extraCategories, function( categoryName )
return "[[Category:" .. categoryName .. "]]";
return "[[Category:" .. categoryName .. "]]";
end);
end )
 
table.insert(res, table.concat(extraCategories));
table.insert( res, table.concat( extraCategories ) );
end
end


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


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


return p
return p
-- </nowiki>
-- </nowiki>

Версия от 23:50, 26 февраля 2024

Ошибка Lua на строке 565: attempt to index field 'smw' (a nil value). This template is meant to be used on module documentations.

{{DependencyList|<<Module name>>}}

All parameters are optional. If Module name is omitted the page name will be used instead; /doc is automatically removed.

If category is false then no categories will be added to the page, the default value is true.

If compact is true links will be separated by commas instead of every link on a new line.

Examples

{{DependencyList|<Module:For>}} Ошибка Lua на строке 565: attempt to index field 'smw' (a nil value).

{{DependencyList|<Module:Infobox>}} Ошибка Lua на строке 565: attempt to index field 'smw' (a nil value).


--- Based on Module:DependencyList from RuneScape Wiki
--- Modified to use SMW instead of DPL
--- @see https://runescape.wiki/w/Module:DependencyList

require("strict");

local p = {}
local libraryUtil = require( 'libraryUtil' )
local arr = require( 'Module:Array' )
local yn = require( 'Module:Yesno' )
local param = require( 'Module:Paramtest' )
local userError = require("Module:User error")
local hatnote = require('Module:Hatnote')._hatnote
local mHatlist = require('Module:Hatnote list')
local mbox = require( 'Module:Mbox' )._mbox
local TNT = require( 'Module:Translate' ):new()

local moduleIsUsed = false
local shouldAddCategories = false
local COLLAPSE_LIST_LENGTH_THRESHOLD = 5
local dynamicRequireListQueryCache = {}

local NS_MODULE_NAME =  mw.site.namespaces[ 828 ].name
local NS_TEMPLATE_NAME = mw.site.namespaces[ 10 ].name


--- FIXME: This should go to somewhere else, like Module:Common
--- Calls TNT with the given key
---
--- @param key string The translation key
--- @return string If the key was not found in the .tab page, the key is returned
local function translate( key, ... )
    local success, translation = pcall( TNT.format, 'Module:DependencyList/i18n.json', key or '', ... )

    if not success or translation == nil then
        return key
    end

    return translation
end


local builtins = {
    ["libraryUtil"] = {
        link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#libraryUtil",
        categories = {},
    },
	[ "strict" ] = {
		link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#strict",
		categories = { translate( 'category_strict_mode_modules' ) },
	},
}


--- 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 formatPageName( str )
    local name = mw.text.trim( 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( '^.', mw.ustring.upper )
        :gsub( ':.', mw.ustring.upper )

    return name
end


---@param str string
---@param allowBuiltins? boolean
---@return string
local function formatModuleName( str, allowBuiltins )
	if allowBuiltins then
		local name = mw.text.trim( str )
			-- Only remove quotes at start and end of string if both are the same type
            :gsub([[^(['"])(.-)%1$]], '%2')

        if builtins[name] then
            return name
        end
	end

    local module = formatPageName( str )

    if not mw.ustring.find( module, '^[Mm]odule?:' ) then
        module = NS_MODULE_NAME .. ':' .. module
    end

    return module
end


local function dualGmatch( str, pat1, pat2 )
    local f1 = mw.ustring.gmatch( str, pat1 )
    if pat2 then
        local f2 = mw.ustring.gmatch( str, pat2 )
        return function()
            return f1() or f2()
        end
    else
        return f1
    end
end

local function isDynamicPath( str )
    return mw.ustring.find( str, '%.%.' ) or mw.ustring.find( str, '%%%a' )
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[]
local function getDynamicRequireList( query )
    if query:find( '%.%.' ) then
        query = mw.text.split( query, '..', true )
        query = arr.map( query, function( x ) return mw.text.trim( x ) end )
        query = arr.map( query, function( x ) return ( x:match('^[\'\"](.-)[\'\"]$') or '%') end )
        query = table.concat( query )
    else
        local _, _query = query:match( '(["\'])(.-)%1' )
        query = _query:gsub( '%%%a', '%%' )
    end
    query = query:gsub( '^[Mm]odule:', '' )

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

    return {};
end


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

    assert( content ~= nil, translate( 'message_not_exists', moduleName ) )

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

    local function getList( pat1, pat2, list, dynList )
        for match in dualGmatch( content, pat1, pat2 ) do
            match = mw.text.trim( match )
            local name = extractModuleName( match, content )

            if isDynamicPath( name ) then
                dynList:insert( getDynamicRequireList( name ), true )
            elseif name ~= '' then
                name = formatModuleName( name, true )
                table.insert( list, name )

                if builtins[name] then
                    extraCategories = extraCategories:insert( builtins[name].categories, true )
                end
            end
        end
    end

    getList( 'require%s*(%b())', 'require%s*((["\'])%s*[Mm]odule:.-%2)', requireList, dynamicRequirelist )
    getList( 'mw%.loadData%s*(%b())', 'mw%.loadData%s*((["\'])%s*[Mm]odule:.-%2)', loadDataList, dynamicLoadDataList )
    getList( 'mw%.loadJsonData%s*(%b())', 'mw%.loadJsonData%s*((["\'])%s*[Mm]odule:.-%2)', loadJsonDataList, dynamicLoadJsonDataList )
    getList( 'pcall%s*%(%s*require%s*,([^%),]+)', nil, requireList, dynamicRequirelist )
    getList( 'pcall%s*%(%s*mw%.loadData%s*,([^%),]+)', nil, loadDataList, dynamicLoadDataList )
    getList( 'pcall%s*%(%s*mw%.loadJsonData%s*,([^%),]+)', nil, loadJsonDataList, dynamicLoadJsonDataList )

    if searchForUsedTemplates then
        for preprocess in mw.ustring.gmatch( content, ':preprocess%s*(%b())' ) do
            local function recursiveGMatch( str, pat )
                local list = {}
                local i = 0

                repeat
                    for match in mw.ustring.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 = mw.ustring.match( template, '{(.-)[|{}]' )
                if name ~= '' then
                    if name:find( ':' ) then
                        local ns = name:match( '^(.-):' )
                        if arr.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

    requireList = requireList .. dynamicRequirelist
    requireList = requireList:unique()
    loadDataList = loadDataList .. dynamicLoadDataList
    loadDataList = loadDataList:unique()
    loadJsonDataList = loadJsonDataList .. dynamicLoadJsonDataList
    loadJsonDataList = loadJsonDataList:unique()
    usedTemplateList = usedTemplateList:unique()
    extraCategories = extraCategories:unique()
    table.sort( extraCategories )

    return {
        requireList = requireList,
        loadDataList = loadDataList,
        loadJsonDataList = loadJsonDataList,
        usedTemplateList = usedTemplateList,
        extraCategories = extraCategories
    }
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( content ~= nil, translate( 'message_not_exists', templateName ) )

    for moduleName, funcName in mw.ustring.gmatch( content, '{{[{|safeubt:}]-#[Ii]nvoke:([^|]+)|([^}|]+)[^}]*}}' ) do
        moduleName = formatModuleName( moduleName )
        funcName = mw.text.trim( funcName )
        if mw.ustring.find( funcName, '^{{{' ) then
        	funcName = funcName ..  '}}}'
        end
        table.insert( invokeList, { moduleName = moduleName, funcName = funcName } )
    end

    invokeList = arr.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

---@return string
local function messageBoxUnused()
	local category = shouldAddCategories and '[[Category:' .. translate( 'category_unused_module' ) .. ']]' or ''

	return mbox(
		translate( 'message_unused_module_title' ),
		translate( 'message_unused_module_desc' ),
		{ icon = 'WikimediaUI-Alert.svg' }
	) .. category
end

--- Returns the wikitext for the message template (mbox/hatnote)
---@param msgKey string message key in /i18n.json
---@param pageName string page name used for the message
---@param list table
---@param listType string type of the page list used for the message
---@return string
local function getDependencyListWikitext( msgKey, pageName, list, listType )
    local listLabel = mw.ustring.format( '%d %s', #list, listType )
    local listContent = mHatlist.andList( list, false )

    --- Return mbox
    if #list > COLLAPSE_LIST_LENGTH_THRESHOLD then
        return mbox(
            translate( msgKey, pageName, listLabel ),
            listContent,
            { icon = 'WikimediaUI-Code.svg' }
        )
    --- Return hatnote
    else
        return hatnote(
            translate( msgKey, pageName, listContent ),
            { icon='WikimediaUI-Code.svg' }
        )
    end
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 = mw.ustring.format(
        'intitle:/%s%s/i -intitle:/%s\\/""/i -intitle:doc prefix:"%s"',
        query,
        query:find( '"$' ) and '' or '""',
        query,
        prefix
    )

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

--- Helper function to return the wikitext of the templates and categories
---@param currentPageName string
---@param pageList table|nil
---@param pageType string
---@param message string
---@param category string|nil
---@return string
local function formatDependencyList( currentPageName, pageList, pageType, message, category )
    local res = {}

    if type( pageList ) == 'table' and #pageList > 0 then
        table.sort( pageList )
        table.insert( res, getDependencyListWikitext( message, currentPageName, pageList, pageType ) )

        if shouldAddCategories and category then
            table.insert( res, mw.ustring.format( '[[Category:%s]]', category ) )
        end
    end

    return table.concat( res )
end


---@param templateName string
---@param invokeList table<string, string>[]    @This is the list returned by getInvokeCallList()
---@return string
local function formatInvokeCallList( templateName, invokeList )
    local category = shouldAddCategories and '[[Category:' .. translate( 'category_lua_based_template' ) .. ']]' or ''
    local res = {}

    for _, item in ipairs( invokeList ) do
        local msg = translate(
                'message_invokes_function',
    		templateName,
    		item.funcName,
    		item.moduleName
    	)
        table.insert( res, hatnote( msg, { icon = 'WikimediaUI-Code.svg' } ) )
    end

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

    return table.concat( res )
end


---@param moduleName string
---@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, whatLinksHere )
    local function lcfirst( str )
		return mw.ustring.gsub( str, '^[Mm]odule?:.', mw.ustring.lower )
	end

    local templateData = arr.map( whatLinksHere, function( x ) return { templateName = x, invokeList = getInvokeCallList( x ) } end )
    templateData = arr.filter( templateData, function( x )
        return arr.any( x.invokeList, function( y )
            return lcfirst( y.moduleName ) == lcfirst( moduleName )
        end )
    end )

    local invokedByList = {}

    for _, template in ipairs( templateData ) do
        for _, invoke in ipairs( template.invokeList ) do
            --- NOTE: Somehow only templates aren't linked properly, not sure why
            table.insert( invokedByList, translate( 'message_function_invoked_by', invoke.funcName, '[[' .. template.templateName .. ']]' ) )
        end
    end

    if #invokedByList > 0 then
        moduleIsUsed = true
    end

    return formatDependencyList(
        moduleName,
        invokedByList,
        translate( 'list_type_templates' ),
        'message_module_functions_invoked_by',
        translate( 'category_template_invoked_modules' )
    )
end


---@param moduleName string
---@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, whatLinksHere )
    local childModuleData = arr.map( whatLinksHere, function ( title )
        local lists = getRequireList( title )
        return { name = title, requireList = lists.requireList, loadDataList = lists.loadDataList .. lists.loadJsonDataList }
    end )

    local requiredByList = arr.map( childModuleData, function ( item )
        if arr.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 = arr.map( childModuleData, function ( item )
        if arr.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

    local res = {}

    table.insert( res,
        formatDependencyList(
            moduleName,
            requiredByList,
            translate( 'list_type_modules' ),
            'message_required_by',
            translate( 'category_modules_required_by_modules' )
        )
    )

    table.insert( res,
        formatDependencyList(
            moduleName,
            loadedByList,
            translate( 'list_type_modules' ),
            'message_loaded_by',
            translate( 'category_module_data' )
        )
    )

    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 or template namespace
    if param.is_empty( currentPageName ) and
        ( not arr.contains( { NS_MODULE_NAME, NS_TEMPLATE_NAME }, title.nsText ) ) then
        return ''
    end

    currentPageName = param.default_to( currentPageName, title.fullText )
    currentPageName = mw.ustring.gsub( currentPageName, '/[Dd]o[ck]u?$', '' )
    currentPageName = formatPageName( currentPageName )
    moduleIsUsed = yn( param.default_to( isUsed, false ) )
    shouldAddCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) )

    -- Don't show sandbox and testcases modules as unused
    if title.text:lower():find( 'sandbox' ) or title.text:lower():find( 'testcases' ) then
    	moduleIsUsed = true
    end

    if currentPageName:find( '^' .. NS_TEMPLATE_NAME .. ':' ) then
        local ok, invokeList = pcall( getInvokeCallList, currentPageName )
		if ok then
        	return formatInvokeCallList( currentPageName, invokeList )
        else
			return userError( invokeList )
		end
    end

    local whatTemplatesLinkHere = {}
    local whatModulesLinkHere = {}

    local function cleanFrom( from )
        from = from or ''
        local parts = mw.text.split( from, '|', true )

        if #parts == 2 then
            local name = mw.ustring.gsub( parts[ 1 ], '%[%[:', '' )
            name = mw.ustring.gsub( name, '/[Dd]o[ck]u?', '' )

            return name
        end

        return nil
    end

    local templatesRes = mw.smw.ask({
        '[[Links to::' .. currentPageName .. ']]',
        '[[Template:+]]',
        'sort=Links to',
        'order=asc',
        'mainlabel=from'
    }) or {}

    whatTemplatesLinkHere = arr.new( arr.condenseSparse( arr.map( templatesRes, function ( link )
        return cleanFrom( link[ 'from' ] )
    end ) ) ):unique()

    local moduleRes = mw.smw.ask( {
        '[[Links to::' .. currentPageName .. ']]',
        '[[Module:+]]',
        'sort=Links to',
        'order=asc',
        'mainlabel=from'
    } ) or {}

    whatModulesLinkHere = arr.new( arr.condenseSparse( arr.map( moduleRes, function ( link )
        return cleanFrom( link[ 'from' ] )
    end ) ) ):unique():reject( { currentPageName } )

    local ok, lists = pcall( getRequireList, currentPageName, true )
    if not ok then
        return userError( lists )
    end

    local requireList = arr.map( lists.requireList, function ( moduleName )
        if moduleName:find( '%%' ) then
            return formatDynamicQueryLink( moduleName )
        elseif builtins[moduleName] then
            return '[[' .. builtins[moduleName].link .. '|' .. moduleName .. ']]'
        else
            return '[[' .. moduleName .. ']]'
        end
    end )

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

    local loadJsonDataList = arr.map( lists.loadJsonDataList, function ( moduleName )
        if moduleName:find( '%%' ) then
            return formatDynamicQueryLink( moduleName )
        else
            return '[[' .. moduleName .. ']]'
        end
    end )

    local usedTemplateList = arr.map( lists.usedTemplateList, function( templateName )
        if mw.ustring.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, whatTemplatesLinkHere ) )
    table.insert( res, formatDependencyList( currentPageName, requireList, translate( 'list_type_modules' ), 'message_requires', translate( 'category_modules_required_by_modules' ) ) )
    table.insert( res, formatDependencyList( currentPageName, loadDataList, translate( 'list_type_modules' ), 'message_loads_data_from', translate( 'category_modules_using_data' ) ) )
    table.insert( res, formatDependencyList( currentPageName, loadJsonDataList, translate( 'list_type_modules' ), 'message_loads_data_from', translate( 'category_modules_using_data' ) ) )
    table.insert( res, formatDependencyList( currentPageName, usedTemplateList, translate( 'list_type_templates' ), 'message_transcludes', nil ) )
    table.insert( res, formatRequiredByList( currentPageName, whatModulesLinkHere ) )

	if shouldAddCategories then
		local extraCategories = arr.map( lists.extraCategories, function( categoryName )
			return "[[Category:" .. categoryName .. "]]";
		end )

		table.insert( res, table.concat( extraCategories ) );
	end

    if not moduleIsUsed then
        table.insert( res, 1, messageBoxUnused() )
    end

    return table.concat( res )
end


return p
-- </nowiki>
Содержание