Editing Module:DependencyListGive feedback
Jump to navigation
Jump to search
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
| Latest revision | Your text | ||
| Line 1: | Line 1: | ||
require("strict") | require("strict"); | ||
local p = {} | local p = {} | ||
local libraryUtil = require('libraryUtil') | local libraryUtil = require('libraryUtil') | ||
| Line 7: | Line 7: | ||
local dpl = require('Module:DPLlua') | local dpl = require('Module:DPLlua') | ||
local tooltip = require('Module:Tooltip') | local tooltip = require('Module:Tooltip') | ||
local userError = require("Module:User error") | |||
local moduleIsUsed = false | |||
local COLLAPSE_LIST_LENGTH_THRESHOLD = 5 | local COLLAPSE_LIST_LENGTH_THRESHOLD = 5 | ||
local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30 | local MAX_DYNAMIC_REQUIRE_LIST_LENGTH = 30 | ||
| Line 18: | Line 20: | ||
["strict"] = { | ["strict"] = { | ||
link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#strict", | link = "mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#strict", | ||
categories = { " | categories = { "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'. | ||
local function substVarValue( | ---@param content string The content of the module to search in | ||
local res = | ---@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 | 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 ) | ||
| Line 32: | Line 37: | ||
end | end | ||
local function extractModuleName( capture, | ---@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' ) | capture = capture:gsub( '^%(%s*(.-)%s*%)$', '%1' ) | ||
| Line 38: | Line 46: | ||
return capture | return capture | ||
elseif capture:find( '^[%a_][%w_]*$' ) then -- Check if if is a single variable | elseif capture:find( '^[%a_][%w_]*$' ) then -- Check if if is a single variable | ||
return substVarValue( | return substVarValue( content, capture ) | ||
end | end | ||
| Line 44: | Line 52: | ||
end | end | ||
---@param str string | |||
---@return string | |||
local function formatPageName( str ) | local function formatPageName( str ) | ||
local name = mw.text.trim(str) | local name = mw.text.trim(str) | ||
| Line 49: | Line 59: | ||
:gsub( '_', ' ' ) | :gsub( '_', ' ' ) | ||
:gsub( '^.', string.upper ) | :gsub( '^.', string.upper ) | ||
:gsub( '^([^:]-:)(.)', function(a,b) return a..string.upper(b) end ) | |||
return name | return name | ||
end | end | ||
---@param str string | |||
---@param allowBuiltins? boolean | |||
---@return string | |||
local function formatModuleName( str, allowBuiltins ) | local function formatModuleName( str, allowBuiltins ) | ||
if allowBuiltins then | if allowBuiltins then | ||
| Line 90: | Line 103: | ||
end | 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 | |||
---@return string[] | |||
local function getDynamicRequireList( query ) | local function getDynamicRequireList( query ) | ||
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 (x:match('^ | 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 ) | query = table.concat( query ) | ||
else | else | ||
local _, _query = query:match( '(["\'])(.-)%1' ) | local _, _query = query:match( '(["\'])(.-)%1' ) | ||
query = _query:gsub( '%%%a', '%%' ) | query = _query:gsub( '%%%a', '%%' ) | ||
end | end | ||
query = query:gsub( '^[Mm]odule:', '' ) | query = query:gsub( '^[Mm]odule:', '' ) | ||
| Line 134: | Line 150: | ||
--- Returns a list of modules loaded and required by module 'moduleName'. | --- Returns a list of modules loaded and required by module 'moduleName'. | ||
local function | ---@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 requireList = arr{} | ||
local loadDataList = arr{} | local loadDataList = arr{} | ||
local loadJsonDataList = arr{} | local loadJsonDataList = arr{} | ||
local usedTemplateList = arr{} | |||
local dynamicRequirelist = arr{} | local dynamicRequirelist = arr{} | ||
local dynamicLoadDataList = arr{} | local dynamicLoadDataList = arr{} | ||
local dynamicLoadJsonDataList = arr{} | local dynamicLoadJsonDataList = arr{} | ||
local extraCategories = 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 ) | local function getList( pat1, pat2, list, dynList ) | ||
for match in dualGmatch( | for match in dualGmatch( content, pat1, pat2 ) do | ||
match = mw.text.trim( match ) | match = mw.text.trim( match ) | ||
local name = extractModuleName( match, | local name = extractModuleName( match, content ) | ||
if isDynamicPath( name ) then | if isDynamicPath( name ) then | ||
| Line 168: | Line 193: | ||
getList( 'pcall%s*%(%s*mw%.loadJsonData%s*,([^%),]+)', nil, loadJsonDataList, dynamicLoadJsonDataList ) | 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 | ||
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 | else | ||
table.insert( usedTemplateList, 'Template:'..name ) | 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 | ||
| Line 226: | Line 234: | ||
end | end | ||
requireList = requireList .. dynamicRequirelist | |||
requireList = requireList:unique() | |||
loadDataList = loadDataList .. dynamicLoadDataList | |||
loadDataList = loadDataList:unique() | |||
loadJsonDataList = loadJsonDataList .. dynamicLoadJsonDataList | |||
loadJsonDataList = loadJsonDataList:unique() | |||
usedTemplateList = usedTemplateList:unique() | usedTemplateList = usedTemplateList:unique() | ||
extraCategories = extraCategories:unique(); | |||
table.sort( requireList ) | |||
table.sort( loadDataList ) | |||
table.sort( loadJsonDataList ) | |||
table.sort( usedTemplateList ) | table.sort( usedTemplateList ) | ||
table.sort(extraCategories); | |||
return usedTemplateList | 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'. | ||
local function getInvokeCallList( | ---@param templateName string | ||
local | ---@return table<string, string>[] | ||
local function getInvokeCallList( templateName ) | |||
local content = mw.title.new( templateName ):getContent() | |||
local invokeList = {} | local invokeList = {} | ||
assert( | assert( content ~= nil, string.format( '%s does not exist', templateName ) ) | ||
for moduleName, funcName in string.gmatch( | for moduleName, funcName in string.gmatch( content, '{{[{|safeubt:}]-#[Ii]nvoke:([^|]+)|([^}|]+)[^}]*}}' ) do | ||
moduleName = formatModuleName( moduleName ) | moduleName = formatModuleName( moduleName ) | ||
funcName = mw.text.trim( funcName ) | funcName = mw.text.trim( funcName ) | ||
| Line 249: | Line 276: | ||
-- For form calcs invoking the module directly | -- For form calcs invoking the module directly | ||
for config in dualGmatch( | for config in dualGmatch( content, '<[pd][ri][ev]%s+class%s*=%s*["\']jcConfig["\'](.-)</[pd][ri][ev]>', '{{[Ff]orm calculator%s*|(.+)}}' ) do | ||
local moduleName = string.match( config, 'module%s*=%s*(.-)[\n|]' ) | local moduleName = string.match( config, 'module%s*=%s*(.-)[\n|]' ) | ||
if param.has_content( moduleName ) then | if param.has_content( moduleName ) then | ||
| Line 264: | Line 291: | ||
end | end | ||
---@param pageName string | |||
---@param addCategories boolean | |||
---@return string | |||
local function messageBoxUnused( pageName, addCategories ) | |||
local function messageBoxUnused() | |||
local html = mw.html.create( 'table' ):addClass( 'messagebox obsolete plainlinks' ) | local html = mw.html.create( 'table' ):addClass( 'messagebox obsolete plainlinks' ) | ||
html:tag( 'td' ) | html:tag( 'td' ) | ||
| Line 303: | Line 304: | ||
:tag( 'div' ) | :tag( 'div' ) | ||
:css{ ['font-size']='0.85em', ['line-height']='1.45em' } | :css{ ['font-size']='0.85em', ['line-height']='1.45em' } | ||
:wikitext( '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.' ) | :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() | ||
:done() | :done() | ||
| Line 319: | Line 321: | ||
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. | ||
-- | ---@param query string @This will be in a format like 'Module:Wowee/%' or 'Module:Wowee/%/data' | ||
---@return string | |||
local function formatDynamicQueryLink( query ) | local function formatDynamicQueryLink( query ) | ||
local prefix = query:match( '^([^/]+)' ) | local prefix = query:match( '^([^/]+)' ) | ||
| Line 348: | Line 351: | ||
end | 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 function | |||
local | |||
local res = {} | local res = {} | ||
| Line 388: | Line 366: | ||
item.moduleName | item.moduleName | ||
) ) | ) ) | ||
end | |||
if #invokeList > 0 then | |||
table.insert( res, category ) | |||
end | end | ||
| Line 393: | Line 375: | ||
end | end | ||
local function formatInvokedByList( moduleName, invokedByList ) | ---@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 | end | ||
| Line 416: | Line 417: | ||
) ) | ) ) | ||
end | end | ||
end | |||
if #templateData > 0 then | |||
moduleIsUsed = true | |||
table.insert( res, (addCategories and '[[Category:Template invoked modules]]' or '') ) | |||
end | end | ||
| Line 421: | Line 427: | ||
end | end | ||
local function formatRequiredByList( moduleName, | ---@param moduleName string | ||
local requiredByList = | ---@param addCategories boolean | ||
local loadedByList = | ---@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 | if #requiredByList > COLLAPSE_LIST_LENGTH_THRESHOLD then | ||
| Line 441: | Line 477: | ||
requiredByModuleName | requiredByModuleName | ||
) ) | ) ) | ||
end | |||
if #requiredByList > 0 then | |||
table.insert( res, (addCategories and '[[Category:Modules required by modules]]' or '') ) | |||
end | end | ||
| Line 449: | Line 489: | ||
loadedByModuleName | loadedByModuleName | ||
) ) | ) ) | ||
end | |||
if #loadedByList > 0 then | |||
table.insert( res, (addCategories and '[[Category:Module data]]' or '') ) | |||
end | end | ||
| Line 454: | Line 498: | ||
end | end | ||
local function formatImportList( currentPageName, moduleList, id, message ) | local function formatImportList( currentPageName, moduleList, id, message, category ) | ||
if #moduleList > COLLAPSE_LIST_LENGTH_THRESHOLD then | if #moduleList > COLLAPSE_LIST_LENGTH_THRESHOLD then | ||
moduleList = collapseList( moduleList, id, 'modules' ) | moduleList = collapseList( moduleList, id, 'modules' ) | ||
| Line 464: | Line 506: | ||
return '<div class="seealso">' .. string.format( message, currentPageName, moduleName ) .. '</div>' | return '<div class="seealso">' .. string.format( message, currentPageName, moduleName ) .. '</div>' | ||
end ) | end ) | ||
if #moduleList > 0 and category then | |||
table.insert( res, string.format( '[[Category:%s]]', category ) ) | |||
end | |||
return table.concat( res ) | return table.concat( res ) | ||
end | end | ||
local function formatUsedTemplatesList( currentPageName, | local function formatUsedTemplatesList( currentPageName, addCategories, usedTemplateList ) | ||
local res = {} | local res = {} | ||
| Line 482: | Line 527: | ||
templateName | templateName | ||
) ) | ) ) | ||
end | end | ||
| Line 595: | Line 537: | ||
end | end | ||
---@param currentPageName string|nil | |||
---@param addCategories boolean|string|nil | |||
---@return string | |||
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 ) | ||
| Line 618: | Line 563: | ||
end | end | ||
addCategories = yn(addCategories) | addCategories = yn(addCategories) | ||
-- Don't show module as unused if isUsed=true or on a sandbox page | |||
moduleIsUsed = yn(isUsed) or false | |||
if title.text:lower():find( 'sandbox' ) then | |||
moduleIsUsed = true | |||
end | |||
if currentPageName:find( '^Template:' ) or currentPageName:find( '^Calculator:' ) then | if currentPageName:find( '^Template:' ) or currentPageName:find( '^Calculator:' ) then | ||
return | local ok, invokeList = pcall(getInvokeCallList, currentPageName); | ||
if ok then | |||
return formatInvokeCallList(currentPageName, addCategories, invokeList); | |||
else | |||
return userError(invokeList); | |||
end | |||
end | |||
local whatTemplatesLinkHere, whatModulesLinkHere = dpl.ask( { | |||
namespace = 'Template|Calculator', | |||
linksto = currentPageName, | |||
distinct = 'strict', | |||
ordermethod = 'title', | |||
allowcachedresults = true, | |||
cacheperiod = 604800 -- One week | |||
}, { | |||
namespace = 'Module', | |||
linksto = currentPageName, | |||
nottitlematch = '%/doc|Exchange/%|Exchange historical/%|Data/%|' .. currentPageName:gsub( 'Module:', '' ), | |||
distinct = 'strict', | |||
ordermethod = 'title', | |||
allowcachedresults = true, | |||
cacheperiod = 604800 -- One week | |||
} ) | |||
local ok, lists = pcall(getRequireList, currentPageName, true); | |||
if not ok then | |||
return userError(lists); | |||
end | |||
if mw.title.getCurrentTitle().subpageText ~= 'doc' and (#lists.requireList > 0 or #lists.loadDataList > 0 or #lists.loadJsonDataList > 0) then | |||
bucket( 'dependency_list' ).put{ | |||
require = lists.requireList, | |||
load_data = lists.loadDataList .. lists.loadJsonDataList | |||
} | |||
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 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 | end | ||
if not moduleIsUsed then | |||
table.insert( res, 1, messageBoxUnused( currentPageName:gsub( 'Module:', '' ), addCategories ) ) | |||
end | |||
return table.concat( res ) | |||
end | end | ||
return p | return p | ||