Nicht angemeldeter Benutzer - Bearbeiten von Seiten ist nur als angemeldeter Benutzer möglich.

Modul:PageTree: Unterschied zwischen den Versionen

Aus imedwiki
Zur Navigation springen Zur Suche springen
(updates)
(hotfix)
Zeile 620: Zeile 620:
 
     local sup    = Current.self
 
     local sup    = Current.self
 
     local r
 
     local r
     if not sup:find( "/", 1, true ) then
+
     if type( sup ) == "string" and not sup:find( "/", 1, true ) then
 
         flow( sup )
 
         flow( sup )
 
         repeat
 
         repeat

Version vom 14. September 2014, 18:51 Uhr

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

--[=[ 2014-09-14

Module:pageTree
]=]



-- local globals
local Current = { maxSub = 10 }
local Sort    = false
local Strings = { "segment", "self", "stamped", "subpager", "suppress" }
local Toggles = { "lazy", "level", "lineup", "light", "linked", "list" }



local function face( about )
    -- Ensure presence of entry title
    --     about  -- table, with entry
    --               .show   -- link title
    --               .seed   -- page name
    if not about.show then
       about.show = about.seed:match( "/([^/]+)$" )
       if not about.show then
           about.show = about.seed:match( "^[^:]+:(.+)$" )
           if not about.show then
               about.show = about.seed
           end
       end
    end
end -- face()



local function facility( access )
    -- Load data table
    --     access  -- string, with path of module
    --                        maybe relative, if starting with "/"
    local s = access
    local lucky, r
    if s:byte( 1, 1 ) == 47 then    -- "/"
        if Current.suite then
            s = Current.suite .. s
        end
    end
    lucky, r = pcall( mw.loadData, s )
    if type( r ) ~= "table" then
        r = string.format( "'%s' invalid",  s )
    end
    return r
end -- facility()



local function fade( ask )
    -- Check whether page is to be hidden
    --     ask  -- string, with page name
    -- Returns true if to be hidden
    local r = false
    for k, v in pairs( Current.hide ) do
        if ask:match( v ) then
            r = true
            break -- for k, v
        end
    end -- for k, v
    return r
end -- fade()



local function failsafe( apply )
    -- Clone read-only table
    --     apply  -- table, with basic data elements, read-only
    -- Returns message with markup
    local r = { }
    for k, v in pairs( apply ) do
        r[ k ] = v
    end -- for k, v
    return r
end -- failsafe()



local function failures()
    -- Check all pages
    local redirect = {}
    local unknown  = {}
    local r, s, title
    local n = 0
    for k, v in pairs( Current.pages ) do
        n = n + 1
        s = v.seed
        if type( s ) == "string" then
            title = mw.title.new( s )
            if not title then
                table.insert( unknown, s )
            elseif title.exists then
                if v.shift then
                    if not title.isRedirect then
                        table.insert( redirect,
                                      "(-)" .. s )
                    end
                elseif Current.linked and
                       title.isRedirect then
                    table.insert( redirect,
                                  "(+)" .. s )
                end
            else
                table.insert( unknown, s )
            end
        end
    end -- for k, v
    r = string.format( "n=%d", n )
    n = table.maxn( unknown )
    if n > 0 then
        s = "*** unknown:"
        for i = 1, n do
            r = string.format( "%s %s %s", r, s, unknown[ i ] )
            s = "|"
        end -- for i
    else
        n = table.maxn( redirect )
        if n > 0 then
            s = "*** unexpected redirect:"
            for i = 1, n do
                r = string.format( "%s %s %s", r, s, redirect[ i ] )
                s = "|"
            end -- for i
        end
    end
    return r
end -- failures()



local function fair( adopt )
    -- Expand relative page name, if necessary
    --     adopt  -- string, with page name
    -- Returns absolute page name, or false
    local r
    if adopt:byte( 1, 1 ) == 47 then    -- "/"
        r = Current.start .. adopt:sub( 2 )
    else
        r = adopt
    end
    r = mw.text.trim( r )
    if r == "" then
        r = false
    end
    return r
end -- fair()



local function father( ancestor )
    -- Find parent page
    --     ancestor  -- string, with page name
    -- Returns page name of parent, or Current.series
    local r = ancestor:match( "^(.+)/[^/]+$" )
    if not r then
        r = ancestor:match( "^([^:]+:).+$" )
        if not r then
            r = Current.series
        end
    end
    return r
end -- father()



local function fault( alert )
    -- Format message with class="error"
    --     alert  -- string, with message
    -- Returns message with markup
    return string.format( "<span class=\"error\">%s</span>", alert )
end -- fault()



local function features( apply, access )
    -- Fill Current.pages with elements
    --     apply   -- table, with definitions, read-only
    --     access  -- string, with relative path of module
    -- Returns error message, if failed, or false, if fine
    local r, e, s
    local bad = { }
    local tmp = { }
    for k, v in pairs( apply ) do
        s = type( k )
        e = false
        if s == "number" then
            s = type( v )
            if s == "string" then
                s = v
                e = { }
            elseif s == "table" then
                if type( v.seed ) == "string" then
                    s = v.seed
                    e = failsafe( v )
                end
            end
        elseif s == "string" then
            if type( v ) == "table" then
                s = k
                e = failsafe( v )
            end
        elseif k == true then    -- root
            if Current.pages[ true ] then
                bad[ "true" ] = "duplicated"
            elseif type( v ) == "table" then
                if type( v.seed ) == "string" then
                    Current.pages[ true ]          = failsafe( v )
                    Current.pages[ true ].children = { }
                else
                    bad[ "true" ] = "seed missing"
                end
            else
                bad[ "true" ] = "invalid"
            end
        end
        if e then
            s = fair( s )
            if tmp[ s ] then
                bad[ s ] = "duplicated"
            else
                tmp[ s ] = true
            end
            if s then
                if not Current.pages[ s ] then
                    e.seed = s
                    if e.super then
                        if type( e.super ) == "string" then
                            e.super = fair( e.super )
                        end
                    else
                        e.super = father( s )
                    end
                    e.children = { }
                    Current.pages[ s ] = e
                end
            end
        end
    end -- for k, v
    e = 0
    r = string.format( " in '%s'", access )
    for k, v in pairs( bad ) do
        e = e + 1
        r = string.format( "%s * [%s]: %s ", r, k, v )
    end -- for k, v
    if e == 0 then
        r = false
    elseif e == 1 then
        r = "Error" .. r
    else
        r = "Errors" .. r
    end
    return r
end -- features()



local function feed( access )
    -- Fill Current with data, if not yet set
    --     access  -- string, with relative path of module
    -- Returns error message, if failed, or false, if fine
    local r = facility( access )
    if type( r ) == "table" then
        local s
        if type( r.maxSub ) == "number" then
            Current.maxSub = r.maxSub
        end
        if type( r.stamp ) == "string" then
            if Current.stamp then
                if Current.stamp < r.stamp then
                    Current.stamp = r.stamp
                end
            else
                Current.stamp = r.stamp
            end
        end
        if type( r.start ) == "string" then
            s = mw.text.trim( r.start )
            if s ~= "" then
                Current.start = s
            end
        end
        if not Current.pages then
            Current.pages = { }
        end
        if type( r.pages ) == "table" then
            if not Current.pages then
                Current.pages = { }
            end
            s = features( r.pages, access )
            if s then
                r = s
            end
        end
        if type( r ) == "table" then
            if type( r.sub ) == "string" then
                r = feed( string.format( "%s/%s", access, r.sub ) )
            else
                r = false
            end
        end
    end
    return r
end -- feed()



local function field( about, absolute )
    -- Format entry as link
    --     about     -- table, with entry
    --                  .show   -- link title
    --                  .seed   -- page name
    --                  .shift  -- redirect target
    --     absolute  -- true, if real page name to be shown
    -- Returns string
    local r
    if absolute then
        r = string.format( "[[%s]]", about.seed )
    else
        face( about )
        if about.show == about.seed then
            r = string.format( "[[%s]]", about.seed )
        else
            r = string.format( "[[%s|%s]]", about.seed, about.show )
        end
    end
    if type( about.suffix ) == "string" then
        r = string.format( "%s %s", r, about.suffix )
    end
    if Current.linked and type( about.shift ) == "string" then
        r = string.format( "%s <small>&#8594;[[%s]]</small>",
                           r, fair( about.shift ) )
    end
    return r
end -- field()



local function filter( adjust )
    -- Create sort key (Latin ASCII upcased)
    --     adjust  -- string, to be standardized
    -- Returns string with key
    if not Sort then
        r, Sort = pcall( require, "Module:Sort" )
        if type( Sort ) == "table" then
            Sort = Sort.Sort()
        else
            error( "Module:Sort not ready" )
        end
    end
    return string.upper( Sort.lex( adjust, "latin", false ) )
end -- filter()



local function first( a1, a2, abs )
    -- Compare a1 with a2 in lexicographical order
    --     a1   -- table, with page entry
    --     a2   -- table, with page entry
    --     abs  -- true, if .show to be used rather than .seed
    -- Returns true if a1 < a2
    if not a1.sort then
        if abs then
            face( a1 )
            a1.sort = filter( a1.show )
        else
            a1.sort = filter( a1.seed )
        end
    end
    if not a2.sort then
        if abs then
            face( a2 )
            a2.sort = filter( a2.show )
        else
            a2.sort = filter( a2.seed )
        end
    end
    return ( a1.sort < a2.sort )
end -- first()



local function firstly( a1, a2 )
    -- Compare a1 with a2, considering .index
    --     a1  -- string, with page name
    --     a2  -- string, with page name
    -- Returns true if a1 < a2
    local e1 = Current.pages[ a1 ]
    local e2 = Current.pages[ a2 ]
    local r
    if e1.index then
        if e2.index then
            r = ( e1.index < e2.index )
        else
            r = true
        end
    elseif e2.index then
        r = false
    else
        r = first( e1, e2, true )
    end
    return r
end -- firstly()



local function flag( ahead )
    -- Returns string with leading list syntax, either "#" or "*" or ":"
    --     ahead  -- string, with syntax in case of .lazy
    local r
    if Current.lazy then
        r = ":"
    else
        r = ahead
    end
    return r
end -- flag()



local function flow( acquire )
    -- Collect the .super in path
    --     acquire  -- string, with page name
    if type( acquire ) == "string" then
        local e = Current.pages[ acquire ]
        local s = false
        if e then
            s = e.super
        end
        if not s then
            s = acquire:match( "^(.+)/[^/]+$" )
            if not s then
                s = acquire:match( "^([^:]+:)" )
            end
            if s then
                if not e then
                    e                        = { children = { },
                                                 seed     = acquire }
                    Current.pages[ acquire ] = e
                end
                e.super = s
            elseif e then
                e.super = true
            end
        end
        if type( s ) == "string"  and  s~= acquire then
            flow( s )
        end
    end
end -- flow()



local function fluent()
    -- Collect all .children; add .super where missing
    local e
    local let = true
    for k, v in pairs( Current.pages ) do
        if not v.super then
            flow( k )
        elseif not Current.pages[ v.super ] then
            flow( v.super )
        end
    end -- for k, v
    for k, v in pairs( Current.pages ) do
        if Current.level then
            let = ( not v.seed:find( "/" ) )
        end
        if let and v.super then
            e = Current.pages[ v.super ]
            if e then
                table.insert( e.children, k )
            end
        end
    end -- for k, v
end -- fluent()



local function follow( ahead, amount, above, all )
    -- Render subtree as list of entries
    --     ahead   -- string, with leading list syntax, either "#" or "*"
    --     amount  -- number, of leading elements
    --     above   -- table, with top element (not shown)
    --                .children -- will be shown
    --     all     -- true if all grandchildren shall be shown
    -- Returns string with story
    local let, lift
    local n = table.maxn( above.children )
    local r = ""
    if n > 0 then
        local e
        local start = "\n" .. string.rep( ahead, amount )
        table.sort( above.children, firstly )
        for i = 1, n do
            e    = Current.pages[ above.children[ i ] ]
            lift = ( all or above.long )
            if e.list == false then
                let = Current.list
            elseif Current.hide then
                let = not fade( e.seed )
            else
                let = lift
            end
            if let then
                r = string.format( "%s%s%s",
                                   r,  start,  field( e, false ) )
                if lift then
                    r = r .. follow( ahead,  amount + 1,  e,  all )
                end
            end
        end -- for i
    end
    return r
end -- follow()



local function formatAll()
    -- Render as single list of entries
    local r
    local collect = { }
    local let
    local n       = 0
    for k, v in pairs( Current.pages ) do
        let = true
        if v.list == false  and
           ( not Current.list or v.loose or k == true ) then
            let = false
        elseif Current.level and v.seed:find( "/" ) then
            let = false
        elseif Current.hide then
            let = not fade( v.seed )
        end
        if let then
            if v.show then
                v.show = nil
            end
            if Current.light then
                local j, k = v.seed:find( Current.start )
                if j == 1 then
                    v.show = v.seed:sub( k + 1 )
                end
            end
            n            = n + 1
            collect[ n ] = v
        end
    end -- for k, v
    if n > 0 then
        local start
        local long = ( not Current.light )
        if Current.lineup then
            start = " * "
        else
            start = "\n" .. flag( "#" )
        end
        table.sort( collect, first )
        r = ""
        for k, v in pairs( collect ) do
            r = string.format( "%s%s%s",
                               r,
                               start,
                               field( v, long ) )
        end -- for k, v
    else
        r = false
    end
    return r
end -- formatAll()



local function formatPath( ancestor )
    -- Render tree as partially opened list
    --     ancestor  -- string, with name of root element, or false
    -- Returns string with story
    local higher
    local sup = Current.self
    local r
    if ancestor then
        higher = Current.pages[ ancestor ]
        if type( higher ) == "table" then
            higher.super = false
        end
    end
    while true do
        higher = Current.pages[ sup ]
        if type( higher ) == "table" then
            higher.long = true
            sup         = higher.super
            if not sup then
                break    -- while
            end
        else
            higher = false
            break    -- while
        end
    end -- while true
    if higher then
        r = follow( flag( "*" ),  1,  higher,  false )
    else
        r = false
    end
    return r
end -- formatPath()



local function formatSub( amend, around )
    -- Render tree as subpage hierarchy sequence
    --     amend   -- string, with name of template, or false
    --     around  -- object, with frame, or false
    -- Returns string with story, or false
    local higher
    local n       = 1
    local reverse = { }
    local sup     = Current.self
    local r
    if type( sup ) == "string" and not sup:find( "/", 1, true ) then
        flow( sup )
        repeat
            higher = Current.pages[ sup ]
            if type( higher ) == "table" then
                sup          = higher.super
                reverse[ n ] = higher
                if higher.loose then
                    n = -1
                    break    -- repeat
                elseif sup then
                    n = n + 1
                    if n > Current.maxSub then
                        reverse[ n ] = { seed = "???????" }
                        break    -- repeat
                    end
                else
                    break    -- repeat
                end
            else
                break    -- repeat
            end
        until not higher
    end
    if n > 1 then
        for i = n, 2, -1 do
            reverse[ i ] = field( reverse[ i ], false )
        end -- for i
        if amend then
            local frame
            local ordered = { }
            if around then
                frame = around
            else
                frame = mw.getCurrentFrame()
            end
            for i = n, 2, -1 do
                ordered[ n - i + 1 ] = reverse[ i ]
            end -- for i
            r = frame:expandTemplate{ title=amend, args=ordered }
        else
            r = ""
            for i = n, 2, -1 do
                if i < n then
                    r = r .. "&#160;&#62; "
                end
                r = r .. reverse[ i ]
            end -- for i
        end
    else
        r = false
    end
    return r
end -- formatSub()



local function formatTree( ancestor )
    -- Render entire tree as list text
    --     ancestor  -- string, with name of root element, or false
    -- Returns string with story, or false
    local r
    if type( ancestor ) == "string" then
        r = ancestor
    else
        r = true
    end
    r = Current.pages[ r ]
    if r then
        r = follow( flag( "#" ),  1,  r,  true )
    else
        r = false
    end
    return r
end -- formatTree()



local function forward( args )
    -- Execute main task
    --     args  -- table, with arguments
    -- Returns string with story, or false
    local r
    if type( args.series ) == "string"  and
       type( args.service ) == "string"  and
       type( args.suite ) == "string" then
        Current.series  = args.series
        Current.service = args.service
        Current.suite   = args.suite
        if type( args.hide ) == "table" then
            Current.hide = args.hide
        elseif type( args.suppress ) == "string" then
            Current.hide = { }
            table.insert( Current.hide, args.suppress )
        end
        if Current.series:match( "[:/]$" ) then
            Current.start = args.series
        else
            Current.start = args.series .. "/"
        end
        r = feed( "/" .. Current.series )
        if r then
            r = fault( r )
        else
            local life = true
            if Current.service == "path"  or
               Current.service == "subpages" then
                if args.self then
                    Current.self = args.self
                else
                    Current.page = mw.title.getCurrentTitle()
                    Current.self = Current.page.prefixedText
                end
                if not Current.pages[ Current.self ] then
                     if type( Current.pages[ true ] ) == "table" then
                        Current.self = true
                    else
                        life = false
                    end
                end
            end
            if life then
                if Current.service == "subpages" then
                    r = formatSub( args.subpager, args.frame )
                elseif Current.service == "check" then
                    Current.linked = args.linked
                    r = failures()
                else
                    for k, v in pairs( Toggles ) do
                        Current[ v ] = args[ v ]
                    end -- for k, v
                    if Current.service == "all" then
                        r = formatAll()
                    else
                        local segment
                        if type( args.segment ) == "string" then
                            segment = fair( args.segment )
                            if not Current.pages[ segment ] then
                                Current.pages[ segment ] =
                                                    { seed     = segment,
                                                      children = { },
                                                      super    = true,
                                                      list     = false }
                            end
                        end
                        fluent()
                        if Current.service == "path" then
                            r = formatPath( segment )
                        else
                            r = formatTree( segment )
                        end
                    end
                    if r and args.stamped and Current.stamp then
                        local babel = mw.language.getContentLanguage()
                        local stamp = babel:formatDate( args.stamped,
                                                        Current.stamp )
                        r = stamp .. r
                    end
                end
            else
                r = false
            end
        end
    end
    return r
end -- forward()



local function framed( frame, action )
    -- #invoke call
    --     action  -- string, with keyword
    local params = { service = action,
                     suite   = frame:getTitle() }
    local pars   = frame.args
    local lucky  = false
    local r      = pars[ 1 ]
    if r then
        params.series = mw.text.trim( r )
        if params.series == "" then
            r = false
        end
    end
    if r then
        params.frame = frame
        for k, v in pairs( Strings ) do
            if pars[ v ]  and  pars[ v ] ~= "" then
                params[ v ] = pars[ v ]
            end
        end -- for k, v
        for k, v in pairs( Toggles ) do
            if pars[ v ] then
                params[ v ] = ( pars[ v ] == "1" )
            end
        end -- for k, v
        lucky, r = pcall( forward, params )
    else
        r = "'1=' missing"
    end
    if not lucky then
        r = fault( r )
    elseif not r then
        r = ""
    end
    return r
end -- framed()



-- Export
local p = { }

-- lazy   = do not number but use bullets or nothing
-- level  = top level entries only
-- light  = strip prefix
-- linked = show redirects
-- list   = show suppressed entries

function p.all( frame )
    return  framed( frame, "all" )
end -- p.all

function p.check( frame )
    return  framed( frame, "check" )
end -- p.path

function p.path( frame )
    return  framed( frame, "path" )
end -- p.path

function p.subpages( frame )
    return  framed( frame, "subpages" )
end -- p.subpages

function p.tree( frame )
    return  framed( frame, "tree" )
end -- p.tree

function p.test( args )
    -- Debugging
    --     args  -- table, with arguments; mandatory:
    --              .series   -- tree
    --              .service  -- action mode
    --              .suite    -- Module path
    --              .self     -- page name, in service="path"
    local lucky, r = pcall( forward, args )
    return r or Current
end -- p.test()

return p