Module:Dependencies
From Paracosm
More actions
The folded Technical details panel on module and template pages: which pages use the page, what it uses, and the functions a module defines.
Module:Documentation shows it on every documented module and template, and MediaWiki:Scribunto-doc-page-does-not-exist on a module with no /doc.
For module editors
API
Each page's row lives in the dependencies table of Extension:Bucket, schema Bucket:Dependencies, generated by mise run bucket:schemas from properties.json, registered in Module:BucketQuery/manifests.json.
panel(options) → string: the panel followed by the page's categories, or the categories alone when the panel would be empty. Writes the page's row when the render is the page itself.
| Field | Type | Default | Meaning |
|---|---|---|---|
page
|
string | the current page | The page to describe. A /doc page describes the page it documents.
|
addCategories
|
boolean or string | true, except on a /doc render
|
Whether to add the categories below. |
sources
|
list of { label, text }
|
none | A Source row above the other rows, one line per entry's text (wikitext), with each label (plain text) leading the folded header's facts. Module:Documentation passes the page's wiki-tools and Wikipedia origins.
|
main(frame):panel()for the current page, as{{#invoke:Dependencies|main}}.- With categories on, a template with at least one invoke joins
[[Category:Lua-based templates]], a module that requiresstrictjoins[[Category:Strict mode modules]], and a module nothing invokes, requires, or loads joins[[Category:Unused modules]]. - The functions come from Module:Module toc.
- Uses lists, under Files, the image files the page names: a module string that is a whole file name, a template's
[[File:…]]or a parameter value that is a whole file name, and media URLs (https://media.starcitizen.tools/a/ab/<File>) in either one or in the page's own/styles.csswhen the page loads it. Rendered on the page itself, each file is a thumbnail in a 20px box, which records the page under the file's File usage; the/docand sandbox renders show the name alone. Files are not stored in Bucket.
Gotchas
- The folded header sits inside
<summary>, so it holds plain text only: a link there would be followed instead of opening the panel. - A page writes its row only while rendering itself: never from its
/doc, or a title containing/sandboxor/testcases, or starting withSandbox/, in either namespace. Such a page is never flagged unused. A module's Used by row reads other pages' rows when the module's own page renders, so a new dependent appears after that page is next re-parsed. - Only literal module names are stored. A dynamic
require('Module:X/' .. name)is shown asModule:X/…and stored nowhere. - A template that never shows
{{Documentation}}writes no row, so the modules it invokes do not list it; a module whose/docnever shows it writes no row either. - Stylesheet pages show no panel, so a stylesheet's users are listed on the page that owns it:
Module:Iconlists who loadsModule:Icon/styles.cssunder Styles used by. Only a/styles.csssubpage has an owner. Loading a module's stylesheet does not use its code: a module whose only users load its stylesheet is still unused. - When the Bucket lookup fails, a module's Used by row says the list is unavailable, and the module is not flagged unused.
- A file drawn as a CSS mask, through
{{filepath:}}or a stylesheet URL, records no use on the pages that show it. The Files thumbnail is the only record, made against the page whose source names the file, and it keeps the file off Special:UnusedFiles. - A module's file name built while the page runs shows as a pattern with no thumbnail, such as
Sc-icon-brand-….svg, when string literals are joined with names, calls or indexes, or fill astring.formatpattern. A pattern with nothing literal but the extension is dropped, and a name built any other way, or in a template from a parameter, is not seen. - A file chosen from data (API images, Bucket values) is not seen either. The page that draws it records it only when it draws a
[[File:]]; drawn as a mask, nothing records it. - Any string that is a whole file name counts, even a sentence that happens to end in one.
- A media URL is checked against its name. A path that is not the md5 of the name is flagged, and so is a file that has moved:
[[File:]]follows the redirect, so the thumbnail still shows, but the URL no longer works. The moved check is one expensive lookup per file named by a URL. - A missing file shows as a red link, and MediaWiki adds the page to its hidden
Category:Pages with broken file links. - An SVG thumbnail is a silhouette in the text colour, the way the wiki draws most icons, so a colour logo shows without its colours. A raster thumbnail keeps its own colours.
require('strict')
--- @module Dependencies
--- The folded "Technical details" panel Module:Documentation shows on module and
--- template pages: which pages use the page, what it uses, and the functions a
--- module defines; and the `dependencies` Bucket row each page writes so the
--- modules and stylesheets it uses can list it. Forward lists come from the
--- page's own source; reverse lists come from the rows other pages wrote.
local parse = require('Module:Dependencies/Parse')
local mbox = require('Module:Mbox')
local icon = require('Module:Icon')
local yesno = require('Module:Yesno')
local p = {}
local BUCKET = 'dependencies'
local NS_TEMPLATE = 10
local NS_MODULE = 828
local HEADING = 'Technical details'
local ICON = 'Codex icon code.svg'
local ROW_ICONS = {
source = 'Codex icon reference.svg',
usedBy = 'Codex icon arrowPrevious.svg',
uses = 'Codex icon arrowNext.svg',
functions = 'Codex icon function.svg',
}
local UNUSED_TEXT = 'No template invokes it and no module requires or loads it. '
.. 'If an article invokes it directly, call it through a template instead.'
local UNAVAILABLE_TEXT = 'Unavailable: looking up the pages that use it failed on this render. '
.. 'Purge the page to retry.'
--- @class DependenciesContext
--- @field title string the documented page, e.g. `Module:Mbox`
--- @field prefix string its namespace with the colon, e.g. `Module:`
--- @field namespace integer
--- @field content string|nil its source
--- @field isCurrent boolean the render is the page itself, not its /doc or another page
--- @field excluded boolean a sandbox or testcases page, whose dependencies are not real usage
--- @class DependenciesGroup
--- @field label string e.g. `Required by`
--- @field items string[] wikitext, one per list item
--- @field note string|nil wikitext under the list
--- @field modifier string|nil the list's `t-dependencies__list--` modifier
--- @class DependenciesReport
--- @field usedBy DependenciesGroup[]|nil nil when the reverse lookup failed
--- @field uses DependenciesGroup[]
--- @field usedByCount integer distinct pages across `usedBy`
--- @field usesCount integer entries across `uses`
--- @field unused boolean a module nothing invokes, requires or loads
--- @field functions ModuleTocFunction[]
--- @class DependenciesSource
--- @field label string plain text for the folded header, e.g. `wiki-tools`
--- @field text string wikitext, one line of the Source row
--- @class DependenciesPanelOptions
--- @field page string|nil the page to describe; default the current page, or the page a /doc documents
--- @field addCategories boolean|string|nil defaults to true except on a /doc render
--- @field sources DependenciesSource[]|nil
--- @param title table mw.title
--- @return boolean
local function isExcluded(title)
local text = title.text:lower()
return text:find('^sandbox/') ~= nil or text:find('/sandbox') ~= nil or text:find('/testcases') ~= nil
end
--- @param pageName string|nil
--- @return DependenciesContext|nil
local function context(pageName)
local current = mw.title.getCurrentTitle()
local target = pageName and mw.title.new(pageName) or current
if target == nil then
return nil
end
if target.isSubpage and target.subpageText:lower() == 'doc' then
target = target.basePageTitle
end
if target.namespace ~= NS_TEMPLATE and target.namespace ~= NS_MODULE then
return nil
end
return {
title = target.prefixedText,
prefix = target.prefixedText:match('^[^:]+:'),
namespace = target.namespace,
content = target:getContent(),
isCurrent = current.prefixedText == target.prefixedText,
excluded = isExcluded(target),
}
end
--- @param list any
--- @param value string
--- @return boolean
local function contains(list, value)
for _, item in ipairs(type(list) == 'table' and list or {}) do
if item == value then
return true
end
end
return false
end
--- `files` with the files `extra` names added.
--- @param files DependenciesFiles
--- @param extra DependenciesFiles
--- @return DependenciesFiles
local function mergeFiles(files, extra)
local merged = { names = {}, urls = {}, dynamic = {} }
for _, key in ipairs({ 'names', 'dynamic' }) do
local seen = {}
for _, list in ipairs({ files[key], extra[key] }) do
for _, value in ipairs(list) do
if not seen[value] then
seen[value] = true
merged[key][#merged[key] + 1] = value
end
end
end
table.sort(merged[key])
end
local seen = {}
for _, list in ipairs({ files.urls, extra.urls }) do
for _, url in ipairs(list) do
local key = url.name .. '|' .. url.path
if not seen[key] then
seen[key] = true
merged.urls[#merged.urls + 1] = url
end
end
end
return merged
end
--- What the page itself uses: a module's requires, loads, stylesheets and
--- files, a template's invokes, stylesheets and files. The files include those
--- the page's own `/styles.css` names when the page loads it; any other
--- stylesheet lists its files on the page that owns it.
--- @param ctx DependenciesContext
--- @return table
local function forward(ctx)
local found
if ctx.namespace == NS_MODULE then
found = parse.lua(ctx.content)
found.invokes = {}
else
found = {
requires = {},
loads = {},
dynamic = {},
strict = false,
invokes = parse.invokes(ctx.content),
styles = parse.styles(ctx.content),
files = parse.files(ctx.content),
}
end
local own = ctx.title .. '/styles.css'
local stylesheet = contains(found.styles, own) and mw.title.new(own)
local css = stylesheet and stylesheet:getContent()
if css then
found.files = mergeFiles(found.files, parse.stylesheetFiles(css))
end
return found
end
--- The page's `dependencies` row, or nil when it uses nothing.
--- @param found table
--- @return table|nil
local function rowFor(found)
local invokes, calls, seen = {}, {}, {}
for _, call in ipairs(found.invokes) do
if not seen[call.module] then
seen[call.module] = true
invokes[#invokes + 1] = call.module
end
calls[#calls + 1] = call.module .. '|' .. call.func
end
local row = {}
local function set(field, list)
if list[1] ~= nil then
row[field] = list
end
end
set('requires', found.requires)
set('loads', found.loads)
set('invokes', invokes)
set('invoke_calls', calls)
set('styles', found.styles)
if next(row) == nil then
return nil
end
return row
end
--- @param ctx DependenciesContext
--- @param found table
local function write(ctx, found)
if not ctx.isCurrent or ctx.excluded then
return
end
local row = rowFor(found)
if row == nil then
return
end
pcall(function()
mw.ext.bucket(BUCKET).put(row)
end)
end
--- Splits the rows naming `title` into the pages that invoke, require and load
--- it, and the pages that load its `/styles.css`.
--- @param rows table[] raw Bucket rows keyed by field name (page_name, requires, loads, invokes, invoke_calls, styles)
--- @param title string
--- @return { invokedBy: { page: string, funcs: string[] }[], requiredBy: string[], loadedBy: string[], styledBy: string[] }
local function group(rows, title)
local back = { invokedBy = {}, requiredBy = {}, loadedBy = {}, styledBy = {} }
local prefix = title .. '|'
local stylesheet = title .. '/styles.css'
for _, row in ipairs(rows) do
local page = row.page_name
if page and page ~= title then
if contains(row.invokes, title) then
local funcs, seen = {}, {}
for _, call in ipairs(type(row.invoke_calls) == 'table' and row.invoke_calls or {}) do
local func = call:sub(1, #prefix) == prefix and call:sub(#prefix + 1) or nil
if func and not seen[func] then
seen[func] = true
funcs[#funcs + 1] = func
end
end
back.invokedBy[#back.invokedBy + 1] = { page = page, funcs = funcs }
end
if contains(row.requires, title) then
back.requiredBy[#back.requiredBy + 1] = page
end
if contains(row.loads, title) then
back.loadedBy[#back.loadedBy + 1] = page
end
if contains(row.styles, stylesheet) then
back.styledBy[#back.styledBy + 1] = page
end
end
end
table.sort(back.invokedBy, function(a, b)
return a.page < b.page
end)
table.sort(back.requiredBy)
table.sort(back.loadedBy)
table.sort(back.styledBy)
return back
end
--- The pages that use `title`, or nil when the lookup failed.
--- Queries Bucket directly: Module:BucketQuery would load and link every
--- manifest registered ahead of this one on each documentation render.
--- @param title string
--- @return table|nil
local function reverse(title)
local ok, rows = pcall(function()
local bucket = mw.ext.bucket
return bucket(BUCKET)
.select('page_name', 'requires', 'loads', 'invokes', 'invoke_calls', 'styles')
.where(
bucket.Or(
{ 'requires', title },
{ 'loads', title },
{ 'invokes', title },
{ 'styles', title .. '/styles.css' }
)
)
.limit(5000)
.run()
end)
if not ok or type(rows) ~= 'table' then
return nil
end
return group(rows, title)
end
--- @param text string
--- @return string
local function code(text)
return '<code>' .. mw.text.nowiki(text) .. '</code>'
end
--- A link to `name` as a row shows it: one of the documented page's own subpages
--- relative (`/styles.css`), otherwise without `prefix`, the namespace the row
--- already implies.
--- @param name string
--- @param ctx DependenciesContext
--- @param prefix string
--- @return string
local function link(name, ctx, prefix)
local own = ctx.title .. '/'
local text = name
if name:sub(1, #own) == own then
text = name:sub(#own)
elseif name:sub(1, #prefix) == prefix then
text = name:sub(#prefix + 1)
end
if text == name then
return '[[' .. name .. ']]'
end
return '[[' .. name .. '|' .. text .. ']]'
end
--- @param names string[]
--- @param ctx DependenciesContext
--- @param prefix string
--- @return string[]
local function links(names, ctx, prefix)
local items = {}
for i, name in ipairs(names) do
items[i] = link(name, ctx, prefix)
end
return items
end
--- The templates that invoke the page, each with the functions it calls. When
--- every one calls the same single function, the note names it once instead.
--- @param entries { page: string, funcs: string[] }[]
--- @param ctx DependenciesContext
--- @return string[] items
--- @return string|nil note
local function invokers(entries, ctx)
local shared = entries[1] and entries[1].funcs[1]
for _, entry in ipairs(entries) do
if #entry.funcs ~= 1 or entry.funcs[1] ~= shared then
shared = nil
break
end
end
local items = {}
for i, entry in ipairs(entries) do
items[i] = link(entry.page, ctx, 'Template:')
if shared == nil and entry.funcs[1] ~= nil then
local funcs = {}
for j, func in ipairs(entry.funcs) do
funcs[j] = code(func)
end
items[i] = items[i] .. ' (' .. table.concat(funcs, ', ') .. ')'
end
end
if shared == nil then
return items, nil
end
return items, (#entries > 1 and 'All via ' or 'Via ') .. code(shared)
end
--- @param into DependenciesGroup[]
--- @param label string
--- @param items string[]
--- @param note string|nil
--- @param modifier string|nil
local function addGroup(into, label, items, note, modifier)
if items[1] ~= nil then
into[#into + 1] = { label = label, items = items, note = note, modifier = modifier }
end
end
--- What is wrong with each media URL, by file name: a path that is not the
--- md5 directories of the name, or a file that has moved, which `[[File:]]`
--- follows but a URL does not.
--- @param urls DependenciesFileUrl[]
--- @return table<string, string[]>
local function urlProblems(urls)
local problems, checked = {}, {}
local function add(name, text)
problems[name] = problems[name] or {}
problems[name][#problems[name] + 1] = text
end
for _, url in ipairs(urls) do
local md5 = mw.hash.hashValue('md5', (url.name:gsub(' ', '_')))
local expected = md5:sub(1, 1) .. '/' .. md5:sub(1, 2)
if url.path ~= expected then
add(url.name, 'URL path ' .. code(url.path) .. ' should be ' .. code(expected))
end
if not checked[url.name] then
checked[url.name] = true
-- isRedirect is an expensive lookup: past the page's limit it throws.
local ok, target = pcall(function()
local title = mw.title.new('File:' .. url.name)
return title and title.isRedirect and title.redirectTarget
end)
if ok and target then
add(url.name, 'moved to [[:File:' .. target.text .. '|' .. target.text .. ']], update the URL')
end
end
end
return problems
end
--- The Files group's items. On the page itself each name follows a thumbnail,
--- which records the file as used by the page; anywhere else (its /doc, a
--- sandbox) the name alone, so only the page whose source names a file is
--- recorded as using it.
--- @param files DependenciesFiles
--- @param ctx DependenciesContext
--- @return string[]
local function fileItems(files, ctx)
local thumbnails = ctx.isCurrent and not ctx.excluded
local problems = urlProblems(files.urls)
local items = {}
for _, name in ipairs(files.names) do
local item = '[[:File:' .. name .. '|' .. name .. ']]'
if thumbnails then
-- The wiki recolours most icons, so an SVG shows as a silhouette in the text
-- colour: styles.css blackens it and `skin-invert` whitens it on a dark theme.
local class = name:lower():find('%.svg$') and 't-dependencies__thumb t-dependencies__thumb--svg skin-invert'
or 't-dependencies__thumb'
item = '<span class="' .. class .. '">[[File:' .. name .. '|20x20px|link=|alt=]]</span>' .. item
end
if problems[name] then
item = item
.. ' <span class="t-dependencies__warning">('
.. table.concat(problems[name], '; ')
.. ')</span>'
end
items[#items + 1] = item
end
for _, pattern in ipairs(files.dynamic) do
items[#items + 1] = code(pattern)
end
return items
end
--- @param back table
--- @return integer
local function countPages(back)
local seen, count = {}, 0
local function add(page)
if not seen[page] then
seen[page] = true
count = count + 1
end
end
for _, entry in ipairs(back.invokedBy) do
add(entry.page)
end
for _, key in ipairs({ 'requiredBy', 'loadedBy', 'styledBy' }) do
for _, page in ipairs(back[key]) do
add(page)
end
end
return count
end
--- What the panel shows for the page, before any markup.
--- @param ctx DependenciesContext
--- @param found table forward()'s result
--- @param back table|nil reverse()'s result; nil when the lookup failed
--- @return DependenciesReport
local function report(ctx, found, back)
local uses, usesCount = {}, 0
if ctx.namespace == NS_TEMPLATE then
local calls = {}
for i, call in ipairs(found.invokes) do
calls[i] = code(call.func) .. ' in [[' .. call.module .. ']]'
end
addGroup(uses, 'Invokes', calls)
usesCount = #calls
else
local requires = links(found.requires, ctx, 'Module:')
for _, pattern in ipairs(found.dynamic) do
requires[#requires + 1] = code(pattern)
end
addGroup(uses, 'Requires', requires)
addGroup(uses, 'Loads', links(found.loads, ctx, 'Module:'))
usesCount = #requires + #found.loads
end
addGroup(uses, 'Styles', links(found.styles, ctx, ctx.prefix))
addGroup(uses, 'Files', fileItems(found.files, ctx), nil, 'files')
usesCount = usesCount + #found.styles + #found.files.names + #found.files.dynamic
local result = {
uses = uses,
usesCount = usesCount,
usedByCount = 0,
unused = false,
functions = {},
}
if back ~= nil then
local usedBy = {}
local items, note = invokers(back.invokedBy, ctx)
addGroup(usedBy, 'Invoked by', items, note)
addGroup(usedBy, 'Required by', links(back.requiredBy, ctx, 'Module:'))
addGroup(usedBy, 'Loaded by', links(back.loadedBy, ctx, 'Module:'))
addGroup(usedBy, 'Styles used by', links(back.styledBy, ctx, ctx.prefix))
result.usedBy = usedBy
result.usedByCount = countPages(back)
result.unused = ctx.namespace == NS_MODULE
and not ctx.excluded
and back.invokedBy[1] == nil
and back.requiredBy[1] == nil
and back.loadedBy[1] == nil
end
if ctx.namespace == NS_MODULE then
-- Required here, not at the top: every page records the modules it loads
-- and re-parses when one changes, and template pages never need this one.
result.functions = require('Module:Module toc').functions(ctx.content)
end
return result
end
--- @param ctx DependenciesContext
--- @param found table
--- @param result DependenciesReport
--- @return string
local function categories(ctx, found, result)
local out = {}
if ctx.namespace == NS_TEMPLATE and found.invokes[1] ~= nil then
out[#out + 1] = '[[Category:Lua-based templates]]'
end
if ctx.namespace == NS_MODULE and found.strict then
out[#out + 1] = '[[Category:Strict mode modules]]'
end
if result.unused then
out[#out + 1] = '[[Category:Unused modules]]'
end
return table.concat(out)
end
--- @param addCategories boolean|string|nil
--- @return boolean
local function categoriesEnabled(addCategories)
-- Yesno's `default` only covers an unrecognised string, not a nil argument
-- (a #invoke omitting the parameter, or a Lua caller passing nothing).
local enabled = yesno(addCategories)
if enabled == nil then
local current = mw.title.getCurrentTitle()
return not (current.isSubpage and current.subpageText:lower() == 'doc')
end
return enabled == true
end
--- @param items string[]
--- @param modifier string|nil
--- @return table mw.html
local function list(items, modifier)
local root = mw.html.create('ul'):addClass('t-dependencies__list')
if modifier then
root:addClass('t-dependencies__list--' .. modifier)
end
for _, item in ipairs(items) do
root:tag('li'):wikitext(item)
end
return root
end
--- @param groups DependenciesGroup[]
--- @return string
local function groupsHtml(groups)
local root = mw.html.create('div'):addClass('t-dependencies__groups')
for _, entry in ipairs(groups) do
root:tag('div')
:addClass('t-dependencies__sublabel')
:wikitext(entry.label)
:tag('span')
:addClass('t-dependencies__count')
:wikitext(#entry.items)
local body = root:tag('div'):addClass('t-dependencies__items'):node(list(entry.items, entry.modifier))
if entry.note then
body:tag('div'):addClass('t-dependencies__note'):wikitext(entry.note)
end
end
return tostring(root)
end
--- @param title string
--- @param functions ModuleTocFunction[]
--- @return string
local function functionsHtml(title, functions)
local items = {}
for i, func in ipairs(functions) do
items[i] = string.format(
'[[%s#L-%d|%s]]<span class="t-dependencies__line">L%d</span>',
title,
func.line,
mw.text.nowiki(func.name),
func.line
)
end
return tostring(list(items, 'code'))
end
--- @param label string
--- @param file string
--- @param content string wikitext
--- @param modifier string|nil
--- @return string
local function row(label, file, content, modifier)
local root = mw.html.create('div'):addClass('t-dependencies__row')
if modifier then
root:addClass('t-dependencies__row--' .. modifier)
end
root:tag('div')
:addClass('t-dependencies__label')
:wikitext(icon.render({ icon = file, mask = true, size = '1em', class = 't-dependencies__icon' }))
:wikitext(label)
root:tag('div'):addClass('t-dependencies__content'):wikitext(content)
return tostring(root)
end
--- The folded header: the heading, then plain-text facts. It sits inside
--- <summary>, so it must hold no link.
--- @param facts string[]
--- @param unused boolean
--- @return string
local function summary(facts, unused)
local root = mw.html.create('span'):addClass('t-dependencies__summary')
root:tag('span'):addClass('t-dependencies__heading'):wikitext(HEADING)
if unused or facts[1] ~= nil then
local meta = root:tag('span'):addClass('t-dependencies__meta')
if unused then
meta:tag('span'):addClass('t-dependencies__chip'):wikitext('Unused')
end
if facts[1] ~= nil then
meta:tag('span'):wikitext(table.concat(facts, ' · '))
end
end
return tostring(root)
end
--- The panel for `options.page`, followed by its categories. Writes the page's
--- row when the render is the page itself.
--- @param options DependenciesPanelOptions|nil
--- @return string
function p.panel(options)
options = options or {}
local rows, facts, unused, cats = {}, {}, false, ''
local lines = {}
for _, source in ipairs(options.sources or {}) do
lines[#lines + 1] = '<div>' .. source.text .. '</div>'
facts[#facts + 1] = source.label
end
if lines[1] ~= nil then
rows[#rows + 1] = row('Source', ROW_ICONS.source, table.concat(lines))
end
local ctx = context(options.page)
if ctx ~= nil and ctx.content ~= nil then
local found = forward(ctx)
write(ctx, found)
local back = reverse(ctx.title)
local result = report(ctx, found, back)
unused = result.unused
if unused then
local content = '<div>' .. UNUSED_TEXT .. '</div>'
if result.usedBy[1] ~= nil then
content = content .. groupsHtml(result.usedBy)
end
rows[#rows + 1] = row('Used by', ROW_ICONS.usedBy, content, 'warning')
elseif back == nil then
if ctx.namespace == NS_MODULE then
rows[#rows + 1] = row('Used by', ROW_ICONS.usedBy, UNAVAILABLE_TEXT, 'muted')
end
elseif result.usedBy[1] ~= nil then
rows[#rows + 1] = row('Used by', ROW_ICONS.usedBy, groupsHtml(result.usedBy))
facts[#facts + 1] = 'used by ' .. result.usedByCount
end
if result.uses[1] ~= nil then
rows[#rows + 1] = row('Uses', ROW_ICONS.uses, groupsHtml(result.uses))
facts[#facts + 1] = 'uses ' .. result.usesCount
end
if result.functions[1] ~= nil then
rows[#rows + 1] = row('Functions', ROW_ICONS.functions, functionsHtml(ctx.title, result.functions))
end
if categoriesEnabled(options.addCategories) then
cats = categories(ctx, found, result)
end
end
if rows[1] == nil then
return cats
end
local frame = mw.getCurrentFrame()
-- Module:Icon leaves its stylesheet to the caller; the row icons need it.
local styles = frame:extensionTag({ name = 'templatestyles', args = { src = 'Module:Icon/styles.css' } })
.. frame:extensionTag({ name = 'templatestyles', args = { src = 'Module:Dependencies/styles.css' } })
return styles
.. mbox.render({
title = summary(facts, unused),
text = table.concat(rows),
icon = ICON,
class = 't-dependencies',
})
.. cats
end
--- `{{#invoke:Dependencies|main}}`, for MediaWiki:Scribunto-doc-page-does-not-exist.
--- @param frame table
--- @return string
function p.main(frame)
return p.panel()
end
-- Test-only exports. Not part of the public API.
p._internal = {
group = group,
report = report,
rowFor = rowFor,
}
return p