Home
Random
Log in
Settings
About Brighter Shores Wiki
Disclaimers
Search
Editing
Module:DependencyList
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
local p = {} local libraryUtil = require( 'libraryUtil' ) local arr = require( 'Module:Array' ) local yn = require( 'Module:Yesno' ) local param = require( 'Module:Paramtest' ) local dpl = require( 'Module:DPLlua' ) local tooltip = require( 'Module:Tooltip' ) local moduleIsUsed = false local COLLAPSE_LIST_LENGTH_THRESHOLD = 5 local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30 local dynamicRequireListQueryCache = {} 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 = { "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$', '%2' ) -- Only remove quotes at start and end of string if both are the same type :gsub( '_', ' ' ) :gsub( '^.', string.upper ) :gsub( ':.', string.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 string.find( module, '^[Mm]odule:' ) then module = 'Module:' .. module end return module end local function dualGmatch( str, pat1, pat2 ) local f1 = string.gmatch( str, pat1 ) if pat2 then local f2 = string.gmatch( str, pat2 ) return function() return f1() or f2() end else return f1 end end local function isDynamicPath( str ) return string.find( str, '%.%.' ) or string.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 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 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, string.format( '%s does not exist', 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 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 arr.contains( {'', 'template', 'calculator', 'user'}, ns:lower() ) then table.insert( usedTemplateList, name ) elseif ns == ns:upper() then table.insert( usedTemplateList, ns ) -- Probably a magic word end else if name:match( '^%u+$' ) or name == '!' then table.insert( usedTemplateList, name ) -- Probably a magic word else table.insert( usedTemplateList, 'Template:'..name ) end end end end end end requireList = requireList .. dynamicRequirelist requireList = requireList:unique() loadDataList = loadDataList .. dynamicLoadDataList loadDataList = loadDataList:unique() loadJsonDataList = loadJsonDataList .. dynamicLoadJsonDataList usedTemplateList = usedTemplateList:unique() extraCategories = extraCategories:unique() table.sort( requireList ) table.sort( loadDataList ) table.sort( loadJsonDataList ) table.sort( usedTemplateList ) 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, string.format( '%s does not exist', templateName ) ) for moduleName, funcName in string.gmatch( content, '{{[{|safeubt:}]-#[Ii]nvoke:([^|]+)|([^}|]+)[^}]*}}' ) do moduleName = formatModuleName( moduleName ) funcName = mw.text.trim( funcName ) if string.find( funcName, '^{{{' ) then funcName = funcName .. '}}}' end table.insert( invokeList, {moduleName=moduleName, funcName=funcName} ) end -- For form calcs invoking the module directly for config in dualGmatch( content, '<pre%s+class%s*=%s*["\']jcConfig["\'](.-)</pre>', '{{[Ff]orm calculator%s*|(.+)}}' ) do local moduleName = string.match( config, 'module%s*=%s*(.-)[\n|]' ) if param.has_content( moduleName ) then moduleName = formatModuleName( moduleName ) local funcName = string.match( config, 'modulefunc%s*=%s*(.-)[\n|]' ) or 'main' table.insert( invokeList, {moduleName=moduleName, funcName=funcName} ) end 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 ---@param pageName string ---@param addCategories boolean ---@return string local function messageBoxUnused( pageName, addCategories ) local html = mw.html.create( 'table' ):addClass( 'messagebox obsolete plainlinks' ) html:tag( 'td' ) :attr( 'width', '40xp' ) :wikitext( '[[File:Hourglass (historical).png|center|30px|link=]]' ) :done() :tag( 'td' ) :wikitext( "'''This module is unused.'''" ) :tag( 'div' ) :css{ ['font-size']='0.85em', ['line-height']='1.45em' } :wikitext( string.format( 'This module is neither invoked by a template nor required/loaded by another module. If this is in error, make sure to add <code>{{[[Template:Documentation|Documentation]]}}</code>/<code>{{[[Template:No documentation|No 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 = tooltip._span{ name=id, alt=text } list = arr.map( list, function(x) return '\n# '..x end ) local content = tooltip._div{ name=id, 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( '%%', '< ... >' ) 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 class='seealso'>'''%s''' invokes function '''%s''' in [[%s]] using [[Brighter Shores:Lua|Lua]].</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 or Calculator namespace which link to moduleName. ---@return string local function formatInvokedByList( moduleName, addCategories, whatLinksHere ) local function lcfirst( str ) return string.gsub( str, '^[Mm]odule:.', string.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 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 class='seealso'>'''%s''' is invoked by %s.</div>", moduleName, collapseList( invokedByList, 'invokedBy', 'templates' )[1] ) ) else for _, item in ipairs( invokedByList ) do table.insert( res, string.format( "<div class='seealso'>'''%s's''' %s.</div>", moduleName, item ) ) end end 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 = 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 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 class='seealso'>'''%s''' is required by %s.</div>", moduleName, requiredByModuleName ) ) end if #requiredByList > 0 then table.insert( res, (addCategories and '[[Category:Modules required by modules]]' or '') ) end for _, loadedByModuleName in ipairs( loadedByList ) do table.insert( res, string.format( "<div class='seealso'>'''%s''' is loaded by %s.</div>", moduleName, loadedByModuleName ) ) end if #loadedByList > 0 then table.insert( res, (addCategories and '[[Category:Module data]]' or '') ) end return table.concat( res ) end local function formatImportList( currentPageName, moduleList, id, message, category ) if #moduleList > COLLAPSE_LIST_LENGTH_THRESHOLD then moduleList = collapseList( moduleList, id, 'modules' ) end local res = arr.map( moduleList, function( moduleName ) return '<div class="seealso">' .. string.format( message, currentPageName, moduleName ) .. '</div>' end ) if #moduleList > 0 and category then table.insert( res, string.format( '[[Category:%s]]', category ) ) 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 class='seealso'>'''%s''' transcludes %s using <samp>frame:preprocess()</samp>.</div>", currentPageName, templateName ) ) 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 or calculator namespace or if module is part of exchange or data groups if param.is_empty( currentPageName ) and ( ( not arr.contains( {'Module', 'Template', 'Calculator'}, title.nsText ) ) or ( title.nsText == 'Module' and ( arr.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 = formatPageName( currentPageName ) addCategories = yn( param.default_to( addCategories, title.subpageText~='doc' ) ) moduleIsUsed = yn( param.default_to( isUsed, false ) ) if title.text:lower():find( 'sandbox' ) then moduleIsUsed = true -- Don't show sandbox modules as unused end if currentPageName:find( '^Template:' ) or currentPageName:find( '^Calculator:' ) then local invokeList = getInvokeCallList( currentPageName ) return formatInvokeCallList(currentPageName, addCategories, invokeList) end local whatTemplatesLinkHere, whatModulesLinkHere = dpl.ask( { namespace = 'Template|Calculator', linksto = currentPageName, distinct = 'strict', ignorecase = true, ordermethod = 'title', allowcachedresults = true, cacheperiod = 604800 -- One week }, { namespace = 'Module', linksto = currentPageName, nottitlematch = '%/doc|Exchange/%|Exchange historical/%|Data/%|' .. currentPageName:gsub( 'Module:', '' ), distinct = 'strict', ignorecase = true, ordermethod = 'title', allowcachedresults = true, cacheperiod = 604800 -- One week } ) local lists = getRequireList( currentPageName, true ) 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 string.find( templateName, ':' ) then -- Real templates are prefixed by a namespace, magic words are not return '[['..templateName..']]' else return "'''{{"..templateName.."}}'''" -- 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, formatImportList( currentPageName, requireList, 'require', "'''%s''' requires %s.", addCategories and 'Modules requiring modules' ) ) table.insert( res, formatImportList( currentPageName, loadDataList, 'loadData', "'''%s''' loads data from %s.", addCategories and 'Modules using data' ) ) table.insert( res, formatImportList( currentPageName, loadJsonDataList, 'loadJsonData', "'''%s''' loads data from %s.", addCategories and 'Modules using data' ) ) table.insert( res, formatUsedTemplatesList( currentPageName, addCategories, usedTemplateList ) ) table.insert( res, formatRequiredByList( currentPageName, addCategories, whatModulesLinkHere ) ) if addCategories 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( currentPageName:gsub( 'Module:', '' ), addCategories ) ) end return table.concat( res ) end return p
Summary:
Please note that all contributions to Brighter Shores Wiki are considered to be released under the CC BY-NC-SA 3.0 (see
Brighter Shores:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Preview page with this template
Templates used on this page:
Template:DependencyList
(
edit
)
Template:DependencyList/doc
(
edit
)
Template:Documentation
(
edit
)
Template:Extension DPL
(
edit
)
Template:No documentation
(
edit
)
Template:PageType
(
edit
)
Module:Array
(
edit
)
Module:DPLlua
(
edit
)
Module:DependencyList
(
edit
)
Module:DependencyList/doc
(
edit
)
Module:Documentation
(
edit
)
Module:Paramtest
(
edit
)
Module:Tooltip
(
edit
)
Module:Yesno
(
view source
) (semi-protected)
This page is a member of a hidden category:
Category:Pages using DynamicPageList3 parser function