Nicht angemeldeter Benutzer - Bearbeiten von Seiten ist nur als angemeldeter Benutzer möglich.
Modul:PageTree
Version vom 27. Juni 2014, 20:55 Uhr von wikipedia-de>PerfektesChaos (update)
Die Dokumentation für dieses Modul kann unter Modul:PageTree/Doku erstellt werden
--[=[ 2014-06-26
Module:pageTree
]=]
-- local globals
local Current = { }
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.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>→[[%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
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 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
local higher
local n = 1
local reverse = { }
local sup = Current.self
local r
flow( Current.self )
repeat
higher = Current.pages[ sup ]
if type( higher ) == "table" then
sup = higher.super
reverse[ n ] = higher
if sup then
n = n + 1
else
break -- repeat
end
else
break -- repeat
end
until not higher
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 .. " > "
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