Modul:Shortcuts
Aus Simpsonspedia
Vorlagenprogrammierung | Diskussionen | Lua | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
local Shortcuts = { suite = "Shortcuts", serial = "2019-08-01", item = 0 } --[=[ Support shortcut redirects ]=] local Failsafe = Shortcuts -- local globals local Current = { rule = { } } local Errors = false local Sort = false local function face( adjust ) -- Unlink string -- adjust -- string, with some shortcuts -- Returns unlinked shortcuts return adjust:gsub( "%[%[", "" ) :gsub( "%]%]", "" ) end -- face() local function faces( any ) -- Retrieve single shortcut names -- any -- string, with shortcut name list -- Returns table with shortcut page names local s = any:gsub( "<[^>]+>", "," ) local r = mw.text.split( s, "%s*,%s*" ) for k, v in pairs( r ) do if mw.text.trim( v ) == "" then r[ k ] = nil end end -- for k, v return r end -- faces() local function facet( area, assign, assigned ) -- Create mapping shortcut->target -- area -- string, with target namespace name and colon -- assign -- string, with target page name -- assigned -- string, with shortcut page name -- Returns table with entry -- .shift -- string, with target page name -- .shortcut -- string, with shortcut page name -- .nsn -- number, of shortcut namespace -- .sort -- string, with sortable shortcut page title local space, subject = assigned:match( "^([^:]+):(.+)$" ) local r = { } r.shift = area .. assign r.shortcut = assigned if space then local o = mw.site.namespaces[ space ] if o then r.nsn = o.id end end if not r.nsn then r.nsn = 0 subject = assigned end if Sort then subject = Sort.lex( subject, "latin", false ) end r.sort = string.upper( subject ) return r end -- facet() local function facilitated( attempt ) -- Check whether Config string is a text formatting pattern -- attempt -- string, with format pattern -- Throws error with message, else does nothing local s = Config[ attempt ] if type( s ) ~= "string" or not s:find( "%s", 1, true ) then error( string.format( "/config '%s' invalid", attempt ) ) end end -- facilitated() local function factory( account, alone, assembly ) -- Retrieve mappings shortcut->target for entire target namespace -- account -- string, with module name -- alone -- number, of namespace -- assembly -- table, collecting assignments -- Extending assembly -- Returns number of target pages local space = mw.site.namespaces[ alone ] local r = 0 if space then local got local sub = string.format( "%s/%d", account, alone ) local l, t = pcall( mw.loadData, sub ) if type( t ) == "table" then if space.id == 0 then space = "" else space = space.name .. ":" end for k, v in pairs( t ) do got = faces( face( v ) ) for i, s in pairs( got ) do table.insert( assembly, facet( space, k, s ) ) end -- for i, s r = r + 1 end -- for k, v end end return r end -- factory() local function faculty( adjust ) -- Test template arg for boolean -- adjust -- string or nil -- Returns boolean local s = type( adjust ) local r if s == "string" then r = mw.text.trim( adjust ) r = ( r ~= "" and r ~= "0" ) elseif s == "boolean" then r = adjust else r = false end return r end -- faculty() local function failure( assigned, about ) -- Add one message to Errors -- assigned -- string, with shortcut page name -- about -- string, with error keyword if type( Errors ) ~= "table" then Errors = { } end Errors[ assigned ] = about end -- failure() local function fair( above ) -- Convert shortcut list namespaces into talk pages -- alert -- string, with shortcuts -- Returns converted shortcuts local r = " " .. above if type( Config.talks ) == "table" then local seek, shift for k, v in pairs( Config.talks ) do seek = string.format( "(%%A?)(%s):", k ) shift = string.format( "%%1%s:", v ) r = r:gsub( seek, shift ) end -- for k, v end return mw.text.trim( r ) end -- fair() local function fallback( access, alt ) -- Retrieve message text -- access -- string, with message ID -- alt -- string, with plain message, if no access -- Returns string, with any message local r = Config[ access ] if type( r ) ~= "string" then r = alt or "***** UNKNOWN MESSAGE " .. access .. " *****" end return r end -- fallback() 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 elem = mw.html.create( "span" ) :addClass( "error" ) local ecat = mw.message.new( "Scribunto-common-error-category" ) local r = type( alert ) if r == "string" then r = alert else r = "???? " .. r end elem:wikitext( string.format( "FATAL LUA ERROR %s", r ) ) r = tostring( elem ) if not ecat:isBlank() then ecat = string.format( "%s[[Category:%s]]", r, ecat:plain() ) end return r end -- fatal() local function fault( alert, absent ) -- Format message with class="error"; add Config (category etc.) -- alert -- string, with message -- absent -- boolean, hide message, trigger category -- Returns message with markup local e = mw.html.create( "span" ) :addClass( "error" ) :wikitext( alert or "???" ) local r = tostring( e ) if absent and type( Config ) == "table" then if type( Config.suppress ) == "string" then facilitated( "suppress" ) r = string.format( Config.suppress, r ) end if type( Config.scream ) == "string" then r = string.format( "%s[[Category:%s]]", r, Config.scream ) end end return r end -- fault() local function fiat( ahead, after ) -- Format table row -- ahead -- string, with first cell -- after -- string or false, with second cell -- Returns table row markup local r = string.format( "\n|-\n|%s", ahead ) if after then r = string.format( "%s||%s", r, after ) end return r end -- fiat() local function first( a1, a2 ) -- Compare a1 with a2 in reverse title order -- a1 -- table, with assignment -- a2 -- table, with assignment -- Returns true if a1 < a2 local r if a1.shortcut == a2.shortcut then r = ( string.upper( a1.shift ) > string.upper( a2.shift ) ) elseif a1.sort == a2.sort then r = ( a1.nsn > a2.nsn ) else r = ( a1.sort > a2.sort ) end return r end -- first() local function flag( alone, achieved ) -- Analyze one shortcut -- alone -- string, with shortcut page name -- achieved -- table, with title objects; will be extended -- Adds error message to collection local page = mw.title.new( alone ) if page.exists then if page.isRedirect then local story = page:getContent() local shift = story:match( "^#[^%[]+%[%[([^%]\n]+)%]%]" ) local redirect if not shift or shift:match( "%%%x%x" ) then redirect = false else redirect = mw.title.new( shift ) end if not redirect then failure( alone, fallback( "sayBadLink", "bad link encoding" ) ) elseif mw.title.equals( Current.page, redirect ) then for k, v in pairs( achieved ) do if mw.title.equals( page, v ) then failure( alone, fallback( "sayDuplicated", "duplicated" ) ) page = false break -- for k, v end end -- for k, v if page then table.insert( achieved, page ) end if Current.nsns == Current.nsn and not Current.leave and Config.signature and not story:find( Config.signature, 15 ) then failure( alone, fallback( "signal", ".signature (category) missing" ) ) end else failure( alone, fallback( "sayTarget", "wrong target" ) ) end else failure( alone, fallback( "sayRegular", "regular page" ) ) end else failure( alone, fallback( "sayMissing", "missing" ) ) end end -- flag() local function flash( account, alone ) -- Create all item table body with two columns; shortcut and target -- account -- string, with module name -- alone -- number or false, with namespace limitation -- Returns table rows until end, terminate table and provide totals local r = "\n|}" local collect = { } local n, previous if type( alone ) == "number" then n = factory( account, alone, collect ) elseif type( Config.rooms ) == "table" then n = 0 for k, v in pairs( Config.rooms ) do n = n + factory( account, v, collect ) end -- for k, v else r = r .. "'Config.rooms' not found" collect = false end if collect then local second, shortcut if not Sort then local l, t = pcall( require, "Module:Sort" ) if type( t ) == "table" then Sort = t.Sort() end end table.sort( collect, first ) for k, v in pairs( collect ) do shortcut = string.format( "[[%s]]", v.shortcut ) second = string.format( "[[:%s]]", v.shift ) if v.shortcut == previous then failure( previous, fallback( "sayDuplicated", "duplicated" ) ) end previous = v.shortcut r = fiat( shortcut, second ) .. r end -- for k, v end r = string.format( "%s\n%d/%d", r, #collect, n ) return r end -- flash() local function folder( arglist ) -- Present table rows -- arglist -- table, with parameters -- .targets -- table sequence, target pages specifications -- .space -- string or nil, namespace of all target pages -- .story -- string or nil, append to target page link -- .suffix -- string or nil, append to shortcut list -- Returns table row markup local n, s, space, t, targets local r = false if type( arglist.targets ) == "table" then targets = arglist.targets n = #targets else n = 0 end if n == 0 then r = fallback( "sayNoPage", "No target page" ) else if type( arglist.space ) == "string" then space = mw.text.trim( arglist.space ) if space == "" then space = false end end if not space then if n == 1 then space, s = targets[ 1 ]:match( "^([^:]*):(.+)$" ) targets[ 1 ] = s space = mw.text.trim( space ) if space == "" then space = false end else r = fallback( "sayNoNamespace", "No target namespace" ) end end if not r then local o, nsn if space then o = mw.site.namespaces[ space ] end if o then nsn = o.id space = string.format( ":%s:", o.name ) else nsn = 0 space = ":" end if type( Config.rooms ) == "table" then for k, v in pairs( Config.rooms ) do if v == nsn then local l s = string.format( "%s/%d", arglist.suite, nsn ) l, t = pcall( mw.loadData, s ) break -- for k, v end end -- for k, v end if type( t ) ~= "table" then r = string.format( "%d (%s) * %s", nsn, space, fallback( "sayNamespaceOff", "Namespace not configured" ) ) end end end if r then r = string.format( "\n|-\n|%s", fault( r, false ) ) else local shortcuts, story, suffix if type( arglist.story ) == "string" then story = arglist.story end if type( arglist.suffix ) == "string" then suffix = arglist.suffix end r = "" for i = 1, n do s = mw.text.trim( targets[ i ] ) shortcuts = t[ s ] s = string.format( "[[%s%s]]", space, s ) if story then s = s .. story end if type( shortcuts ) == "string" then if shortcuts:sub( 1, 2 ) ~= "[[" then local got = faces( face( shortcuts ) ) shortcuts = "" for k, v in pairs( got ) do shortcuts = string.format( "%s, [[%s]]", shortcuts, v ) end -- for k, v shortcuts = shortcuts:sub( 3 ) end if suffix then shortcuts = shortcuts .. suffix end else shortcuts = fault( fallback( "sayUnregistered", "no shortcuts registered" ), false ) end r = r .. fiat( s, shortcuts ) end -- for i end return r end -- folder() local function follow( arglist ) -- Analyze list of shortcuts in single page context -- arglist -- table, with parameters -- .shortcuts -- string, comma separated list of shortcuts -- .leave -- true, if dummy entry -- Throws error with message, else returns string with text local pages = { } local shortcuts = face( arglist.shortcuts ) local style = Config.style local got, r if Config.show then facilitated( "show" ) r = string.format( Config.show, shortcuts ) end if Current.style and Config[ Current.style ] then style = Config[ Current.style ] elseif Current.rule then got = type( Current.rule.styling ) if got == "boolean" then style = false elseif got == "string" then style = Config[ Current.rule.styling ] end end if style then if not Config.light then local s facilitated( "style" ) if style:find( ".sub.", 8, true ) then style = style:gsub( "%.sub%.", "-sub" ) end if style:find( "-shortcut:", 16, true ) then if Current.page.isSubpage then s = "%1line-height: 0; top: -2.5em%2" else s = "%1top: -1em%2" end style = style:gsub( "([; '])%-shortcut:%s*top([; '])", s ) s = "%sdata-shortcut-clear-right='1'" if style:find( s ) then e = mw.html.create( "div" ) e:css( { ["clear"] = "right", ["height"] = "0" } ) style = style:gsub( s, "" ) .. tostring( e ) end end r = string.format( style, r ) if r:find( "###", 16, true ) then local k = shortcuts:find( ",", 3, true ) s = shortcuts if k then s = s:sub( 1, k - 1 ) end r = r:gsub( "###", s, 1 ) end end else r = "" end got = shortcuts:gsub( "<s>[^<]+</s>", "" ) :gsub( "<strike>[^<]+</strike>", "" ) got = faces( got ) for k, v in pairs( got ) do if not arglist.leave then flag( v, pages ) end end -- for k, v if Errors then local s = "Shortcuts * " local t for k, v in pairs( Errors ) do t = mw.title.new( k ) s = string.format( "%s <u>[%s %s]</u>: %s", s, t:fullUrl( { redirect = "no" } ), k, v ) end -- for k, v r = r .. fault( s, true ) end return r end -- follow() local function forward( args ) -- Perform task -- args -- table, with parameters -- .self -- string, target page -- .shortcuts -- string, comma separated list of shortcuts -- .style -- string, particular style ID -- .loose -- boolean, ignore undefined shortcuts -- Throws error with message, else returns string with text local l, t, sub local lucky = false local r = false if type( args.suite ) == "string" then sub = args.suite .. "/config" l, t = pcall( mw.loadData, sub ) if type( t ) == "table" then Config = t else r = string.format( "'%s' invalid", sub ) end else r = "bad .suite" end if Config then local leave = false if args.self then Current.self = args.self Current.page = mw.title.new( Current.self ) if not Current.page.exists then Current.page = false r = string.format( "'%s' not found", Current.self ) end elseif args.service == "trows" then lucky = true elseif args.service == "total" then Current.page = false lucky = true else Current.page = mw.title.getCurrentTitle() Current.self = Current.page.prefixedText end if Current.page then Current.nsn = Current.page.namespace if Current.nsn < 0 then -- Special:Booksources leave = true lucky = true else if Current.nsn % 2 == 0 then Current.nsns = Current.nsn Current.nsnt = Current.nsn + 1 else Current.nsnt = Current.nsn Current.nsns = Current.nsn - 1 end t = false if type( Config.rooms ) == "table" then for k, v in pairs( Config.rooms ) do if v == Current.nsns then sub = string.format( "%s/%d", args.suite, v ) l, t = pcall( mw.loadData, sub ) break -- for k, v end end -- for k, v end if args.service == "template" then if type( Config.rules ) == "table" then local rules = Config.rules[ Current.nsn ] if type( rules ) == "table" then local seek = Current.page.text local scope for k, v in pairs( rules ) do if type( v ) == "table" then if type( v.sub ) ~= "string" or mw.ustring.match( seek, v.sub ) then Current.rule.styling = v.styling Current.rule.locally = v.locally end end end -- for k, v end end if type( t ) == "table" then sub = Current.page.text if type( t[ sub ] ) == "string" then if args.shortcuts and not Config.locally then args.shortcuts = false r = fallback( "sayNoLocals", "'1=' not permitted" ) else args.shortcuts = t[ sub ] if Current.nsn == Current.nsnt then args.shortcuts = fair( args.shortcuts ) end end elseif Current.rule.locally then args.shortcuts = false end elseif Current.rule.locally then args.shortcuts = false end if type( args.shortcuts ) == "string" then local suitable = Config.patternSuitable or "" local syntactic = "[_#%{%}|]" suitable = string.format( "^[ -~%s]+$", suitable ) args.shortcuts = mw.text.trim( args.shortcuts ) if args.shortcuts == "" then r = fallback( "sayNoShortcuts", "no shortcuts" ) elseif Current.leave and not args.shortcuts:find( "/" ) then leave = true elseif mw.ustring.match( args.shortcuts, suitable ) and not args.shortcuts:match( syntactic ) then lucky = true else r = fallback( "sayInvalidChar", "shortcut with invalid character" ) end elseif Current.page.prefixedText == Config.skip then args.shortcuts = "NS:PT" args.leave = true lucky = true elseif args.loose then leave = true elseif Config.locally then r = fallback( "sayUnknown", "Shortcuts template:" .. " page not registered," .. " '1=' missing" ) end if not lucky and Current.rule.locally then lucky = true leave = true end elseif args.service ~= "trows" and args.service ~= "total" then r = "bad .service" end end end if lucky then if leave then r = "" -- NOOP elseif args.service == "template" then Current.style = args.style r = follow( args ) elseif args.service == "trows" then r = folder( args ) elseif args.service == "total" then r = flash( args.suite, args.nsn ) end end end if not lucky and r then r = fault( r, true ) if args.service == "trows" then r = fiat( r, false ) end end return r end -- forward() local function framed( frame, action ) -- #invoke call in template environment -- action -- string, with keyword -- Returns markup local lucky, r local params = { service = action, suite = frame:getTitle() } local pars = frame:getParent().args if params.service == "template" then params.loose = faculty( pars.loose ) params.shortcuts = pars[ 1 ] if faculty( pars.light ) and params.light and frame.args.shortcut then params.shortcuts = frame.args.shortcut end if params.shortcuts then params.shortcuts = mw.text.trim( params.shortcuts ) if params.shortcuts == "" then params.shortcuts = false end end params.style = pars.style elseif params.service == "trows" then local k, v, s local got = { } for k, v in pairs( pars ) do if type( k ) == "number" then s = mw.text.trim( v ) if s ~= "" then table.insert( got, s ) end end end -- for k, v if #got > 0 then params.targets = got end params.space = pars.space params.story = pars.story params.suffix = pars.suffix elseif params.service == "total" then params.nsn = pars[ 1 ] if params.nsn and params.nsn:match( "^(%d+)$" ) then params.nsn = tonumber( params.nsn ) else params.nsn = false end end lucky, r = pcall( forward, params ) if not lucky then r = fatal( r ) end return r end -- framed() 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 = { } function p.template( frame ) return framed( frame, "template" ) or "" end -- .template function p.total( frame ) return framed( frame, "total" ) end -- .total function p.trows( frame ) return framed( frame, "trows" ) end -- .trows function p.twoletters( frame ) return framed( frame, "twoletters" ) end -- .twoletters function p.test( args ) -- Debugging -- args -- table, with arguments; mandatory: -- .suite -- Module path -- .service -- action mode, like "template" -- .shortcuts -- list -- .self -- (target) page name local lucky, r = pcall( forward, args ) return r end -- .test() 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 -- .failsafe p.Shortcuts = function () -- Module interface return Shortcuts end -- .Shortcuts return p