Module:Synchbot
Appearance
This module is used to format Synchbot requests. The following functions are defined.
Request
[edit]Arguments
[edit]Renders a Synchbot request log which summarises the actions performed in response to this request. The following arguments are defined:
all requests | |
---|---|
parameter | usage |
user | The name of the user whose user pages to edit. This will be used by the bot to skip wikis where the user isn't registered. |
action | The action to perform on each page; one of delete, replace/prepend/append (for edit), or set (for preferences). |
skip wikis | An arbitrary list of wikis to skip. |
status | The status of the request; one of queued (default), done, not done, or on hold. |
'edit' requests | |
title | The title of the page to edit on each wiki (including namespace). |
text | The text to write to the page. |
skip existing | Whether to skip wikis where the page already exists ('yes' or blank). |
'set' preferences requests | |
set:* | The preferences to set as key:value pairs, like set:language = en .
|
Example (edit pages)
[edit]{{#invoke:synchbot|request |user = Pathoschild |action = replace |title = User:Pathoschild/common.js |text = mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Pathoschild/global.js&action=raw&ctype=text/javascript'); |skip wikis = |skip existing = |status = done }}
request done:
Pathoschild (global account · recent activity · user pages)
- go to
User:Pathoschild/common.js
on every wiki - and replace the text with mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:Pathoschild/global.js&action=raw&ctype=text/javascript');
Example (set preferences)
[edit]{{#invoke:synchbot|request |user = Pathoschild |action = set |set:language = en |set:skin = vector |skip wikis = |status = done }}
request done:
Pathoschild (global account · recent activity · user pages)
- go to every wiki
- and set these preferences:
- Set
language
to en - Set
skin
to vector
- Set
Log
[edit]Arguments
[edit]Renders a Synchbot request log which summarises the actions performed in response to this request. The following arguments are defined:
parameter | usage |
---|---|
log | The logged actions to output, as a multiline sequence of comma-separated values. Each line should consist of three fields: the time (like "00:53"), page link (like "[[s:User:Pathoschild|en.wikisource.org]]"), and log message (like "created (+116)"). Each message will be formatted by matching the log message for common patterns. |
indent | (optional) The number of times to indent the output, corresponding to the indentation level of a threaded wiki discussion. |
counts:* | (optional) Adds to the page counts used to build the summary bar graph. This is used to show an accurate summary even though some pages aren't listed (like page-doesn't-exist entries for a deletion request). Valid count keys: created, updated, deleted, missing (for missing user), skipped. |
Example (no arguments)
[edit]{{#invoke:synchbot|log| 00:53,[[w:User:Pathoschild|en.wikipedia.org]],created (+116). 00:53,[[s:User:Pathoschild|en.wikisource.org]],deleted. 00:55,[[wikt:User:Pathoschild|en.wiktionary.org]],updated (+116). }}
The following log shows what the bot did on each wiki. You can click the columns to sort the log.
summary: |
time | wiki | logged action |
---|---|---|
00:53 | en.wikipedia.org | created (+116). |
00:53 | en.wikisource.org | deleted. |
00:55 | en.wiktionary.org | updated (+116). |
Example (with arguments)
[edit]{{#invoke:synchbot|log |indent = 3 |log = 00:53,[[w:User:Pathoschild|en.wikipedia.org]],created (+116). 00:53,[[s:User:Pathoschild|en.wikisource.org]],deleted. 00:55,[[wikt:User:Pathoschild|en.wiktionary.org]],updated (+116). }}
The following log shows what the bot did on each wiki. You can click the columns to sort the log.
summary: |
time | wiki | logged action |
---|---|---|
00:53 | en.wikipedia.org | created (+116). |
00:53 | en.wikisource.org | deleted. |
00:55 | en.wiktionary.org | updated (+116). |
local p = {}
local inner = {}
--##########
--## Public functions
--##########
--- Render a Synchbot request box which summarises the request.
-- @param frame The arguments passed to the script. See docs on inner.renderRequest.
-- @test p.request({ args = { user = "Pathoschild", action = "replace", title = "User:Pathoschild/Woop.js", text = "test content", ["skip wikis"] = "enwiki, frwiki", ["skip existing"] = "no" } })
-- @test p.request({ args = { user = "Pathoschild", action = "set", title = nil, text = nil, ["skip wikis"] = "enwiki, frwiki", ["skip existing"] = "no", ["set:language"] = "en" } })
function p.request(frame)
-- pack preference settings
local settings = {}
for key,value in pairs(frame.args) do
local keyParts = mw.text.split(key, ':', true)
if(#keyParts == 2 and keyParts[1] == 'set') then
settings[keyParts[2]] = value
end
end
-- render
return inner.renderRequest(frame.args["user"], frame.args["action"], frame.args["title"], frame.args["text"], frame.args["skip wikis"], frame.args["skip existing"], settings, frame.args["status"], frame.args["flags"])
end
--- Render a Synchbot request log which summarises the actions performed in response to this request.
-- @param frame The arguments passed to the script. See docs on inner.renderLog.
-- @test p.log({ args = { ["count:skipped"] = "945", [1] = "00:53,[[w:User:Pathoschild|en.wikipedia.org]],created (+116).\n00:53,[[s:User:Pathoschild|en.wikisource.org]],deleted.\n00:55,[[wikt:User:Pathoschild|en.wiktionary.org]],updated (+116).\n00:56,[[wikidata:User:Pathoschild|wikidata.org]],skipped (user is not registered here)." } })
function p.log(frame)
-- pack counts
local counts = {}
for key,value in pairs(frame.args) do
local keyParts = mw.text.split(key, ':', true)
if(#keyParts == 2 and keyParts[1] == 'count') then
counts[keyParts[2]] = value
end
end
-- render
return inner.renderLog(inner.trimInput(frame.args[1] or frame.args["log"]), inner.trimInput(frame.args["indent"]), counts)
end
-- Render a global CSS/JS migration notice. See [[User:Pathoschild/2014–2015 global script migration]].
function p.migrationNotice(frame)
-- analyse user
local user = mw.title.getCurrentTitle().baseText
local hasGlobalCss = mw.title.new('User:' .. user .. '/global.css').exists
local hasGlobalJs = mw.title.new('User:' .. user .. '/global.js').exists
-- render migration notice
local html = {}
local write = inner.write
write(html, 'Hello ' .. user .. '. You have ')
if hasGlobalJs and hasGlobalCss then
write(html, 'global scripts and styles in <code>[[User:' .. user .. '/global.js]]</code> and <code>[[User:' .. user .. '/global.css]]</code>, which you import using [[toollabs:meta/userpages/' .. user .. '#css,js,user,subpages|your local CSS/JS pages]]. ')
elseif hasGlobalJs then
write(html, 'global scripts in <code>[[User:' .. user .. '/global.js]]</code>, which you import using [[toollabs:meta/userpages/' .. user .. '#js,user,subpages|your local JS pages]]. ')
else
write(html, 'global styles in <code>[[User:' .. user .. '/global.css]]</code>, which you import using [[toollabs:meta/userpages/' .. user .. '#css,user,subpages|your local CSS pages]]. ')
end
write(html, 'Since August 2014, your <code>global.js</code> and <code>global.css</code> pages are [[global user pages|loaded automatically on all wikis]]. ')
if hasGlobalJs then
write(html, 'Since you already import them yourself, you may experience script errors or tools being added twice. ')
end
write(html, 'Do you want me to fix this by removing the imports from your local pages using [[Synchbot]] (without changing any other content)?')
return inner.flush(html)
end
-- Render a global CSS/JS migration request for [[Synchbot]]. See [[User:Pathoschild/2014–2015 global script migration]].
-- @test p.migrationRequest({ args = { ['user'] = 'Pathoschild', ['oldid'] = '12530499', ['diffid'] = '12577547' } })
function p.migrationRequest(frame)
-- analyse user
local user = frame.args['user']
local oldid = frame.args['oldid']
local diffid = frame.args['diffid']
local hasGlobalCss = mw.title.new('User:' .. user .. '/global.css').exists
local hasGlobalJs = mw.title.new('User:' .. user .. '/global.js').exists
-- render migration notice
local html = {}
local write = inner.write
write(html, '===[[user:' .. user .. '|]]===\n')
write(html, '{{#invoke:synchbot|request\n')
write(html, ' |user = ' .. user .. '\n')
write(html, ' |action = delete\n')
if hasGlobalCss and hasGlobalJs then
write(html, ' |title = User:' .. user .. '/*.css, User:' .. user .. '/*.js\n')
elseif hasGlobalCss then
write(html, ' |title = User:' .. user .. '/*.css\n')
else
write(html, ' |title = User:' .. user .. '/*.js\n')
end
write(html, ' |text = \n')
write(html, ' |skip wikis = \n')
write(html, ' |skip existing = no\n')
write(html, ' |status = <!-- don\'t change this line -->\n')
write(html, '}}\n')
write(html, 'Remove manual ')
if hasGlobalCss and hasGlobalJs then
write(html, '<code>[[User:' .. user .. '/global.css|global.css]]</code> and <code>[[User:' .. user .. '/global.js|global.js]]</code>')
elseif hasGlobalCss then
write(html, '<code>[[User:' .. user .. '/global.css|global.css]]</code>')
else
write(html, '<code>[[User:' .. user .. '/global.js|global.js]]</code>')
end
write(html, ' imports since [[global user pages|they\'re now automatic]], and delete local pages that don\'t contain anything else. Discussed at [[Special:Diff/' .. oldid .. '/' .. diffid .. '#Global CSS/JS migration|User talk:' .. user .. '#Global CSS/JS migration]]. ~~~')
return inner.flush(html)
end
-- Render a notice like "this request was imported from X" for Synchbot archives imported from another Synchbot service.
function p.importedArchive(frame)
local link = frame.args[1]
return '<small style="color:gray;">This archived request was imported from ' .. link .. '.</small>'
end
-- Render a user entry for [[User:Pathoschild/2014 global script migration]].
function p.migrationStatus(frame)
-- read arguments
local user = inner.trimInput(frame.args['user'])
local affected = ({['y'] = true, ['n'] = false})[inner.trimInput(frame.args['affected'])]
local contacted = ({['y'] = true, ['n'] = false})[inner.trimInput(frame.args['contacted'])]
local migrated = ({['y'] = true, ['n'] = false})[inner.trimInput(frame.args['migrated'])]
local notes = inner.trimInput(frame.args['notes'])
-- render
return inner.renderMigrationStatus(user, affected, contacted, migrated, notes)
end
--##########
--## Protected functions
--##########
-- Render a Synchbot request box which summarises the request.
-- @param user The name of the user whose user pages to edit. This will be used by the bot to skip wikis where the user isn't registered.
-- @param action The action to perform on each page; one of 'delete', 'replace', 'prepend', 'append', or 'custom'.
-- @param title The title of the page to edit on each wiki (including namespace).
-- @param text The text to write to the page.
-- @param skipWikis An arbitrary list of wikis to skip.
-- @param skipExistingWikis Whether to skip wikis where the page already exists.
-- @param settings A table of user preferences to set (if any).
-- @param status The status of the request; one of 'queued' (or ''), 'done', 'not done', or 'on hold'.
-- @param flags A string containing arbitrary flags used to control the generated settings.
-- @test p.renderRequest('Pathoschild', 'replace', 'User:Pathoschild/Woop.js', 'test content', 'enwiki, frwiki', 'no')
-- @test p.renderRequest('Pathoschild', 'set', nil, nil, 'enwiki, frwiki', 'no', { ['language'] = 'en' })
function inner.renderRequest(user, action, title, text, skipWikis, skipExistingWikis, settings, status, flags)
-- parse input
user = inner.trimInput(user)
action = inner.trimInput(action)
title = inner.trimInput(title)
skipWikis = inner.trimInput(skipWikis)
skipExistingWikis = inner.trimInput(skipExistingWikis)
status = inner.trimInput(status)
settings = settings or {}
-- normalize
if skipExistingWikis == 'no' then
skipExistingWikis = false
end
-- derive config
status = ({['declined'] = 'not done'})[status] or status or 'queued'
local statusColor = ({['done'] = 'CFC', ['not done'] = 'FCC', ['withdrawn'] = 'FCC', ['on hold'] = 'FFC'})[status] or 'F2F2F2'
local renderedSkipList = nil
if skipWikis or skipExistingWikis then
if skipWikis and skipExistingWikis then
renderedSkipList = 'existing pages and ' .. skipWikis
elseif skipExistingWikis then
renderedSkipList = 'existing pages'
else
renderedSkipList = skipWikis
end
end
-- render container & status
local html = {}
local write = inner.write
write(html, '<div style="width:10em; padding:0.2em; border:1px #CCC solid; -moz-border-radius:0 2em 0 0; -webkit-border-radius:0 2em 0 0; border-radius:0 2em 0 0; border-bottom:0; background:#%s;">request %s: </div>', statusColor, status)
write(html, '<div style="padding:0.5em; border:1px solid #CCC; -moz-border-radius:0 1em 1em 1em; -webkit-border-radius:0 1em 1em 1em; border-radius:0 1em 1em 1em;">')
-- render user details
if user then
local encodedUser = mw.uri.encode(user)
write(html, '[[user:%s|%s]] <small class="plainlinks">([https://meta.toolforge.org/stalktoy/%s global account] · [https://meta.toolforge.org/crossactivity/%s recent activity] · [https://meta.toolforge.org/userpages/%s user pages])</small>', user, user, encodedUser, encodedUser, encodedUser)
else
write(html, '<span color="red">no user specified</span>')
end
write(html, '<ul>')
-- render title
write(html, '<li>go to ')
if title ~= nil then
write(html, '<code>%s</code> on ', title)
end
write(html, 'every wiki')
if renderedSkipList then
write(html, '<small> (except on %s)</small>', renderedSkipList)
end
write(html, '</li>')
-- render action
write(html, '<li>')
if not(({['delete'] = 1, ['replace'] = 1, ['prepend'] = 1, ['append'] = 1, ['set'] = 1, ['custom'] = 1})[action]) then
write(html, 'and... <span color="red">uh-oh! The action "%s" is invalid. It should be delete, replace, prepend, append, or set.</span>', action)
else
if action == 'delete' then
write(html, ' and delete the page.')
elseif action == 'set' then
write(html, ' and set these preferences:<ul>')
for key,value in pairs(settings) do
write(html, '<li>Set <code>' .. key .. '</code> to ' .. value .. '</li>')
end
write(html, '</ul>')
elseif action == 'custom' then
write(html, ' and follow the instructions below.')
else
if action == 'replace' then
write(html, ' and replace the text with ')
else
write(html, ' and %s this text: ', action)
end
write(html, '<div style="max-height:25em; margin-left:2em; padding-left:0.5em; overflow:auto; border-left:3px solid #CCC; font-size:0.9em; color:#666;">%s</div>', text)
end
end
write(html, '</ul>')
write(html, '</div>')
-- render console info
inner.renderRequestSettings(html, user, action, title, text, skipWikis, skipExistingWikis, flags)
return inner.flush(html)
end
--- Render the python settings matching the request.
-- @param html The table to which to write output.
-- @param user The name of the user whose user pages to edit. This will be used by the bot to skip wikis where the user isn't registered.
-- @param action The action to perform on each page; one of 'delete', 'replace', 'prepend', or 'append'.
-- @param title The title of the page to edit on each wiki (including namespace).
-- @param text The text to write to the page.
-- @param skipWikis An arbitrary list of wikis to skip.
-- @param skipExistingWikis Whether to skip wikis where the page already exists.
-- @param status The status of the request; one of 'queued' (or ''), 'done', 'not done', or 'on hold'.
-- @param flags A string containing arbitrary flags used to control the generated settings.
-- @test p.renderRequestSettings({}, 'Pathoschild', 'overwrite', 'User:Pathoschild/Woop.js', 'test content')
-- @test p.renderRequestSettings({}, 'Pathoschild', 'delete', 'User:Pathoschild/*.js', '', 'metawiki', false, 'globalcssjscleanup')
function inner.renderRequestSettings(html, user, action, title, text, skipWikis, skipExistingWikis, flags)
-- generate basic settings
user = user or ''
local renderedSkipList = skipWikis and ('\'' .. table.concat(mw.text.split(skipWikis, ' *, *'), '\', \'') .. '\'') or ''
local isGlobalCssJsCleanup = flags ~= nil and string.find(flags, 'globalcssjscleanup')
-- generate summary
local deleteSummary = 'None'
local editSummary = 'None'
if isGlobalCssJsCleanup then
local summary = mw.text.nowiki(mw.ustring.format('no longer needed with [[m:global user pages|global user pages]] ([[m:Synchbot|requested by %s]])', user))
deleteSummary = mw.ustring.format('\'%s\'', summary)
editSummary = mw.ustring.format('\'removed import %s\'', summary)
end
-- generate text & action
if text ~= nil and string.find(text, string.char(127)) then
text = '...' -- don't handle text which contains strip markers
end
local script = 'None'
if isGlobalCssJsCleanup then
script = 'library().remove_global_imports(bot)'
elseif action == 'replace' then
script = 'bot.save(u"""' .. mw.text.nowiki(text) .. '""")'
elseif action == 'prepend' then
script = 'bot.save(u"""' .. mw.text.nowiki(text) .. '"""' .. ' + "\\n\\n" + bot.text())'
elseif action == 'append' then
script = 'bot.save(bot.text() + "\\n\\n" + ' .. 'u"""' .. mw.text.nowiki(text) .. '""")'
elseif action == 'delete' then
script = 'bot.delete()'
end
-- render
inner.write(html, '<pre style="display:none;">')
inner.write(html, '\nrequest_user = \'%s\',\nrequest_titles = [\'%s\'],\nrequest_action = lambda bot: %s,\nskip_existing = %s,\nskip_new = %s,\nskip_unregistered = %s,\nskip_wikis = [%s],\nonly_wikis = [],\ndelete_summary = %s,\nedit_summary = %s,\n',
user,
title or 'None',
script,
skipExistingWikis and 'True' or 'False',
action == 'delete' and 'True' or 'False', -- skip uncreated pages when deleting
action == 'delete' and 'False' or 'True', -- don't skip unregistered wikis when deleting
renderedSkipList,
deleteSummary,
editSummary
)
inner.write(html, '</pre>')
end
--- Render a Synchbot request log which summarises the actions performed in response to this request.
-- @param log The logged actions to output, as a multiline sequence of comma-separated values. Each line should consist of three fields: the time (like "00:53"), page link (like "[[s:User:Pathoschild|en.wikisource.org]]"), and log message (like "created (+116)"). Each message will be formatted by matching the log message for common patterns.
-- @param indent (optional) The number of times to indent the output, corresponding to the indentation level of a threaded wiki discussion.
-- @param counts (optional) Counts to add to the bar chart for entries not shown in the log.
-- @test p.renderLog("00:53,[[w:User:Pathoschild|en.wikipedia.org]],created (+116).\n00:53,[[s:User:Pathoschild|en.wikisource.org]],deleted.\n00:55,[[wikt:User:Pathoschild|en.wiktionary.org]],updated (+116).\n00:56,[[wikidata:User:Pathoschild|wikidata.org]],skipped (user is not registered here).", 1)
function inner.renderLog(log, indent, counts)
-- derive config
indent = indent or 1
html = {}
-- render header
local write = inner.write
write(html, '<div style="max-width:50em; max-height:25em; overflow:auto; margin-left:%dem; padding:0.5em; border:1px solid #AAA; border-width:1px 0; font-size:0.85em;">The following log shows what the bot did on each wiki. You can click the columns to sort the log.<br />', indent * 2 + 1)
-- render bar chart summary
inner.renderLogChart(html, log, counts)
-- render log
write(html, '<table class="sortable"><tr><th>time</th><th>wiki</th><th>logged action</th></tr>')
log = string.gsub(log, '([^\n,]+),([^\n,]+),([^\n]+)', function(logTime, pageLink, message)
-- determine color
if string.find(message, 'created', 1, true) then
color = '#CFC'
elseif string.find(message, 'updated', 1, true) then
color = '#FDC'
elseif string.find(message, 'deleted', 1, true) or string.find(message, 'marked for deletion', 1, true) then
color = '#FCC'
else
color = '#CCC'
end
-- render entry
return mw.ustring.format('<tr style="background:%s;"><td>%s</td><td>%s</td><td>%s</td></tr>', color, logTime, pageLink, message)
end)
write(html, log)
write(html, '</table></div>')
-- render
return inner.flush(html)
end
--- Render a visualisation which summarises the logged actions.
-- @param html The table to which to write the chart.
-- @param log The logged actions to output, as a multiline sequence of comma-separated values. Each line should consist of three fields: the time (like "00:53"), page link (like "[[s:User:Pathoschild|en.wikisource.org]]"), and log message (like "created (+116)"). Each message will be formatted by matching the log message for common patterns.
-- @param counts (optional) Counts to add to the bar chart for entries not shown in the log.
function inner.renderLogChart(html, log, counts)
-- count log actions
local created = inner.countMatches(log, 'created')
local updated = inner.countMatches(log, 'updated')
local deleted = inner.countMatches(log, 'deleted') + inner.countMatches(log, 'marked for deletion')
local missing = inner.countMatches(log, 'user is not registered here') + inner.countMatches(log, 'user is detached here') + inner.countMatches(log, 'user is unregistered here')
local skipped = inner.countMatches(log, 'skip') - missing
-- add manual counts
created = created + (counts['created'] or 0)
updated = updated + (counts['updated'] or 0)
deleted = deleted + (counts['deleted'] or 0)
missing = missing + (counts['missing'] or 0)
skipped = skipped + (counts['skipped'] or 0)
-- format chart
if created + updated + deleted + missing + skipped > 0 then
local BarChart = require('Module:Bar')
local bars = BarChart.renderFromLua({
{value = created, color = 'green', title = created .. ' pages created'},
{value = updated, color = 'orange', title = updated .. ' pages updated'},
{value = deleted, color = 'red', title = deleted .. ' pages deleted'},
{value = skipped, color = '#CCC', title = skipped .. ' pages skipped'},
{value = missing, color = 'transparent', title = missing .. ' pages skipped on wikis where the user isn\'t registered', css='border:1px solid #CCC'},
nil,
'30em'
});
-- render log table
inner.write(html, '<table><tr><td>summary: </td><td style="width:30em;">%s</td></tr></table>', tostring(bars))
end
end
-- Render a user entry for [[User:Pathoschild/2014 global script migration]].
-- @param user The name of the user to migrate.
-- @param affected Whether the user needs to be migrated.
-- @param contacted Whether the user has been offered a migration.
-- @param migrated Whether the user has been migrated.
-- @param notes Ad-hoc notes about the migration.
-- @test p.renderMigrationStatus('Pathoschild', false, nill, nil, nil)
-- @test p.renderMigrationStatus('Pathoschild', true, nill, nil, nil)
-- @test p.renderMigrationStatus('Pathoschild', true, true, nil, 'no response to offer yet.')
-- @test p.renderMigrationStatus('Pathoschild', true, true, true, '[[synchbot|migrated]]')
-- @test p.renderMigrationStatus('Pathoschild', true, true, false, 'declined migration')
function inner.renderMigrationStatus(user, affected, contacted, migrated, notes)
-- choose row style
local rowStyle = ''
if migrated or affected == false then
rowStyle = 'background:#CFC;'
elseif contacted and migrated == nil then
rowStyle = 'background:#CCC; color:#666;'
elseif contacted == false or (contacted and migrated == false) then
rowStyle = 'background:#FCC;'
end
-- choose cell styles
local affectedStyle = affected and 'background:#CFC;' or ''
local contactedStyle = contacted and 'background:#CFC;' or ''
local affectedIcons = {[true] = '✓', [false] = '∅'}
local icons = {[true] = '✓', [false] = '✖'}
-- render
local html = {}
local write = inner.write
write(html, '|- style="' .. rowStyle .. '"\n')
write(html, '| \'\'\'[[user:' .. user .. '|' .. user .. ']]\'\'\' (<small>[[User talk:' .. user .. '|talk]] • [[Special:Contributions/' .. user .. '|contribs]]</small>)\n')
write(html, '| <small class="plainlinks">[[toollabs:meta/userpages/' .. user .. '#css,js,user,talk,subpages|pages]] • [[toollabs:meta/crossactivity/' .. user .. '|activity]] • [[toollabs:meta/stalktoy/' .. user .. '|account]]</small>\n')
write(html, '| [[user:' .. user .. '/global.css|css]] • [[user:' .. user .. '/global.js|js]]\n')
write(html, '|style="' .. affectedStyle .. '"| ' .. (affectedIcons[affected] or '') .. '\n')
write(html, '|style="' .. contactedStyle .. '"| ' .. (icons[contacted] or '') .. '\n')
write(html, '| ' .. (icons[migrated] or '') .. '\n')
write(html, '| <small>' .. (notes or '') .. '</small>\n')
return inner.flush(html)
end
--- Write an output message to a cache table for eventual concatenation.
-- @param seq The table to which to save the message.
-- @param message The message to save.
-- @param ... The format arguments to apply to the message, if any.
function inner.write(seq, message, ...)
if select('#', ...) == 0 then
table.insert(seq, message)
else
table.insert(seq, mw.ustring.format(message, ...))
end
end
--- Concatenate an output cache table into an output-ready string.
-- @param seq The table with messages to output.
function inner.flush(seq)
return table.concat(seq)
end
--- Count the number of times a substring occurs in a string.
-- @param str The string to search.
-- @param substr The substring whose occurences to count.
function inner.countMatches(str, substr)
local _, count = string.gsub(str, substr, substr)
return count
end
--- Normalize a user-input string by trimming outer whitespace, and replacing it with nil if it's empty.
-- @param str The input string to normalize.
function inner.trimInput(str)
-- trim
if str then
str = mw.text.trim(str)
end
-- return input or nil
if not str or str == '' then
return nil
end
return str
end
return p