Modul:PageTree: Unterschied zwischen den Versionen
w>PerfektesChaos (update) |
w>PerfektesChaos (+ limit (restrictions)) |
||
Zeile 1: | Zeile 1: | ||
--[=[ 2014-12- | --[=[ 2014-12-28 | ||
Module:pageTree | Module:pageTree | ||
]=] | ]=] | ||
Zeile 9: | Zeile 9: | ||
local Sort = false | local Sort = false | ||
local Strings = { "segment", "self", "stamped", "subpager", "suppress" } | local Strings = { "segment", "self", "stamped", "subpager", "suppress" } | ||
local Toggles = { "lazy", "level", "lineup", "light", "linked", "list" } | local Toggles = { "lazy", "level", "lineup", "light", "linked", "limit", | ||
"list" } | |||
Zeile 148: | Zeile 149: | ||
return r | return r | ||
end -- fair() | end -- fair() | ||
local function fasten( adopt ) | |||
-- Format restrictions | |||
-- adopt -- string, with restriction entry | |||
-- Returns absolute page name, or false | |||
local designs = { | |||
autoconfirmed = "background:#FFFF00", | |||
superprotect = "background:#FF0000;border:#FFFF00 9px solid", | |||
sysop = "background:#FFFF00;border:#FF0000 2px solid", | |||
["?????????"] = "border:#FF0000 5px solid;color:#FF0000" } | |||
local restrictions = mw.text.split( adopt, ":" ) | |||
local r = "" | |||
local start = "margin-left:2em;" | |||
local staff, strict, style | |||
for i = 1, #restrictions do | |||
strict, staff = restrictions[ i ]:match( "^(.*)=(.+)$" ) | |||
strict = mw.text.trim( strict ) | |||
if strict == "" then | |||
strict = "?????????" | |||
end | |||
style = designs[ staff ] | |||
if not style then | |||
style = designs[ "?????????" ] | |||
strict = strict .. "?????????" | |||
end | |||
if start then | |||
style = start .. style | |||
start = false | |||
end | |||
style = style .. ";padding-left:3px;padding-right:3px;" | |||
r = string.format( "%s<span style='%s'>%s</span>", | |||
r, style, strict ) | |||
end -- for i | |||
return r | |||
end -- fasten() | |||
Zeile 335: | Zeile 373: | ||
-- Format entry as link | -- Format entry as link | ||
-- about -- table, with entry | -- about -- table, with entry | ||
-- .show | -- .show -- link title | ||
-- .seed | -- .seed -- page name | ||
-- .shift | -- .shift -- redirect target | ||
-- .protection -- restrictions | |||
-- absolute -- true, if real page name to be shown | -- absolute -- true, if real page name to be shown | ||
-- Returns string | -- Returns string | ||
Zeile 357: | Zeile 396: | ||
r = string.format( "%s <small>→[[%s]]</small>", | r = string.format( "%s <small>→[[%s]]</small>", | ||
r, fair( about.shift ) ) | r, fair( about.shift ) ) | ||
end | |||
if Current.limit and type( about.protection ) == "string" then | |||
r = string.format( "%s %s", | |||
r, fasten( about.protection ) ) | |||
end | end | ||
return r | return r | ||
Zeile 806: | Zeile 849: | ||
r = formatPath( segment ) | r = formatPath( segment ) | ||
else | else | ||
if args.limit == "1" or | |||
args.limit == true then | |||
Current.limit = true | |||
end | |||
r = formatTree( segment ) | r = formatTree( segment ) | ||
end | end | ||
Zeile 903: | Zeile 950: | ||
-- .suite -- Module path | -- .suite -- Module path | ||
-- .self -- page name, in service="path" | -- .self -- page name, in service="path" | ||
-- .limit -- show restrictions | |||
local lucky, r = pcall( forward, args ) | local lucky, r = pcall( forward, args ) | ||
return r or Current | return r or Current |
Version vom 28. Dezember 2014, 22:04 Uhr
Die Dokumentation für dieses Modul kann unter Modul:PageTree/doc erstellt werden
--[=[ 2014-12-28 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", "limit", "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 fasten( adopt ) -- Format restrictions -- adopt -- string, with restriction entry -- Returns absolute page name, or false local designs = { autoconfirmed = "background:#FFFF00", superprotect = "background:#FF0000;border:#FFFF00 9px solid", sysop = "background:#FFFF00;border:#FF0000 2px solid", ["?????????"] = "border:#FF0000 5px solid;color:#FF0000" } local restrictions = mw.text.split( adopt, ":" ) local r = "" local start = "margin-left:2em;" local staff, strict, style for i = 1, #restrictions do strict, staff = restrictions[ i ]:match( "^(.*)=(.+)$" ) strict = mw.text.trim( strict ) if strict == "" then strict = "?????????" end style = designs[ staff ] if not style then style = designs[ "?????????" ] strict = strict .. "?????????" end if start then style = start .. style start = false end style = style .. ";padding-left:3px;padding-right:3px;" r = string.format( "%s<span style='%s'>%s</span>", r, style, strict ) end -- for i return r end -- fasten() local function fatal( alert ) -- Format disaster message with class="error" and put into category -- alert -- string, with message, or other data -- Returns message string with markup local ecat = mw.message.new( "Scribunto-common-error-category" ) local r = type( alert ) if r == "string" then r = alert else r = "???? " .. r end if ecat:isBlank() then ecat = "" else ecat = string.format( "[[Category:%s]]", ecat:plain() ) end r = string.format( "<span class=\"error\">FATAL LUA ERROR %s</span>", r ) .. ecat return r end -- fatal() 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 elseif e.super == nil then 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 -- .protection -- restrictions -- 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 if Current.limit and type( about.protection ) == "string" then r = string.format( "%s %s", r, fasten( about.protection ) ) 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 firsthand( a1, a2 ) -- Compare a1 with a2, considering .show -- a1 -- string, with page name -- a2 -- string, with page name -- Returns true if a1 < a2 return first( a1, a2, true ) 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 v.super == nil 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, firsthand ) 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 sup = Current.self local higher, i, r if ancestor then higher = Current.pages[ ancestor ] if type( higher ) == "table" then higher.super = false end else local point = Current.pages[ sup ] if not point or point.list == false then sup = true end end for i = Current.maxSub, 0, -1 do higher = Current.pages[ sup ] if type( higher ) == "table" then higher.long = true sup = higher.super if not sup then break -- for end else higher = false break -- for end end -- for --i 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 .. " > " 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 if args.limit == "1" or args.limit == true then Current.limit = true end 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 r = pars[ 1 ] if r then params.series = mw.text.trim( r ) if params.series == "" then r = false end end if r then local lucky 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 ) if not lucky then r = fatal( r ) end else r = fault( "'1=' missing" ) end if 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" -- .limit -- show restrictions local lucky, r = pcall( forward, args ) return r or Current end -- p.test() return p