Jump to content

Module:Rfx

Permanently protected module
From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mr. Stradivarius (talk | contribs) at 13:52, 15 July 2013 (Make vote-getters methods instead of table properties, as having them as sub-tables was causing metatable problems. Also fix a bug in getDupesExist (and change the name).). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

----------------------------------------------------------------------
--                          Module:Rfx                              --
-- This is a library for retrieving information about requests      --
-- for adminship and requests for bureaucratship on the English     --
-- Wikipedia. Please see the module documentation for instructions. --
----------------------------------------------------------------------

local libraryUtil = require( 'libraryUtil' )

--------------------------------------
--         Helper functions         --
--------------------------------------

local function allArgsExist( obj, ... )
    obj = obj or {}
    local args = { ... }
    for i, v in ipairs( args ) do
        if not obj[ v ] then
            return false
        end
    end
    return true
end

-- Get a page object, passing the function through pcall 
-- in case we are over the expensive function count limit.
local function getPageObject( page )
    if not page then
        return nil
    end
    local noError, pageObject = pcall( mw.title.new, page )
    if not noError or ( noError and not pageObject ) then
        return nil
    end
    return pageObject
end

-- Returns an array of usernames that voted in a particular section.
-- If a username cannot be processed, an error message is displayed
-- in the table field, together with a random number to avoid it
-- being mistaken for a duplicate vote.
local function getVoteTable( section )
    if type( section ) ~= 'string' then
        return nil
    end
    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 + 1, math.random() )
        end
        table.insert(t, username)
    end
    return t
end

local function checkDups( ... )
    local t = {}
    local tables = { ... }
    for i, v  in ipairs( tables ) do
        for j, name in ipairs( v ) do
            if t[ name ] then
                return true
            else
                t[ name ] = true
            end
        end
    end
    return false
end

------------------------------------------
--   Define the constructor function    --
------------------------------------------

local rfx = {}

function rfx.new( pagename )
    local obj = {}
    local data = {}
    local checkSelf = libraryUtil.makeCheckSelfFunction( 'Module:Rfx', 'rfx', obj, 'rfx object' )
    
    -- Get the page object and check to see whether we are a subpage of WP:RFA or WP:RFB.
    if type( pagename ) ~= 'string' then
        return nil
    end
    data.page = getPageObject( pagename )
    if not data.page then
        return nil
    end
    local rfaPage = getPageObject( 'Wikipedia:Requests for adminship' )
    local rfbPage = getPageObject( 'Wikipedia:Requests for bureaucratship' )
    if rfaPage and data.page:isSubpageOf( rfaPage ) then
        data.type = 'rfa'
    elseif rfbPage and data.page:isSubpageOf( rfbPage ) then
        data.type = 'rfb'
    else
        return nil
    end
    
    -- Get the page content and process it for votes, end times, etc.
    local pageText = data.page:getContent()
    local introText, supportText, opposeText, neutralText
    if type( pageText ) == 'string' then
        introText, supportText, opposeText, neutralText = mw.ustring.match(
            pageText,
            '^(.-)\n====[^=\n][^\n]-====.-'
            .. '\n=====%s*[sS]upport%s*=====(.-)'
            .. '\n=====%s*[oO]ppose%s*=====(.-)'
            .. '\n=====%s*[nN]eutral%s*=====(.-)$'
        )
    end
    -- Get the user lists as methods, as putting the tables directly
    -- in the data table causes metatable headaches.
    if supportText and opposeText and neutralText then
        function data:getSupportUsers()
            return getVoteTable( supportText )
        end
        function data:getOpposeUsers()
            return getVoteTable( opposeText )
        end
        function data:getNeutralUsers()
            return getVoteTable( neutralText )
        end
    end
    local supportUsers = data:getSupportUsers()
    local opposeUsers = data:getOpposeUsers()
    local neutralUsers = data:getNeutralUsers()
    if supportUsers and opposeUsers and neutralUsers then
        data.supports = #supportUsers
        data.opposes = #opposeUsers
        data.neutrals = #neutralUsers
    end
    if introText then
        data.endTime = mw.ustring.match( introText, '(%d%d:%d%d, %d+ %w+ %d+) %(UTC%)')
        data.user = mw.ustring.match( introText, '===%s*%[%[%s*[wW]ikipedia%s*:%s*[rR]equests for %w+/.-|%s*(.-)%s*%]%]%s*===')
    end
    
    -- Checks for duplicate botes
    function data:dupesExist()
        checkSelf( self, 'dupesExist' )
        if not ( supportUsers and opposeUsers and neutralUsers ) then
            return nil
        end
        return checkDups( supportUsers, opposeUsers, neutralUsers )
    end
    
    -- Functions to get time left.
    function data:getSecondsLeft()
        checkSelf( self, 'getSecondsLeft' )
        if not self.endTime then
            return nil
        end
        local lang = mw.getContentLanguage()
        local now = tonumber( lang:formatDate("U") )
        
        local noError, endTime = pcall( lang.formatDate, lang, 'U', self.endTime )
        if not noError or ( noError and type( endTime ) ~= 'string' ) then
            return nil
        end
        local endTime = tonumber( endTime )
        if type( endTime ) ~= 'number' then
            return nil
        end
        local secondsLeft = endTime - now
        if secondsLeft <= 0 then
            return 0
        else
            return secondsLeft
        end
    end
    function data:getTimeLeft()
        checkSelf( self, 'getTimeLeft' )
        local lang = mw.getContentLanguage()
        local secondsLeft = self:getSecondsLeft()
        if not secondsLeft then
            return nil
        end
        local daysLeft
        if secondsLeft <= 0 then
            daysLeft = 'Pending closure...'
        else
            daysLeft = mw.ustring.gsub( lang:formatDuration( secondsLeft, {'days', 'hours'}), ' and', ',' )
        end
        return daysLeft
    end
    
    -- Specify which fields are read-only, and prepare the metatable.
    local readOnlyFields = {
        getTimeLeft = true,
        getSecondsLeft = true,
        dupsExist = true,
        endTime = true,
        user = true,
        supports = true,
        opposes = true,
        neutrals = true,
        supportUsers = true,
        opposeUsers = true,
        neutralUsers = true,
        ['type'] = true,
        page = true
    }
    
    local function pairsfunc( t, k )
        local v
        repeat
            k = next( readOnlyFields, k )
            if k == nil then
                return nil
            end
            v = t[k]
        until v ~= nil
        return k, v
    end
    
    return setmetatable( obj, {
        __eq = function( o1, o2 )
            return mw.title.equals( o1.page, o2.page )
        end,
        __pairs = function ( t )
            return pairsfunc, t, nil
        end,
        __index = data,
        __newindex = function( t, key, value )
            if readOnlyFields[ key ] then
                error( 'index "' .. key .. '" is read-only', 2 )
            else
                rawset( t, key, value )
            end
        end,
        __tostring = function( t )
            return t.page.prefixedText
        end
    })
end

return rfx