Module:User:Mr. Stradivarius/BannerConvert

local dts = require('Module:User:Anomie/deepToString').deepToString -- for debugging

local p = {}

local basicDisplayParams = {
    'small',
    'category',
    'listas',
    'class',
    'auto',
    'importance',
    'attention',
}

-- Replaces bar characters with a pseudo-random string for converting back later.
local function debar( s )
    local s = mw.ustring.gsub( s, '|', 'DEBARRED-29vuy7dsgtz87465gfbq29gr1bzj-DERRABED' )
    return s
end

-- Undoes the transformation done by debar().
local function rebar( s )
    local s = mw.ustring.gsub( s, 'DEBARRED%-29vuy7dsgtz87465gfbq29gr1bzj%-DERRABED', '|' )
    return s
end

-- Returns a table containing the numbers of the arguments that exist for the specified prefix and suffix.
local function getArgNums( t, prefix, suffix )
    if type( t ) ~= 'table' then
        return nil
    end
    prefix = type( prefix ) == 'string' and prefix or ''
    suffix = type( suffix ) == 'string' and suffix or ''
    local nums = {}
    for k, v in pairs( t ) do
        if mw.ustring.find( v, '%S' ) then
            local num = tostring( k )
            num = mw.ustring.match( num, '^' .. prefix .. '([1-9]%d*)' .. suffix .. '$' )
            if num then
                table.insert( nums , tonumber( num ) )
            end
        end
    end
    table.sort( nums )
    return nums
end

local function getAliasNames( s, t )
    if type( s ) ~= 'string' then return end
    local t = t or {}
    for tripleBraces in mw.ustring.gmatch( s, '{{%b{}}}' ) do
        tripleBraces = mw.ustring.sub( tripleBraces, 4, -4 )
        local alias, default = mw.ustring.match( tripleBraces, '^(.-)|(.*)$' )
        if not alias then
            alias = tripleBraces
        end
        table.insert( t, mw.text.trim( alias ) )
        if default then
            getAliasNames( default, t )
        end
    end    
    return t
end

-- Searches a string for the first instance of the specified template, and returns a table with the template arguments.
local function getTemplateArgs( s, templateName )
    if type( s ) ~= 'string' or type( templateName ) ~= 'string' or #templateName == 0 then
        return nil
    end
    -- Process the template name so that we can match both upper case and lower case for the first character.
    local firstLetter, lastLetters
    if #templateName == 1 then
        firstLetter, lastLetters = templateName, ''
    else
        firstLetter, lastLetters = mw.ustring.sub( templateName, 1, 1 ), mw.ustring.sub( templateName, 2, -1 )
    end
    local pattern = mw.ustring.format( '^{{%%s*[%s%s]%s%%s*[|}]', mw.ustring.lower( firstLetter ), mw.ustring.upper( firstLetter ), lastLetters )
    -- Find the template text.
    local template
    for braces in mw.ustring.gmatch( s, '{%b{}}' ) do
        if mw.ustring.match( braces, pattern ) then
            template = braces
            break
        end
    end
    if not template then
        return nil
    end
    -- Temporarily replace characters inside [[]] or {{}} with a quasi-random code. The bars that are left delineate the template's parameters.
    template = mw.ustring.sub( template, 3, -3 ) -- Strip the double braces so that debar() doesn't match the whole banner.
    template = mw.ustring.gsub( template, '{%b{}}', debar )
    template = mw.ustring.gsub( template, '%[%b[]%]', debar )
    -- Build the table of parameters.
    local params = {}
    for field in mw.ustring.gmatch( template, '|([^|]*)' ) do
        local param, value = nil, nil
        if mw.ustring.find( field, '=' ) then
            param, value = mw.ustring.match( field, '%s*([^=]-)%s*=(.*)' )
            value = value or ''
            value = mw.text.trim( value )
        else
            value = field
        end
        if param then
            paramNum = tonumber( param )
            if paramNum and paramNum >= 1 and math.floor( paramNum ) == paramNum and paramNum ~= math.huge then
                param = paramNum
            end
            param = mw.ustring.gsub( param, ' ', '_' )
            if not value then
                params[ mw.ustring.lower( param ) ] = ''
            else
                params[ mw.ustring.lower( param ) ] = value
            end
        else
            table.insert( params, value )
        end
    end
    -- Replace the quasi-random code with the bars again.
    for k, v in pairs( params ) do
        params[ k ] = rebar( v )
    end
    return params
end

local function processRow( t, prefixTable, suffixTable, num )
    num = num and tostring( num ) or ''
    local ret = {}
    for i, prefix in ipairs( prefixTable ) do
        for j, suffix in ipairs( suffixTable ) do
            if type( prefix ) == 'string' and type( suffix ) == 'string' then
                local arg = mw.ustring.format( '%s%s%s', prefix, num, suffix )
                local val = t[ arg ]
                if val and mw.ustring.find( val, '%S' ) then
                    local suffixTrimmed = mw.ustring.match( suffix, '^_+(.-)$' ) or suffix
                    local aliases = getAliasNames( val )
                    if #aliases >= 1 then
                        local aliasParam = suffixTrimmed == '' and 'aliases' or suffixTrimmed .. '_aliases'
                        ret[ aliasParam ] = aliases
                    else
                        ret[ suffixTrimmed ] = val
                    end
                end
            end
        end
    end
    return ret
end


local function processRows( t, prefixTable, suffixTable, nums )
    if type( t ) ~= 'table' then
        return nil
    end
    if type( prefixTable ) ~= 'table' or #prefixTable == 0 then
        prefixTable = { '' }
    end
    if type( suffixTable ) ~= 'table' or #suffixTable == 0 then
        suffixTable = { '' }
    end
    nums = type( nums ) == 'table' and nums
    ret = {}
    if nums then
        for i, num in ipairs( nums ) do
            if type( num ) == 'number' and num >= 1 and math.floor( num ) == num and num ~= math.huge then
                table.insert( ret, processRow( t, prefixTable, suffixTable, num ) )
            end
        end
    else
        return processRow( t, prefixTable, suffixTable, num )
    end
    return ret
end

function p.main( frame )
    local page
    if frame == mw.getCurrentFrame() then
        page = frame:getParent().args[1]
        if not page then
            page = frame.args[1]
        end
    else
        page = frame
    end
    if type( page ) ~= 'string' then return end
    local pageObject = mw.title.new( page )
    if not pageObject then return end
    local content = pageObject:getContent()
    if not content then return end
    content = mw.ustring.gsub( content, '<!%-%-.-%-%->', '' ) -- Remove html comments.
    
    local params = getTemplateArgs( content, 'WPBannerMeta' )
    local aliases = {}
    for i, param in ipairs( basicDisplayParams ) do
        local aliasNames = getAliasNames( params[ param ] )
        aliases[ param ] = aliasNames
    end
    local tfnums = getArgNums( params, 'tf_' )
    local tf = processRows( params, { 'tf_' }, { '', '_link', '_name', '_image', '_nested', '_text', '_quality', '_main_cat', '_assessment_cat' }, tfnums )
    do return dts( tf ) end -- for debugging
    
    local rows = {}
    for i, ptable in ipairs( params ) do
        params[ i ][ 2 ] = rebar( ptable[ 2 ] )
        if type( ptable[ 1 ] ) == 'number' then
            table.insert( rows, mw.ustring.format( '[%s] = "%s"', mw.ustring.lower( tostring( ptable[ 1 ]  ) ), ptable[ 2 ]  ) )
        else
            table.insert( rows, mw.ustring.format( '%s = "%s"', mw.ustring.lower( ptable[ 1 ] ), ptable[ 2 ] ) )
        end
    end
    return mw.ustring.format( 'local banner = {\n    %s\n}', table.concat( rows, ',\n    ' ) )
end

return p