Modul:TemplateData/global

aus Wikipedia, der freien Enzyklopädie
local Export = { suite    = "TemplateDataGlobal",
                 serial   = "2020-08-01",
                 item     = 51435481,
                 subpages = "TemplateData",
                 suffix   = "tab" }
--[=[
Retrieve TemplateData from Commons:Data (or other global source)
require()
Inspired by [[User:Yurik]].
]=]
local Failsafe = Export



local failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or "wikidata" or "~" or "@" or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2020-08-01
    local last  = ( atleast == "~" )
    local link  = ( atleast == "@" )
    local since = atleast
    local r
    if last  or  link  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            local entity = mw.wikibase.getEntity( suited )
            if type( entity ) == "table" then
                local seek = Failsafe.serialProperty or "P348"
                local vsn  = entity:formatPropertyValues( seek )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string"  and
                   vsn.value ~= "" then
                    if last  and  vsn.value == Failsafe.serial then
                        r = false
                    elseif link then
                        if mw.title.getCurrentTitle().prefixedText  ==
                           mw.wikibase.getSitelink( suited ) then
                            r = false
                        else
                            r = suited
                        end
                    else
                        r = vsn.value
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- failsafe()



local function fair( already, adapt, append )
    -- Merge local definitions into global base
    -- Parameter:
    --     already  -- global item
    --     adapt    -- local override item
    --     append   -- append to sequence table
    -- Returns merged item
    local r
    if already and adapt then
        if type( already ) == "table"  and
           type( adapt ) == "table" then
            r = already
            if append then
                for i = 1, #adapt do
                    table.insert( r, adapt[ i ] )
                end    -- for i
            else
                for k, v in pairs( adapt ) do
                    r[ k ] = v
                end    -- for k, v
            end
        else
            r = adapt
        end
    else
        r = already or adapt
    end
    return r
end -- fair()


local function feed( apply )
    -- Retrieve override from JSON code
    -- Parameter:
    --     apply  --  string, with JSON
    -- Returns string, with error message, or table
    local lucky, r = pcall( mw.text.jsonDecode, apply )
    if not lucky then
        r = "fatal JSON error in LOCAL override"
    end
    return r
end -- feed()



local function find( access )
    -- Fetch data from page
    -- Parameter:
    --    access  -- string, with core page name
    -- Returns
    --    1. string, with prefixed page name
    --    2. table with JSON data, or error message
    local storage = access
    local lucky, r
    if Export.suffix  and  not storage:find( ".", 2, true ) then
        local k = -1 - #Export.suffix
        if storage:sub( k ) ~= "." .. Export.suffix then
            storage = string.format( "%s.%s", storage, Export.suffix )
        end
    end
    if Export.subpages  and  not storage:find( "/", 1, true ) then
        storage = string.format( "%s/%s", Export.subpages, storage )
    end
    lucky, r = pcall( mw.ext.data.get, storage, "_" )
    storage = "Data:" .. storage
    if mw.site.siteName ~= "Wikimedia Commons" then
        storage = "commons:" .. storage
    end
    if type( r ) ~= "table"  and  type( r ) ~= "string" then
        r = "INVALID"
    end
    return storage, r
end -- find()



local function flat( apply )
    -- Convert tabular data into TemplateData
    -- Parameter:
    --     apply -- table, with tabular data
    -- Returns string, with error message, or table, with TemplateData
    local r, scream
    local function failed( at, alert )
              if scream then
                  scream = string.format( "%s * #%d: %s",
                                          scream, at, alert )
              else
                  scream = add
              end
          end -- failed()
    if type( apply.schema ) == "table"  and
       type( apply.schema.fields ) == "table"  and
       type( apply.data ) == "table" then
        local order = { }
        local entry, got, params, parOrder, s, sign, td, v
        for k, v in pairs( apply.schema.fields ) do
            if type( v ) == "table" then
                table.insert( order, v.name )
            end
        end    -- for k, v
        for i = 1, #apply.data do
            entry = apply.data[ i ]
            if type( entry ) == "table" then
                got  = { }
                sign = false
                for j = 1, #entry do
                    s = order[ j ]
                    v = entry[ j ]
                    if type( v ) == "string" then
                        v = mw.text.trim( v )
                        if v == "" then
                            v = false
                        end
                    end
                    if v then
                        if s == "name" then
                            sign = v
                        elseif s == "aliases" then
                            if type( v ) == "string" then
                                got.aliases = mw.text.split( v,
                                                             "%s*|%s*" )
                            else
                                failed( i, "aliases not a string" )
                            end
                        else
                            got[ s ] = v
                        end
                    end
                end    -- for j
                if sign == "|" then
                    if td then
                        failed( i, "root repeated" )
                    else
                        td = { description = got.description }
                        if type( got.type ) == "string" then
                            td.format = got.type:gsub( "N", "\n" )
                        end
                    end
                elseif sign then
                    if params then
                        if params[ sign ] then
                            failed( i, "name repeated: " .. sign )
                        end
                    else
                        params   = { }
                        parOrder = { }
                    end
                    params[ sign ] = got
                    table.insert( parOrder, sign )
                else
                    failed( i, "missing name" )
                end
            else
                failed( i, "invalid component" )
            end
        end    -- for i
        r = td or { }
        r.params     = params
        r.paramOrder = parOrder
    else
        r = "bad tabular structure"
    end
    return scream or r or "EMPTY"
end -- flat()



local function flush( assembly, avoid )
    -- Remove element from sequence table
    -- Parameter:
    --     assembly  -- sequence table
    --     avoid     -- element
    for i = 1, #assembly do
        if assembly[ i ] == avoid then
            table.remove( assembly, i )
            break    -- for i
        end
    end    -- for i
end -- flush()



local function fold( already, adapt )
    -- Merge local parameter definitions into global base
    -- Parameter:
    --     already  -- table, with global data
    --     adapt    -- sequence table, with local params overrides
    -- Returns string, with error message, or table, with TemplateData
    local order  = { }
    local params = { }
    local r = already
    local entry, override, s
    r.params     = r.params or { }
    r.paramOrder = r.paramOrder or { }
    for i = 1, #adapt do
        override = adapt[ i ]
        if type( override ) ~= "table" then
            r = string.format( "No object at LOCAL params[%d]", i )
            break    -- for i
        elseif type( override.global ) == "string" then
            s     = override.global
            entry = r.params[ s ]
            if type( entry ) == "table" then
                flush( r.paramOrder, s )
                if type( override["local"] ) == "string" then
                    s = override["local"]
                    override["local"] = nil
                elseif override["local"] == false then
                    entry = nil
                end
                if entry then
                    override.global = nil
                    for k, v in pairs( override ) do
                        entry[ k ] = fair( entry[ k ], override[ k ],
                                           ( k == "aliases" ) )
                    end    -- for k, v
                    table.insert( order, s )
                end
                params[ s ] = entry
            else
                r = string.format( "No GLOBAL params %s for LOCAL [%d]",
                                   s, i )
                break    -- for i
            end
        elseif type( override["local"] ) == "string" then
            s = override["local"]
            override["local"] = nil
            params[ s ] = override
            table.insert( order, s )
        else
            r = string.format( "No name for LOCAL params[%d]", i )
            break    -- for i
        end
    end    -- for i
    if type( r ) == "table" then
        for i = 1, #r.paramOrder do
            s = r.paramOrder[ i ]
            params[ s ] = r.params[ s ]
            table.insert( order, s )
        end    -- for i
        r.params     = params
        r.paramOrder = order
    end
    return r
end -- fold()



local function fork( already, adapt )
    -- Merge local definitions into global base
    -- Parameter:
    --     already  -- table, with global data
    --     adapt    -- table, with local overrides
    -- Returns string, with error message, or table, with TemplateData
    local root = { "description", "format", "maps", "sets", "style" }
    local r = already
    for k, v in pairs( root ) do
        if adapt[ v ] then
            r[ v ] = fair( r[ v ], adapt[ v ] )
        end
    end    -- for k, v
    if type( adapt.params ) == "table" then
        r = fold( r, adapt.params )
    end
    return r
end -- fork()



local function furnish( apply, at, adapt )
    -- Convert external data into TemplateData
    -- Parameter:
    --     apply  -- table, with external data
    --     at     -- string, with page name
    --     adapt  -- JSON string or table or not, with local overrides
    -- Returns string, with error message, or table, with TemplateData
    local r
    if at:sub( -4 ) == ".tab" then
        r = flat( apply )
    else
        r = "Unknown page format: " .. at
    end
    if adapt  and  type( r ) == "table" then
        local override = adapt
        if type( adapt ) == "string" then
            override = feed( adapt )
            if type( override ) == "string" then
                r = override
            end
        end
        if type( override ) == "table" then
            r = fork( r, override )
        end
    end
    return r
end -- furnish()



Export.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return failsafe( since )  or  ""
end -- Export.failsafe()



Export.fetch = function ( access, adapt )
    -- Fetch data from site
    -- Parameter:
    --     access  -- string, with page specification
    --     adapt   -- JSON string or table or not, with local overrides
    -- Returns
    --    1. string, with error message or prefixed page name
    --    2. table with TemplateData, or not
    local storage, t = find( access )
    local s
    if type( t ) == "table" then
        t = furnish( t, storage, adapt )
        if type( t ) ~= "table" then
            s = t
        end
    else
        s = t
    end
    if type( t ) ~= "table" then
        storage = string.format( "[[%s]]", storage )
        if s then
            storage = string.format( "%s * %s", storage, s )
        end
        t = false
    end
    return storage, t
end -- Export.fetch()



return Export