Die Dokumentation für dieses Modul kann unter Modul:PageUtil/Doku erstellt werden

local PageUtil = { suite  = "PageUtil",
                   serial = "2024-08-20",
                   item   = 89064539 }
--[=[
PageUtil
]=]


if mw.site.server:find( ".beta.wmflabs.org", 4, true ) then
    require( "strict" )
end


local Failsafe = PageUtil



PageUtil.maxPages = 200



local function fault( alert, frame )
    -- Format message with class="error"
    --     alert  -- string, with message
    --     frame  -- object, if known
    -- Returns message with markup
    local scream = alert
    if frame then
        scream = string.format( "%s * %s", frame:getTitle(), scream )
    end
    return tostring( mw.html.create( "span" )
                            :addClass( "error" )
                            :wikitext( scream ) )
end -- fault()



local function find( area, ask )
    -- Check for local page existence without traces
    --     area    -- number, with namespace number >= 0
    --     ask     -- string, with title
    -- Returns boolean
    local r = false
    if mw.title.makeTitle( area, ask ).protectionLevels.edit then
        r = true
    end
    return r
end -- find()



local function flat( adjust, assembly )
    -- Replace links to pages by inner links
    --     adjust    -- string, with text
    --     assembly  -- table, with page infos
    -- Returns adjusted string
    local r = adjust
    local seek, shift, source, subst
    for k, v in pairs( assembly ) do
        source = v[ 1 ]
        shift  = v[ 2 ]
        source = ":?" .. source:gsub( " ", "[_ ]+" )
                               :gsub( "[%.%(%)%*%?%+%-]", "%1" )
                      .. "%s*"
        seek   = "%[%[%s*" .. source .. "(#[^%]]*%]%])"
        subst  = "[[%1"
        r = r:gsub( seek, subst )
        seek  = "%[%[%s*" .. source .. "(%|[^%]]*%]%])"
        subst = "[[#" .. shift .. "%1"
        r = r:gsub( seek, subst )
        seek  = "%[%[%s*(" .. source .. "%]%])"
        subst = "[[#" .. shift .. "|%1"
        r = r:gsub( seek, subst )
    end -- for k, v
    return r
end -- flat()



local function fraction( access, frame )
    -- Retrieve text from section
    --     access  -- string, with request
    --     frame   -- object
    -- Returns content, or false
    -- Uses:
    --     mw.title.new() .exists
    local r
    local seek = "^(#lstx?):%s*%[%[([^%[|%]\n]+)%]%]%s*(%S.*)%s*$"
    local scope, source, section = access:match( seek )
    if source then
        local page = mw.title.new( source )
        source = page.prefixedText
        if page.exists then
            section = mw.text.trim( section )
            if section ~= "" then
                r = frame:callParserFunction{ name = scope,
                                              args = { source,
                                                       section } }
            end
        else
            r = tostring( mw.html.create( "div" )
                                 :addClass( "error" )
                                 :wikitext( source ) )
        end
    end
    return r
end -- fraction()



local function full( access, frame, alias, assembly )
    -- Retrieve text from page
    --     access    -- string, with page name
    --     frame     -- object
    --     alias     -- number, unique
    --     assembly  -- table, with page infos
    -- Returns string with content, or nil
    -- Uses:
    --     mw.title.new() .exists
    local page = mw.title.new( access )
    local r
    if page then
        if page.exists then
            local source  = page.prefixedText
            local segment = string.format( "PageUtilMerge-%d", alias )
            local seed
            if page.namespace == 0 then
                seed = ":" .. source
            else
                seed = source
            end
            r = string.format( "%s\n%s",
                               tostring( mw.html.create( "span" )
                                                :attr( "id", segment ) ),
                               frame:expandTemplate( { title = seed } ) )
            table.insert( assembly,  { source, segment } )
        else
            r = tostring( mw.html.create( "div" )
                                 :addClass( "error" )
                                 :wikitext( page.prefixedText ) )
        end
    else
        r = string.format( "%s '%s'", "Unknown page", access )
        r = tostring( mw.html.create( "div" )
                             :addClass( "error" )
                             :wikitext( r ) )
    end
    return r
end -- full()



PageUtil.exists = function ( area, ask, apply, alert )
    -- Check for existence
    --     area    -- number or string, with namespace specification
    --                -- number >= 0 of local namespace
    --                -- "media"  -- files including commons
    --                -- "msg"    -- system messages
    --                -- "data"   -- commons:Data:*.tab
    --     ask     -- table, with list of titles
    --     apply   -- table or not, with 0, 1, 2 elements
    --                -- [true]: category list on existence
    --                -- [false]: category list on non-existence
    --                -- category titles, multiple separated by \n
    --     alert   -- table or not, with 0, 1, 2 elements
    --                -- [true]: return value on existence
    --                -- [false]: return value on non-existence
    --                -- if string then followed by apply categories
    -- Returns string, with content, or other element of alert
    local got, leg, r
    if type( area ) == "number"  and
       area >= 0  and
       math.floor( area ) == area then
        for k, v in pairs( ask ) do
            if type( v ) == "string" then
                got = got  or  { }
                leg = find( area, v )
                got[ leg ] = got[ leg ]  or  { }
                table.insert( got[ leg ], v )
            end
        end -- for k, v
    elseif area == "media" then
        for k, v in pairs( ask ) do
            if type( v ) == "string" then
                got = got  or  { }
                leg = find( 6, v )
                if not leg then
                    leg = mw.title.makeTitle( 6, v ).exists
                end
                got[ leg ] = got[ leg ]  or  { }
                table.insert( got[ leg ], v )
            end
        end -- for k, v
    elseif area == "msg" then
        for k, v in pairs( ask ) do
            if type( v ) == "string" then
                got = got  or  { }
                leg = mw.message.new( v ):exists()
                got[ leg ] = got[ leg ]  or  { }
                table.insert( got[ leg ], v )
            end
        end -- for k, v
    elseif area == "data" then
        local suffix = "tab"
        local data, j, lucky
        for k, v in pairs( ask ) do
            if type( v ) == "string" then
                got = got  or  { }
                if not v:find( ".", 2, true ) then
                    j = -1 - #suffix
                    if v:sub( j ) ~= "." .. suffix then
                        v = string.format( "%s.%s", v, suffix )
                    end
                end
                lucky, data = pcall( mw.ext.data.get, v )
                if lucky then
                    leg = true
                else
                    leg = false
                end
                got[ leg ] = got[ leg ]  or  { }
                table.insert( got[ leg ], v )
            end
        end -- for k, v
    else
        r = tostring( mw.html.create( "span" )
                             :addClass( "error" )
                             :wikitext( "exists@PageUtil space error" ) )
    end
    if got then
        local b = { false, true }
        local bg, ea, sa, sr
        for bk, bv in pairs( b ) do
            bg = got[ bv ]
            if bg then
                sr = type( r )
                ea = alert[ bv ]
                if ea then
                    sa = type( ea )
                    if sr == "string"  and
                       sa == "string" then
                        r = r .. ea
                    elseif sr == "nil" then
                        r  = ea
                        sr = sa
                    end
                    if sr == "string"  and
                       r:find( "@@@", 1, true ) then
                       local space
                       if type( area ) == "number" then
                           space = area
                       elseif area == "media" then
                           space = 6
                       elseif area == "msg" then
                           space = 8
                       elseif area == "data" then
                           space = "commons:Data"
                       end
                       if type( space ) == "number" then
                           sa = mw.site.namespaces[ space ].name
                           if space == 6  or  space == 14 then
                               space = ":" .. sa
                           else
                               space = sa
                           end
                       end
                       sa = ""
                       for k, v in pairs( bg ) do
                           sa = string.format( "%s [[%s:%s]]",
                                               sa, space, v )
                       end -- for k, v
                       r = r:gsub( "@@@", sa )
                    end
                end
                ea = apply[ bv ]
                if ea then
                    if sr == "nil" then
                        r  = ""
                        sr = "string"
                    end
                    if sr == "string" then
                        sa = type( ea )
                        if sa == "string" then
                            r = string.format( "%s[[Category:%s]]",
                                               r, sa )
                        elseif sa == "table" then
                            for k, v in pairs( ea ) do
                                r = string.format( "%s[[Category:%s]]",
                                                   r, v )
                            end -- for k, v
                        end
                    end
                end
            end
        end -- for bk, bv
    end
    return r
end -- PageUtil.exists()



PageUtil.getProtection = function ( access, action )
    -- Retrieve protection
    --     access  -- string or title or nil, with page, default: current
    --     action  -- string or nil, with action, default: edit
    -- Returns number: One of: 0, 0.5, 0.75, 1
    local t = type( access )
    local r = 0
    local p
    if t == "string" then
        t = mw.title.new( access )
    elseif t == "table" then
        t = access
    else
        t = mw.title.getCurrentTitle()
    end
    p = t.protectionLevels
    if type( p ) == "table" then
        local s
        if type( action ) == "string" then
            s = mw.text.trim( action )
            if s == "" then
                s = false
            end
        end
        p = p[ s or "edit" ]
        if type( p ) == "table" then
            for k, v in pairs( p ) do
                if v == "autoconfirmed" then
                    r = 0.5
                elseif v == "editeditorprotected" then
                    r = 0.75
                elseif v == "sysop" then
                    r = 1
                end
            end -- for k, v
        end
    end
    return r
end -- PageUtil.getProtection()



PageUtil.merge = function ( args, frame )
    -- Retrieve text
    --     args   -- table, with request
    --     frame  -- object, if available
    -- Returns string, with content
    local max = 0
    local r   = ""
    for k, v in pairs( args ) do
        if type( k ) == "number"  and
           k > max then
            max = k
        end
    end -- for k, v
    if max > 0 then
        local n     = 0
        local pages = {  { mw.title.getCurrentTitle().prefixedText,
                           "" }  }
        local mode, s, section, swallow
        if not frame then
            frame = mw.getCurrentFrame()
        end
        for i = 1, max do
            s = args[ i ]
            if s then
                swallow = s:match( "^%s*(#lstx?:[^\n]*%S)%s*$" )
                if swallow then
                    s = fraction( swallow, frame )
                    n = n + 1
                else
                    swallow = s:match( "^%s*%[%[([^%[|%]\n]+)%]%]%s*$" )
                    if swallow then
                        s = full( swallow, frame, i, pages )
                        n = n + 1
                    end
                end
                if s then
                    r = r .. mw.text.trim( s )
                end
                if n > PageUtil.maxPages then
                    s = string.format( "'''Too many pages (max. %d)'''",
                                       PageUtil.maxPages )
                    r = string.format( "%s\n\n%s",
                                       r,
                                       fault( s, frame ) )
                    break -- for i
                end
            end
        end -- for i
        r = flat( r, pages )
    end
    return r
end -- .merge()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or wikidata|item|~|@ or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2024-03-01
    local since  = atleast
    local last   = ( since == "~" )
    local linked = ( since == "@" )
    local link   = ( since == "item" )
    local r
    if last  or  link  or  linked  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 )
            if link then
                r = suited
            else
                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 linked 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
        elseif link then
            r = false
        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.exists = function ( frame )
    local pars = frame:getParent().args
    local ns, r
    if not pars.ns then
    elseif pars.ns:find( "^%d+$" ) then
        ns = tonumber( pars.ns )
    elseif pars.ns:find( "^%a+$" ) then
        ns = pars.ns:lower()
    end
    if ns then
        local coll, t
        for k, v in pairs( pars ) do
            t = type( k )
            if t == "string" then
                if t:match( "^%d+$" ) then
                    k = tonumber( k )
                else
                    k = false
                end
            end
            if k then
                v = mw.text.trim( v )
                if v ~= "" then
                    coll = coll or { }
                    table.insert( coll, v )
                end
            end
        end -- for k, v
        if coll then
            local loss = ( pars.loss == "1" )
            local cat, err
            if pars.cat then
                t = mw.text.split( pars.cat, "%s*\n%s*" )
                if #t > 0 then
                    cat = { [ loss ] = t }
                end
            end
            if pars.err  and  pars.err ~= "" then
               err = { [ loss ] = pars.err }
            end
            r = PageUtil.exists( ns, coll, cat, err )
        end
    end
    return r or ""
end -- p.exists

p.getCategories = function ( frame )
    local r = ""
    local s = frame.args[ 1 ]
    local c, t
    if s then
        s = mw.text.trim( s )
        if s == "" then
            s = false
        end
    end
    if s then
        t = mw.title.new( s )
    else
        t = mw.title.getCurrentTitle()
    end
    c = t.categories
    if c then
        local n = #c
        if n > 0 then
            for i = 1, n do
                if r ~= "" then
                    r = r .. "|"
                end
                r = r .. c[ i ]
            end -- for i
        end
    end
    return r
end -- p.getCategories

p.getProtection = function ( frame )
    local n = PageUtil.getProtection( frame.args[ 1 ], frame.args[ 2 ] )
    local t = { [ 0 ]    = "",
                [ 0.5 ]  = mw.ustring.char( 189 ),
                [ 0.75 ] = mw.ustring.char( 190 ),
                [ 1 ]    = "1" }
    return t[ n ]
end -- p.getProtection

p.isRedirect = function ()
    return mw.title.getCurrentTitle().isRedirect and "1"  or  ""
end -- p.isRedirect

p.merge = function ( frame )
    local lucky, r = pcall( PageUtil.merge, frame.args, frame )
    if not lucky then
        r = fault( r, frame )
    end
    return r
end -- p.merge

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()

function p.PageUtil()
    return PageUtil
end

setmetatable( p,  { __call = function ( func, ... )
                                 setmetatable( p, nil )
                                 return Failsafe
                             end } )

return p