Modul:Vorlage:Unsigniert

aus Wikipedia, der freien Enzyklopädie
local Unsigned = { suite  = "Unsigned",
                   serial = "2019-07-19",
                   item   = 0 }
--[=[
Deal with unsigned contributions on talk pages
]=]
local Failsafe = Unsigned



Unsigned.cnf =
    { cat       = "Wikipedia:Vorlagenfehler/Vorlage:Unsigniert",
      defaults  = { ["1"] = "nicht" },
      gsub      = { { " Mrz%.", " Mär." } },
      parMap    = { ["1"] = "User",
                    ["2"] = "Stamp",
                    ["3"] = "2",
                    ALT   = "1",
                    Datum = "DateTime" },
      service   = "Unsigniert",
      signature = "<small>{''$1 [[Hilfe:Signatur|signierter]] Beitrag'' von $SIGNATURE$2)</small>",
      sole      = "<small>(ohne [gültigen] Zeitstempel [[Hilfe:Signatur#wie|signierter]] Beitrag)</small>",
      stamp     = "dmy",
      support   = "[[Vorlage:Unsigniert]]",
      user      = { { "Benutzer", "Benutzerin" },  "BD" },
      zone      = "CES?T"
    }
Unsigned.timestamps = {
   dmy = { seek = "^[012]%d, [0123]%d. %l%l%l%. 20%d%d %($ZONE%)$",
           set  = "H:i, j. M Y (T)" } }



local function Fetch( ask, advanced )
    -- Fetch module
    -- Parameter:
    --     ask    -- string, with name
    --                       "DateTime"
    --                       "Multilingual"
    --                       "URLutil"
    -- Returns table of module
    -- error: Module not available
    local sign = ask
    local r, stem
    if sign:sub( 1, 1 ) == "/" then
        sign = Unsigned.frame:getTitle() .. sign
    else
        stem = sign
        sign = "Module:" .. stem
    end
    if Unsigned.extern then
        r = Unsigned.extern[ sign ]
    else
        Unsigned.extern = { }
    end
    if not r then
        local lucky, g = pcall( require, sign )
        if type( g ) == "table" then
            if stem  and  type( g[ stem ] ) == "function" then
                r = g[ stem ]()
            else
                r = g
            end
            Unsigned.extern[ sign ] = r
        else
            error( string.format( "Fetch(%s) %s", sign, g ) )
        end
    end
    return r
end -- Fetch()



local function Flat( a )
    -- Standardize HTML entities (e.g. before substitution)
    -- Parameter:
    --     a  -- string
    -- Returns:
    --     string
    local h = {   [34] = "quot",
                  [38] = "amp",
                  [39] = "apos",
                  [60] = "lt",
                  [62] = "gt",
                 [160] = "nbsp",
                 [173] = "shy",
                [8194] = "ensp",
                [8195] = "emsp",
                [8201] = "thinsp",
                [8204] = "zwnj",
                [8205] = "zwj",
                [8206] = "lrm",
                [8207] = "rlm" }
    local w = { 35, 42, 58, 59, 91, 92, 93, 123, 124, 125 }
    local i = 1
    local r = a
    local k, s, x
    while i do
        i = r:find( "&#", i, true )
        if i then
            x, s = r:sub( i + 2 ):match( "^(x?)(%x+);" )
            if s then
                if x == "x" then
                    k = 16
                else
                    k = 10
                end
                k = tonumber( s, k )
                s = string.format( "&#%s%s;", x, s )
                x = false
                if k >= 35  and  k <= 125 then
                    for j = 1, #w do
                        if k == w[ j ] then
                            x = true
                            break -- for j
                        end
                    end -- j = 1, #w
                elseif k < 32  or  k == 8209  or  k == 8239 then
                    x = true
                end
                if x then
                    x = string.format( "&#%d;", k )
                elseif k >= 0xFFFE then
                    x = string.format( "&#x%X;", k )
                else
                    x = h[ k ]
                    if x then
                        x = string.format( "&%s;", x )
                    else
                        x = mw.ustring.char( k )
                    end
                end
                r = r:gsub( s, x )
            end
            i = i + 1
        end
    end    -- while i
    return r
end -- Flat()



local function factory()
    -- Create signature
    -- Returns:
    --     string, wikitext
    -- error: invalid format
    local pLang  = mw.language.getContentLanguage()
    local female = function( alike, alt )
                       local g
                       if type( alt[ 1 ] ) == "string"   and
                          type( alt[ 2 ] ) == "string"   and
                          ( type( alt[ 3 ] ) == "string"  or
                              not alt[ 3 ] ) then
                           g = pLang:gender( alike, alt )
                       end
                       return g
                   end -- female()
    local URLutil = Fetch( "URLutil" )
    local s       = Unsigned.vals.User
    local sign    = Unsigned.cnf.signature or "$SIGNATURE"
    local r, show, space
    if URLutil.isIP( s ) then
        local special
        r       = { "Contributions" }
        special = Unsigned.frame:callParserFunction( "#special", r )
        space   = mw.site.namespaces[ 3 ].name
        show    = mw.message.new( "talkpagelinktext" )
                                  :inLanguage( pLang:getCode() )
                                  :plain()
        r       = string.format( "[[%s/%s|%s]] &#91;[[%s:%s|%s]]&#93;",
                                 special, s, s, space, s, show )
    elseif s:find( "[/@#<>%|%[%]%{%}:]" ) then
        local e     = mw.html.create( "code" )
                             :wikitext( mw.text.nowiki( s ) )
        local slang = pLang:getCode()
        r = mw.message.new( "nosuchusershort" )
                      :inLanguage( slang )
                      :params( tostring( e ) )
                      :plain()
        error( r, 0 )
    else
        local space3
        s = s:gsub( "_", " " )
        if type( Unsigned.cnf.user ) == "table" then
            local e = Unsigned.cnf.user[ 1 ]
            if type( e ) == "table" then
                space = female( s, e )
            elseif type( e ) == "string" then
                space = e
            end
            e = Unsigned.cnf.user[ 2 ]
            if type( e ) == "table" then
                space3 = female( s, e )
            elseif type( e ) == "string" then
                space3 = e
            end
        end
        show   = mw.message.new( "contribslink" )
                           :inLanguage( pLang:getCode() )
                           :plain()
        space  = space   or   mw.site.namespaces[ 2 ].name
        space3 = space3  or   mw.site.namespaces[ 3 ].name
        r      = string.format( "[[%s:%s|%s]] &#91;[[%s:%s|%s]]&#93;",
                                 space, s, s, space3, s, show )
    end
    r = sign:gsub( "$SIGNATURE", r )
    if r:find( "%$%d" ) then
        for i = 1, 9 do
            s    = tostring( i )
            show = Unsigned.vals[ s ] or ""
            r    = mw.ustring.gsub( r,  "$" .. s,  show )
        end -- for i
    end
    return Flat( r )
end -- factory()



local function fault( a )
    -- Format error message by class=error
    -- Parameter:
    --     a  -- string, error message
    -- Returns:
    --     string, HTML span
    local b = mw.html.create( "div" )
                     :addClass( "error" )
                     :css( "background-color", "#FFC0C0" )
                     :css( "border", "#FF0000 3px solid" )
                     :css( "margin", "1em" )
                     :css( "padding", "0.5em" )
    local c = mw.html.create( "div" )
                     :css( "font-family", "monospace" )
                     :css( "margin-top", "0.5em" )
    local p = { }
    local r, s
    if type( Unsigned.cnf ) ~= "table" then
        Unsigned.cnf = { }
    end
    if type( Unsigned.cnf.support ) == "string" then
        s = Unsigned.cnf.support
    else
        s = Unsigned.self or Unsigned.suite
    end
    r = string.format( "'''%s''' &#8211; %s",  s,  a or "????" )
    b:wikitext( r )
    Unsigned.pars = Unsigned.pars or { }
    for k, v in pairs( Unsigned.pars ) do
        table.insert( p, k )
    end -- for k, v
    table.sort( p )
    if type( Unsigned.cnf.service ) == "string" then
        s = Unsigned.cnf.service
    else
        s = Unsigned.self
    end
    r = string.format( "{{subst:%s", s )
    for i = 1, #p do
        s = p[ i ]
        r = string.format( "%s |%s=%s", r, s, Unsigned.pars[ s ] )
    end -- for i
    r = r .. "}}"
    p = { [ 1 ] = "nowiki", [ 2 ] = r }
    r = Unsigned.frame:callParserFunction( "#tag", p )
    c:wikitext( r )
    c:newline()
    b:newline()
    b:node( c )
    r = tostring( b )
    if type( Unsigned.cnf.cat ) == "string" then
        r = string.format( "%s[[category:%s]]",
                           r, Unsigned.cnf.cat )
    end
    return Flat( r )
end -- fault()



local function finish( attempt )
    -- Postprocess stamp
    -- Parameter:
    --     attempt  -- string, stamp
    -- Returns
    --     string, improved stamp
    local r = attempt
    if type( Unsigned.cnf.gsub ) == "table" then
        for k, v in pairs( Unsigned.cnf.gsub ) do
            if type( v ) == "table"  and
               type( v[ 1 ] ) == "string"  and
               type( v[ 2 ] ) == "string" then
                r = mw.ustring.gsub( Unsigned.vals.Stamp,
                                     v[ 1 ],
                                     v[ 2 ] )
            end
        end -- for k, v
    end
    return r
end -- finish()



local function format( attempt )
    -- Make stamp from arbitrary date time
    -- Parameter:
    --     attempt  -- string, date time
    -- Returns
    --     string, formatted stamp
    -- error: invalid format
    local DateTime = Fetch( "DateTime" )
    local s  = attempt
    local dt = DateTime( s )
    local r
    if type( dt ) == "table" then
        if not dt.zone then
            dt:future( - tonumber( dt:format( "Z" ) ) )
        end
        s = Unsigned.stamp or "c"
        r = dt:format( s )
    else
        local e     = mw.html.create( "code" )
                             :wikitext( mw.text.nowiki( s ) )
        local p     = mw.language.getContentLanguage()
        local slang = p:getCode()
        local stamp = mw.message.new( "blocklist-timestamp" )
                                :inLanguage( slang )
                                :plain()
        s = mw.message.new( "confirmedit-preview-invalid" )
                      :inLanguage( slang )
                      :plain()
        s = string.format( "%s %s: %s",
                           stamp, s, tostring( e ) )
        error( s, 0 )
    end
    return r
end -- format()



local function furnish( arglist )
    -- Initialize config and parameters
    -- Parameter:
    --     arglist  -- table, template parameters
    -- error: serious problem
    local cnf
    if type( Unsigned.cnf ) == "table" then
        cnf = Unsigned.cnf
    else
        local lucky
        lucky, cnf = pcall( mw.loadData,
                            Unsigned.self .. "/config" )
        if type( cnf ) == "table" then
            Unsigned.cnf = cnf
        else
            error( cnf, 0 )
        end
    end
    if type( cnf.stamp ) == "string"  and
       type( cnf.parMap ) == "table" then
        local strip = mw.ustring.char( 0x5B,
                                       0x22, 0x27, 0x200E, 0x200F,
                                       0x5D )
        local timestamp = Unsigned.timestamps[ cnf.stamp ]
        local s
        if type( timestamp ) == "table" then
            if type( timestamp.seek ) == "string"  and
               type( timestamp.set )  == "string" then
                local shift = cnf.zone or "%u%u%u%u?"
                Unsigned.seek  = timestamp.seek:gsub( "$ZONE", shift )
                Unsigned.stamp = timestamp.set
            else
                error( "INTERNAL: invalid timestamp: " .. cnf.stamp,  0 )
            end
        else
            error( "INTERNAL: missing timestamp: " .. cnf.stamp,  0 )
        end
        Unsigned.pars = { }
        Unsigned.vals = { }
        for k, v in pairs( arglist ) do
            if type( v ) == "string" then
                if type( k ) == "number" then
                    k = tostring( k )
                end
                s = cnf.parMap[ k ]
                if type( s ) == "string" then
                    v = mw.ustring.gsub( v, strip, "" )
                    v = mw.text.trim( v )
                    if v ~= "" then
                        Unsigned.vals[ s ] = v
                    end
                --    else    UNKNOWN PAR
                end
                if v ~= "" then
                    Unsigned.pars[ k ] = v
                end
            end
        end -- for k, v
        if type( cnf.defaults ) == "table" then
            for k, v in pairs( cnf.defaults ) do
                if type( Unsigned.vals[ k ] ) == "nil" then
                    Unsigned.vals[ k ] = v
                end
            end -- for k, v
        end
    else
        error( "INTERNAL: missing cnf.parMap / cnf.global", 0 )
    end
end -- furnish()



local function f( arglist )
    -- Main procedure
    -- Parameter:
    --     arglist  -- table, template parameters
    -- Returns
    --     string, formatted signature
    -- error: serious problem
    local r
    furnish( arglist )
    if Unsigned.vals.DateTime then
        Unsigned.vals.Stamp = format( Unsigned.vals.DateTime )
    elseif Unsigned.vals.Stamp then
        if not mw.ustring.match( Unsigned.vals.Stamp,
                                 Unsigned.seek ) then
            Unsigned.vals.Stamp = format( Unsigned.vals.Stamp )
        end
    end
    if Unsigned.vals.User then
        r = factory()
    elseif Unsigned.vals.Stamp and Unsigned.cnf.sole then
        r = Unsigned.cnf.sole
    else
        r = ""
    end
    if Unsigned.vals.Stamp then
        r = string.format( "%s %s",
                           r, finish( Unsigned.vals.Stamp ) )
    end
    if r == "" then
        local p     = mw.language.getContentLanguage()
        local slang = p:getCode()
        local r     = mw.message.new( "content-json-empty-object" )
                                :inLanguage( slang )
                                :plain()
        error( r, 0 )
    end
    return r
end -- f()



Unsigned.main = function ( arglist, frame )
    -- Main interface
    local lucky, r
    Unsigned.frame = frame or mw.getCurrentFrame()
    lucky, r = pcall( f, arglist )
    if not lucky then
        r = fault( r )
    end
    return r
end -- Unsigned.main()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version or "wikidata" or "~"
    --                 or false
    -- Postcondition:
    --     Returns  string  -- with queried version, also if problem
    --              false   -- if appropriate
    local last  = ( atleast == "~" )
    local since = atleast
    local r
    if last  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local vsn = entity:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string"  and
                   vsn.value ~= "" then
                    if last  and  vsn.value == Failsafe.serial then
                        r = false
                    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.failsafe()



-- Export
local p = { }

p.f = function ( frame )
    -- Template call
    Unsigned.self = frame:getTitle()
    return Unsigned.main( frame:getParent().args, frame )
end -- p.f

p.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.failsafe( since )  or  ""
end -- p.failsafe

p.Unsigned = function ()
    -- Module interface
    return Unsigned
end

return p