Jump to content

Module:Archive list: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
m Protected Module:Archive list: High-risk Lua module ([Edit=Block all non-admin users] (indefinite) [Move=Block all non-admin users] (indefinite))
split up into more functions, and add the ability to specify custom separators and custom start and end points
Line 1: Line 1:
-- This module implements {{archive list}} in Lua, and adds a few
-- This module implements {{archive list}} in Lua, and adds a few
-- new features.
-- new features.

-- Process a numeric argument to make sure it is a positive
-- integer.
local function processNumArg( num )
if num then
num = tonumber( num )
if type( num ) == 'number' then
num = math.floor( num )
if num >= 0 then
return num
end
end
end
return nil
end


-- Checks whether a page exists, going through pcall
-- Checks whether a page exists, going through pcall
Line 43: Line 58:
-- maximum of 500,000 possible archives before we hit the
-- maximum of 500,000 possible archives before we hit the
-- expensive function limit.
-- expensive function limit.
local function getBiggestArchiveNum( prefix )
local function getBiggestArchiveNum( prefix, max )
-- Return the value for max if it is specified.
max = processNumArg( max )
if max then
return max
end
-- Otherwise, detect the largest archive number.
local check1000 = checkArchives( prefix, 1000, 1 )
local check1000 = checkArchives( prefix, 1000, 1 )
if check1000 == 1 then
if check1000 == 1 then
Line 57: Line 79:
end
end


-- Get the archive link prefix (the title of the archive pages
local function getPrefix( args )
-- Get the root page.
-- minus the number).
local function getPrefix( root, prefix, prefixSpace )
local root = args.root or mw.title.getCurrentTitle().prefixedText
local ret = root or mw.title.getCurrentTitle().prefixedText
-- Get the prefix.
ret = ret .. '/'
local prefix = root .. '/'
if prefix then
ret = ret .. prefix
if args.prefix then
prefix = prefix .. args.prefix
if prefixSpace == 'yes' then
if args.prefixspace == 'yes' then
ret = ret .. ' '
prefix = prefix .. ' '
end
end
else
else
prefix = prefix .. 'Archive '
ret = ret .. 'Archive '
end
end
return prefix
return ret
end
end


-- Generate the archive links from the values found in the
-- Get the number of archives to put on one line. Set to
-- math.huge if there should be no line breaks.
-- other functions.
local function _main( args )
local function getLineNum( links, nobr )
local linksToNum = tonumber( links )
-- Get the number of archives to put on one line. Set to
-- math.huge if there should be no line breaks.
local links = tonumber( args.links )
local lineNum
local lineNum
if args.nobr == 'yes' or (args.links and not links) then
if nobr == 'yes' or (links and not linksToNum) then
lineNum = math.huge
lineNum = math.huge
-- If links is a number, process it. Negative values and expressions
-- If links is a number, process it. Negative values and expressions
-- such as links=8/2 produced some interesting values with the old
-- such as links=8/2 produced some interesting values with the old
-- template, but we will ignore those for simplicity.
-- template, but we will ignore those for simplicity.
elseif type(links) == 'number' and links >= 0 then
elseif type(linksToNum) == 'number' and linksToNum >= 0 then
-- The old template rounded down decimals to the nearest integer.
-- The old template rounded down decimals to the nearest integer.
lineNum = math.floor( links )
lineNum = math.floor( linksToNum )
if lineNum == 0 then
if lineNum == 0 then
-- In the old template, values of links between 0 and 0.999
-- In the old template, values of links between 0 and 0.999
Line 97: Line 116:
lineNum = 10 -- Default to 10 links.
lineNum = 10 -- Default to 10 links.
end
end
return lineNum
end
-- Get the prefix and the biggest archive number.

local prefix = getPrefix ( args )
-- Gets the prefix to put before the archive links.
local archiveMax = getBiggestArchiveNum( prefix )
local function getLinkPrefix( prefix, space )
-- Get the link prefix.
local ret = ''
if type(prefix) == 'string' then
ret = prefix
if space == 'yes' then
ret = ret .. ' '
end
end
return ret
end

-- Get the number to start listing archives from.
local function getStart( start )
start = processNumArg( start )
if start then
return start
else
return 1
end
end

-- Generates the list of archive links. glargs.max must be either zero (for
-- no archives) or a positive integer value.
local function generateLinks( glargs )
if type( glargs ) ~= 'table' or not glargs.max or not glargs.prefix then
error('insufficient arguments passed to generateLinks', 2)
end
-- If there are no archives yet, return a message and a
-- If there are no archives yet, return a message and a
-- link to create Archive one.
-- link to create Archive one.
if archiveMax == 0 then
if glargs.max == 0 then
return 'no archives yet ([[' .. prefix .. '1|create]])'
return 'no archives yet ([[' .. glargs.prefix .. '1|create]])'
end
end
-- Return an html error if the start number is greater than the
-- Get the link prefix.
-- maximum number.
local linkprefix = ''
local start = glargs.start or 1
if args.linkprefix then
if start > glargs.max then
return '<span class="error">Start value "'
linkprefix = args.linkprefix
if args.linkprefixspace == 'yes' then
.. tostring( start )
.. '" is greater than the most recent archive number "'
linkprefix = linkprefix .. ' '
end
.. tostring( glargs.max )
.. '".</span>'
end
end
local linkPrefix = glargs.linkPrefix or ''
local sep = glargs.sep or ', '
local lineNum = glargs.lineNum or 10
local lineSep = glargs.lineSep or '<br />'

-- Generate the archive links.
-- Generate the archive links.
local lineCounter = 1 -- The counter to see whether we need a line break or not.
local lineCounter = 1 -- The counter to see whether we need a line break or not.
local ret = '' -- The string to return.
local ret = {} -- A table containing the strings to be returned.
for archiveNum = 1, archiveMax do
for archiveNum = start, glargs.max do
local link = mw.ustring.format(
ret = ret .. '[[' .. prefix .. tostring(archiveNum) .. '|' .. linkprefix .. tostring(archiveNum) .. ']]'
'[[%s%d|%s%d]]',
glargs.prefix, archiveNum, linkPrefix, archiveNum
)
table.insert( ret, link )
-- If we don't need a new line, output a comma. We don't need
-- If we don't need a new line, output a comma. We don't need
-- a comma after the last link.
-- a comma after the last link.
if lineCounter < lineNum and archiveNum < archiveMax then
if lineCounter < lineNum and archiveNum < glargs.max then
ret = ret .. ', '
table.insert( ret, sep )
lineCounter = lineCounter + 1
lineCounter = lineCounter + 1
-- Output new lines if needed. We don't need a new line after
-- Output new lines if needed. We don't need a new line after
-- the last link.
-- the last link.
elseif lineCounter >= lineNum and archiveNum < archiveMax then
elseif lineCounter >= lineNum and archiveNum < glargs.max then
ret = ret .. '<br />'
table.insert( ret, lineSep )
lineCounter = 1
lineCounter = 1
end
end
end
end
return ret
return table.concat( ret )
end

-- Get the archive data and pass it to generateLinks().
local function _main( args )
local prefix = getPrefix( args.root, args.prefix, args.prefixspace )
local max = getBiggestArchiveNum( prefix, args.max )
local lineNum = getLineNum( args.links, args.nobr )
local linkPrefix = getLinkPrefix( args.linkprefix, args.linkprefixspace )
local start = getStart( args.start )
local glargs = {
start = start,
max = max,
prefix = prefix,
linkPrefix = linkPrefix,
sep = args.sep,
lineNum = lineNum,
lineSep = args.linesep
}
return generateLinks( glargs )
end
end


Line 140: Line 214:
-- #invoke.
-- #invoke.
local function _count( args )
local function _count( args )
local prefix = getPrefix( args )
local prefix = getPrefix( args.root, args.prefix, args.prefixspace )
local archiveMax = getBiggestArchiveNum( prefix )
local archiveMax = getBiggestArchiveNum( prefix )
return archiveMax
return archiveMax
Line 162: Line 236:
end
end
-- Trim whitespace from all arguments, and ignore blank values for
-- Ignore blank values for parameters other than "links",
-- parameters other than "links", which functions differently
-- which functions differently depending on whether it is
-- depending on whether it is blank or absent.
-- blank or absent.
local args = {}
local args = {}
for k, v in pairs( origArgs ) do
for k, v in pairs( origArgs ) do
v = mw.text.trim(v)
if k == 'links' or v ~= '' then
if k == 'links' or v ~= '' then
args[k] = v
args[k] = v

Revision as of 08:58, 3 July 2013

-- This module implements {{archive list}} in Lua, and adds a few
-- new features.

-- Process a numeric argument to make sure it is a positive
-- integer.
local function processNumArg( num )
    if num then
        num = tonumber( num )
        if type( num ) == 'number' then
            num = math.floor( num )
            if num >= 0 then
                return num
            end
        end
    end
    return nil
end

-- Checks whether a page exists, going through pcall
-- in case we are over the expensive function limit.
local function checkPageExists( title )
    if not title then
        error('No title passed to checkArchiveExists', 2)
    end
    local noError, titleObject = pcall(mw.title.new, title)
    if not noError then
        -- If we are over the expensive function limit then assume
        -- that the page doesn't exist.
        return false
    else
        if titleObject then
            return titleObject.exists
        else
            return false -- Return false if given a bad title.
        end
    end
end

-- Checks every nth archive to see if it exists, and returns the
-- number of the first archive that doesn't exist. It is
-- necessary to do this in batches because each check is an
-- expensive function call, and we want to avoid making too many
-- of them so as not to go over the expensive function limit.
local function checkArchives( prefix, n, start )
    local i = start
    local exists = true
    while exists do
        exists = checkPageExists( prefix .. tostring( i ) )
        if exists then
            i = i + n
        end
    end
    return i
end

-- Return the biggest archive number, using checkArchives()
-- and starting in intervals of 1000. This should get us a
-- maximum of 500,000 possible archives before we hit the
-- expensive function limit.
local function getBiggestArchiveNum( prefix, max )
    -- Return the value for max if it is specified.
    max = processNumArg( max )
    if max then
        return max
    end
    
    -- Otherwise, detect the largest archive number.
    local check1000 = checkArchives( prefix, 1000, 1 )
    if check1000 == 1 then
        return 0 -- Return 0 if no archives were found.
    end
    local check200 = checkArchives( prefix, 200, check1000 - 1000 )
    local check50 = checkArchives( prefix, 50, check200 - 200 )
    local check10 = checkArchives( prefix, 10, check50 - 50 )
    local check1 = checkArchives( prefix, 1, check10 - 10 )
    -- check1 is the first page that doesn't exist, so we want to
    -- subtract it by one to find the biggest existing archive.
    return check1 - 1
end

-- Get the archive link prefix (the title of the archive pages
-- minus the number).
local function getPrefix( root, prefix, prefixSpace )
    local ret = root or mw.title.getCurrentTitle().prefixedText
    ret = ret .. '/'
    if prefix then
        ret = ret .. prefix
        if prefixSpace == 'yes' then
            ret = ret .. ' '
        end
    else
        ret = ret .. 'Archive '
    end
    return ret
end

-- Get the number of archives to put on one line. Set to
-- math.huge if there should be no line breaks.
local function getLineNum( links, nobr )
    local linksToNum = tonumber( links )
    local lineNum
    if nobr == 'yes' or (links and not linksToNum) then
        lineNum = math.huge
    -- If links is a number, process it. Negative values and expressions
    -- such as links=8/2 produced some interesting values with the old
    -- template, but we will ignore those for simplicity.
    elseif type(linksToNum) == 'number' and linksToNum >= 0 then
        -- The old template rounded down decimals to the nearest integer.
        lineNum = math.floor( linksToNum )
        if lineNum == 0 then
            -- In the old template, values of links between 0 and 0.999
            -- suppressed line breaks.
            lineNum = math.huge
        end
    else
        lineNum = 10 -- Default to 10 links.
    end
    return lineNum
end

-- Gets the prefix to put before the archive links.
local function getLinkPrefix( prefix, space )
    -- Get the link prefix.
    local ret = ''
    if type(prefix) == 'string' then
        ret = prefix
        if space == 'yes' then
            ret = ret .. ' '
        end
    end
    return ret
end

-- Get the number to start listing archives from.
local function getStart( start )
    start = processNumArg( start )
    if start then
        return start
    else
        return 1
    end
end 

-- Generates the list of archive links. glargs.max must be either zero (for
-- no archives) or a positive integer value.
local function generateLinks( glargs )
    if type( glargs ) ~= 'table' or not glargs.max or not glargs.prefix then
        error('insufficient arguments passed to generateLinks', 2)
    end
    -- If there are no archives yet, return a message and a
    -- link to create Archive one.
    if glargs.max == 0 then
        return 'no archives yet ([[' .. glargs.prefix .. '1|create]])'
    end
    -- Return an html error if the start number is greater than the 
    -- maximum number.
    local start = glargs.start or 1
    if start > glargs.max then
        return '<span class="error">Start value "' 
            .. tostring( start ) 
            .. '" is greater than the most recent archive number "' 
            .. tostring( glargs.max ) 
            .. '".</span>'
    end
    local linkPrefix = glargs.linkPrefix or ''
    local sep = glargs.sep or ', '
    local lineNum = glargs.lineNum or 10
    local lineSep = glargs.lineSep or '<br />'

    -- Generate the archive links.
    local lineCounter = 1 -- The counter to see whether we need a line break or not.
    local ret = {} -- A table containing the strings to be returned.
    for archiveNum = start, glargs.max do
        local link = mw.ustring.format(
            '[[%s%d|%s%d]]',
            glargs.prefix, archiveNum, linkPrefix, archiveNum
        )
        table.insert( ret, link )
        -- If we don't need a new line, output a comma. We don't need
        -- a comma after the last link. 
        if lineCounter < lineNum and archiveNum < glargs.max then
            table.insert( ret, sep )
            lineCounter = lineCounter + 1
        -- Output new lines if needed. We don't need a new line after
        -- the last link.
        elseif lineCounter >= lineNum and archiveNum < glargs.max then
            table.insert( ret, lineSep )
            lineCounter = 1
        end
    end
    return table.concat( ret )
end

-- Get the archive data and pass it to generateLinks().
local function _main( args )
    local prefix = getPrefix( args.root, args.prefix, args.prefixspace )
    local max = getBiggestArchiveNum( prefix, args.max )
    local lineNum = getLineNum( args.links, args.nobr )
    local linkPrefix = getLinkPrefix( args.linkprefix, args.linkprefixspace )
    local start = getStart( args.start )
    local glargs = {
        start = start,
        max = max,
        prefix = prefix,
        linkPrefix = linkPrefix,
        sep = args.sep,
        lineNum = lineNum,
        lineSep = args.linesep
    }
    return generateLinks( glargs )
end

-- A wrapper function to make getBiggestArchiveNum() available from
-- #invoke.
local function _count( args )
    local prefix = getPrefix( args.root, args.prefix, args.prefixspace )
    local archiveMax = getBiggestArchiveNum( prefix )
    return archiveMax
end

function makeWrapper( func )
    return function( frame )
        -- If we are being called from #invoke, get the args from #invoke
        -- if they exist, or else get the arguments passed to the parent
        -- frame. Otherwise, assume the arguments are being passed directly
        -- in from another module or from the debug console.
        local origArgs
        if frame == mw.getCurrentFrame() then
            origArgs = frame:getParent().args
            for k, v in pairs( frame.args ) do
                origArgs = frame.args
                break
            end
        else
            origArgs = frame
        end
        
        -- Ignore blank values for parameters other than "links",
        -- which functions differently depending on whether it is
        -- blank or absent.
        local args = {}
        for k, v in pairs( origArgs ) do
            if k == 'links' or v ~= '' then
                args[k] = v
            end
        end
        
        return func( args )
    end
end

return {
    main = makeWrapper( _main ),
    count = makeWrapper( _count )
}