Module:RFX report: Difference between revisions

Content deleted Content added
fix pattern match
update from /sandbox. Implements support for RRfAs
 
(19 intermediate revisions by 10 users not shown)
Line 1:
-- This module is a replacement for the RfX report bot.
 
local rfx = require( 'Module:Rfx' )
--[==[
local colours = mw.loadData( 'Module:RFX report/colour' )
TODO:
* Add a purge link (but where?)
* Show a message if there are no active RfXs (again, where?)
* Get the last active RfX date if there are none open?
This could be screen-scraped from
[[Wikipedia:Successful requests for adminship/2013]] etc.
* Comment the code properly
--]==]
 
local colours = mw.loadData('Module:RFX report/colour')
 
local p = {}
local r = {
rfa = {},
rfb = {}
}
 
local function getLongRfxNamegetTableLength(rfxTypetbl)
local length = 0
if rfxType == 'rfa' then
for _ in pairs(tbl) do
return 'adminship'
length = length + 1
elseif rfxType == 'rfb' then
end
return 'bureaucratship'
return length
else
error('invalid rfx code detected - must be either "rfa" or "rfb"', 2)
end
end
 
local function matchRfxgetRfxes(text, rfxType)
-- Get the title object for [[Wikipedia:Requests for adminship]].
return mw.ustring.gmatch( text, '{{[wW]ikipedia:[rR]equests for ' .. getLongRfxName(rfxType) .. '/([^{}]-)}}' )
local noError, rfa = pcall( mw.title.new, 'Wikipedia:Requests for adminship' )
end
if not noError or ( noError and not rfa ) then
 
return nil
local function addRfxToTable(text, rfxType, exceptions)
end
for rfxPage in matchRfx(text, rfxType) do
local rfaText = rfa:getContent()
if not rfaText then
return nil
end
-- Return a table with a list of pages transcluded from
-- [[Wikipedia:Requests for adminship]], minus the exceptions
-- which are always transcluded there.
local t = {}
local exceptions = { 'Front matter', 'Header', 'bureaucratship' }
for rfxPage, rfxSubpage in mw.ustring.gmatch( rfaText, '{{[ _]*([wW]ikipedia:[rR]equests for %w+/([^{}]-))[ _]*}}' ) do
local isException = false
iffor _, v in ipairs( exceptions then) do
forif _,rfxSubpage == v in ipairs(exceptions) dothen
if rfxPageisException == v thentrue
isException = true
end
end
end
if not isException then
table.insert( r[rfxType]t, { page = 'Wikipedia:Requests for ' .. getLongRfxName(rfxType) .. '/' .. rfxPage } )
end
end
return t
end
 
local function getRfxPagesmakeRow( rfxObject )
if not ( type( rfxObject ) == 'table' and rfxObject.getTitleObject and rfxObject.getSupportUsers ) then
local title = mw.title.new( 'Wikipedia:Requests for adminship' )
return nil
local text = title:getContent()
addRfxToTable( text, 'rfa', {'Front matter', 'bureaucratship'} )
addRfxToTable( text, 'rfb' )
end
 
local function getRfxSections(page)
local pageObject = mw.title.new(page)
if not pageObject then
error(tostring(page) .. ' is an invalid title', 2)
end
local pageTextstatus = pageObjectrfxObject:getContentgetStatus()
local page = rfxObject:getTitleObject().prefixedText
if not pageText then
local user = rfxObject.user or rfxObject:getTitleObject().subpageText
error('could not get page content from page ' .. pageObject.title, 2)
local supports = rfxObject.supports
local opposes = rfxObject.opposes
local neutrals = rfxObject.neutrals
local percent = rfxObject.percent
local colour
if percent then
colour = colours[ rfxObject.type ][ percent ]
end
colour = colour or ''
local intro, s, o, n = mw.ustring.match(
pageText,
'^(.-)\n====[^=]-====.-'
.. '\n=====%s*[sS]upport%s*=====(.-)'
.. '\n=====%s*[oO]ppose%s*=====(.-)'
.. '\n=====%s*[nN]eutral%s*=====(.-)$'
 
local percentStr = mw.ustring.format( '%d', percent )
)
if percent == 0 and supports == 0 and opposes == 0 and neutrals == 0 then
return intro, s, o, n
percentStr = 'N/A'
end
elseif percent == 100 and opposes ~= 0 then
 
percentStr = '>99'
-- Returns a table of usernames that voted in a particular section.
local function getVoteTable(section)
section = mw.ustring.match(section, '^(.-\n#.-)\n[^#]') or section -- Discard subsequent numbered lists.
local t = {}
for vote in mw.ustring.gmatch(section, '\n#[^#*;:][^\n]*') do
local username = false
for link in mw.ustring.gmatch(vote, '%[%[([^%[%]]-)%]%]') do
-- If there is a pipe, get the text before it.
if mw.ustring.match(link, '|') then
link = mw.ustring.match(link, '^(.-)|')
end
-- If there is a slash, get the text before that.
if mw.ustring.match(link, '/') then
link = mw.ustring.match(link, '^(.-)/')
end
-- Decode html entities and percent encodings.
link = mw.text.decode(link, true)
link = mw.uri.decode(link, 'WIKI')
-- If there is a hash, get the text before that.
if mw.ustring.match(link, '#') then
link = mw.ustring.match(link, '^(.-)#')
end
local userLink = mw.ustring.match(link, '^[%s_]*[uU][sS][eE][rR][%s_]*:[%s_]*(.-)[%s_]*$')
local userTalkLink = mw.ustring.match(link, '^[%s_]*[uU][sS][eE][rR][%s_]*[tT][aA][lL][kK][%s_]*:[%s_]*(.-)[%s_]*$')
if userLink then
username = userLink
elseif userTalkLink then
username = userTalkLink
end
end
if not username then
username = mw.ustring.format( 'Error processing vote no. %d. (Random number: %.8f.)', #t, math.random() )
end
table.insert(t, username)
end
return t
end
 
local votes
local function checkDups(...)
if supports and opposes and neutrals and percent then
local t = {}
local tables votes = {mw.ustring..}format( [==[
for i, v in ipairs(tables) do
| class="rfx-report-number" | [[%s#Support|%d]]
for j, name in ipairs(v) do
| class="rfx-report-number" | [[%s#Oppose|%d]]
if t[name] then
| class="rfx-report-number" | [[%s#Neutral|%d]]
return true
| class="rfx-report-number rfx-report-percent" style="background: #%s; color: #202122" | %s]==],
else
page, t[name] = truesupports,
endpage, opposes,
page, neutrals,
colour, percentStr
)
else
votes = '\n| colspan="4" class="rfx-report-error" | Error parsing votes'
end
if status then
status = mw.language.getContentLanguage():ucfirst( status )
if status == 'Pending closure' then
status = 'Pending closure...'
end
status = '\n|' .. status
else
status = '\n| class="rfx-report-error" | Error getting status'
end
local endTime = rfxObject.endTime
local secondsLeft = rfxObject:getSecondsLeft()
local timeLeft = rfxObject:getTimeLeft()
local time
if endTime and timeLeft then
time = mw.ustring.format( '\n| %s\n| %s', endTime, timeLeft )
else
time = '\n| colspan="2" class="rfx-report-error" | Error parsing end time'
end
local dupes = rfxObject:dupesExist()
return false
if dupes then
dupes = '<span class="rfx-report-dupes-yes">yes</span>'
elseif dupes == false then
dupes = 'no'
else
dupes = '--'
end
local report = rfxObject:getReport()
if report then
report = mw.ustring.format( '\n| [%s report]', tostring( report ) )
else
report = '\n| class="rfx-report-error" | Report not found'
end
local pending_class = ''
if status == 'pending closure' then
pending_class = 'class="rfx-report-pending"'
end
return mw.ustring.format(
'\n|-%s\n| [[%s|%s]]%s%s%s\n| class="rfx-report-dupes" | %s%s',
pending_class, page, user, votes, status, time, dupes, report
)
end
 
local function getEndTimemakeHeading(intro rfxType )
local frame = mw.getCurrentFrame()
local ret = mw.ustring.match(intro, '(%d%d:%d%d, %d+ %w+ %d+) %(UTC%)')
returnlocal retrfxCaps
if rfxType == 'rfa' then
end
rfxCaps = 'RfA'
 
elseif rfxType == 'rfb' then
local function getUser(intro)
rfxCaps = 'RfB'
local ret = mw.ustring.match(intro, '===%s*%[%[%s*[wW]ikipedia%s*:%s*[rR]equests for %w+/.-|%s*(.-)%s*%]%]%s*===')
elseif rfxType == 'rrfa' then
return ret
rfxCaps = 'RRfA'
end
 
local function getTimeLeft(endTime)
local lang = mw.getContentLanguage()
local now = tonumber( lang:formatDate("U") )
endTime = tonumber( lang:formatDate("U", endTime) )
local secondsLeft = endTime - now
local daysLeft
if secondsLeft <= 0 then
daysLeft = 'Pending closure...'
else
return nil
daysLeft = mw.ustring.gsub( lang:formatDuration(secondsLeft, {'days', 'hours'}), ' and', ',')
end
return daysLeftmw.ustring.format(
'\n|-\n! scope="col" | %s candidate !! scope="col" | <abbr title="Support">S</abbr> !! scope="col" | <abbr title="Oppose">O</abbr> !! scope="col" | <abbr title="Neutral">N</abbr> !! scope="col" | <abbr title="Support percentage (%%)">S %%</abbr> !! scope="col" | Status !! scope="col" | Ending (UTC) !! scope="col" | Time left !! scope="col" | <abbr title="Has duplicate votes?">Dups?</abbr> !! scope="col" | Report',
rfxCaps
)
end
 
local function makeRfxTablesgetRfasRfbsSeparate(rfxType)
local rfxes = getRfxes()
for i, rfx in ipairs(r[rfxType]) do
if not rfxes then
local intro, s, o, n = getRfxSections(rfx.page)
sreturn = getVoteTable(s)nil
o = getVoteTable(o)
n = getVoteTable(n)
r[rfxType][i].user = getUser(intro)
r[rfxType][i].support = #s
r[rfxType][i].oppose = #o
r[rfxType][i].neutral = #n
r[rfxType][i].percent = math.floor( (r[rfxType][i].support / (r[rfxType][i].support + r[rfxType][i].oppose) * 100) + 0.5 )
r[rfxType][i].dups = checkDups(s, o, n)
r[rfxType][i].endTime = getEndTime(intro)
r[rfxType][i].timeLeft = getTimeLeft( r[rfxType][i].endTime )
r[rfxType][i].report = '[//tools.wmflabs.org/xtools/rfa/?p=' .. mw.uri.encode( r[rfxType][i].page ) .. ' report]'
end
-- Get RfX objects and separate RfAs and RfBs.
local rfas = {}
local rfbs = {}
local rrfas = {}
for i, rfxPage in ipairs( rfxes ) do
local rfxObject = rfx.new( rfxPage )
if rfxObject then
if rfxObject.type == 'rfa' then
table.insert( rfas, rfxObject )
elseif rfxObject.type == 'rfb' then
table.insert( rfbs, rfxObject )
elseif rfxObject.type == 'rrfa' then
table.insert( rrfas, rfxObject)
end
end
end
return rfas, rfbs, rrfas
end
 
local function makeReportRows(rfxType)
local rfas, rfbs, rrfas = getRfasRfbsSeparate()
local ret = ''
 
for i, rfx in ipairs(r[rfxType]) do
local ret = local dups{}
if #rfas + #rrfas if> r[rfxType][i].dups0 then
table.insert( ret, makeHeading( 'rfa' dups) = "'''yes'''")
for i, rfaObject in ipairs( rfas ) do
else
dupstable.insert( =ret, 'no'makeRow( rfaObject ) )
end
for i, rrfaObject in ipairs( rrfas ) do
table.insert( ret, makeRow( rrfaObject ) )
end
end
local style = ''
local styleInline = ''
if r[rfxType][i].timeLeft ==#rfbs 'Pending> closure...'0 then
table.insert( ret, makeHeading( 'rfb' ) )
style = ' style="background: #f8cdc6;" |'
for i, rfbObject in styleInlineipairs( =rfbs ') background: #f8cdc6;'do
table.insert( ret, makeRow( rfbObject ) )
end
local page = r[rfxType][i].page
local colour = colours[rfxType][ r[rfxType][i].percent ]
ret = ret .. mw.ustring.format( [==[
 
|-
|%s [[%s|%s]]
| style="text-align: right;%s" | [[%s#Support|%d]]
| style="text-align: right;%s" | [[%s#Oppose|%d]]
| style="text-align: right;%s" | [[%s#Neutral|%d]]
| style="text-align: right; background: #%s;" | %d
|%s %s
|%s %s
| style="text-align: center;%s" | %s
|%s %s]==],
style, page, r[rfxType][i].user,
styleInline, page, r[rfxType][i].support,
styleInline, page, r[rfxType][i].oppose,
styleInline, page, r[rfxType][i].neutral,
colour, r[rfxType][i].percent,
style, r[rfxType][i].endTime,
style, r[rfxType][i].timeLeft,
styleInline, dups,
style, r[rfxType][i].report
)
end
return table.concat( ret )
end
 
local function makeReport( args )
local purgeLink = mw.title.getCurrentTitle():fullUrl( 'action=purge' )
getRfxPages()
local header = mw.ustring.format(
makeRfxTables('rfa')
'\n|+ Requests for [[Wikipedia:Requests for adminship|adminship]] and [[Wikipedia:Requests for bureaucratship|bureaucratship]] <span class="rfx-report-purge plainlinks">[%s update]</span>',
makeRfxTables('rfb')
purgeLink
)
local rows = makeReportRows() or ''
if rows == '' then
rows = '\n|-\n| colspan="10" | No current discussions. <span class="rfx-report-recent">[[WP:Requests for adminship by year|Recent RfAs]], recent RfBs: ([[Wikipedia:Successful bureaucratship candidacies|successful]], [[Wikipedia:Unsuccessful bureaucratship candidacies|unsuccessful]])</span>'
end
local float = args.float or args.align or 'right'
localif clearnot =float argsor mw.cleartext.trim(float) or== 'left' then
float = nil
local ret = '{| class="wikitable" '
if args.style then
ret = ret .. args.style
else
ret = ret .. 'style="white-space:wrap; clear: ' .. clear .. '; '
.. 'margin-top: 0em; margin-bottom: .5em; float: ' .. float .. '; '
.. 'padding: .5em 0em 0em 1.4em; background: #ffffff; '
.. 'border-collapse: collapse; border-spacing: 0;"'
end
iflocal #r.rfaclear >= 0 thenargs.clear
if not clear or mw.text.trim(clear) == '' then
ret = ret .. '\n|-\n! RfA candidate !! S !! O !! N !! S% !! Ending (UTC) !! Time left !! Dups? !! Report'
clear = nil
ret = ret .. makeReportRows('rfa')
end
if #r.rfb > 0 then
local style = ''
ret = ret .. '\n|-\n! RfB candidate !! S !! O !! N !! S% !! Ending (UTC) !! Time left !! Dups? !! Report'
if float or clear then
ret = ret .. makeReportRows('rfb')
style = string.format(
'style="%s%s"',
clear and ('clear: ' .. clear .. ';') or '',
float and ('float: ' .. float .. ';') or ''
)
end
 
ret = ret .. '\n|-\n|}'
return retmw.getCurrentFrame():extensionTag{
name = 'templatestyles', args = { src = 'Module:RFX report/styles.css' }
} .. mw.ustring.format(
'\n{| class="wikitable rfx-report" %s%s%s\n|-\n|}',
style,
header,
rows
)
end
 
function p.countRfas()
local rfas, rfbs, rrfas = getRfasRfbsSeparate()
return getTableLength(rfas)
end
 
function p.main( frame )
-- Process the arguments.
local args
if frame == mw.getCurrentFrame() then
args = frame:getParent().args
for k, v in pairs( frame.args ) do
args = frame.args
break
Line 255 ⟶ 248:
args = frame
end
return makeReport( args )
end