Modul:MediaWikiGadgetDefinition
Vorlagenprogrammierung | Diskussionen | Lua | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
local MediaWikiGadgetDefinition = { suite = "MediaWikiGadgetDefinition", serial = "2024-03-03", item = 111520596 } --[==[ Documentation, validation and comparison of MediaWiki gadget definitions ]==] local Failsafe = MediaWikiGadgetDefinition local GlobalMod = MediaWikiGadgetDefinition local ACTIVE = { } local COMMONS = { } local ERROR = { } local GLOBAL = { } local HTML = { bgcolorSleeping = "D0D0D0" } local I18N = { } local OPTIONS = { } local ROW = { comment = { live = false, suffix = "#" } } local TEMPLATE = { } local VALUES = { } COMMONS.tabs = { config = "MediaWikiGadgetDefinition", actions = "MediaWiki/URLactions", contentModels = "MediaWiki/Contentmodels", skins = "MediaWiki/SkinsWMF", modules = "MediaWiki/ResourceLoaderModules" } OPTIONS.config = { { sign = "ID", live = true, mode = 0, type = "string", scan = "rootpage" }, { sign = "ResourceLoader", live = true, mode = 1, type = "boolean" }, { sign = "default", live = true, mode = 1, type = "boolean" }, { sign = "hidden", live = true, mode = 1, type = "boolean" }, { sign = "package", live = true, mode = 1, type = "boolean" }, { sign = "targets", live = true, mode = 1, type = { "string" }, scan = "array" }, { sign = "skins", live = true, mode = 1, type = { "string" }, scan = "array" }, { sign = "rights", live = true, mode = 1, type = { "string" }, scan = "letter-id" }, { sign = "actions", live = true, mode = 1, type = { "string" }, scan = "array" }, { sign = "namespaces", live = true, mode = 1, type = { "number" }, scan = "namespace" }, { sign = "specials", live = false, mode = 1, type = { "string" }, scan = "LetterID" }, { sign = "contentModels", live = true, mode = 1, type = { "string" }, scan = "array" }, { sign = "pageprops", live = false, mode = 1, type = { "string" }, scan = "letter-_ID" }, { sign = "transcludes", live = false, mode = 1, type = { "string" }, scan = "page" }, { sign = "groups", live = false, mode = 1, type = { "string" }, scan = "letter-id" }, { sign = "userlangs", live = false, mode = 1, type = { "string" }, scan = "lang" }, { sign = "contentlangs", live = false, mode = 1, type = { "string" }, scan = "lang" }, { sign = "category", live = false, mode = 1, type = "string", scan = "page" }, { sign = "categories", live = true, mode = 1, type = { "string" }, scan = "page" }, { sign = "type", live = true, mode = 1, type = "string", scan = "radio" }, { sign = "top", live = true, mode = 1, type = "boolean" }, { sign = "supportsUrlLoad", live = true, mode = 1, type = "boolean" }, { sign = "dependencies", live = true, mode = 2, type = { "string" }, lone = true, scan = "letter.id" }, { sign = "scripts", live = true, mode = 3, type = { "string" }, scan = "page" }, { sign = "styles", live = true, mode = 3, type = { "string" }, scan = "page" }, { sign = "datas", live = true, mode = 3, type = { "string" }, scan = "page" }, { sign = "peers", live = true, mode = 3, type = { "string" }, lone = true }, { sign = "messages", live = false, mode = 3, type = { "string" }, lone = true, scan = "rootpage" }, { sign = "Comment", live = false, mode = 4, type = "string" } } VALUES.arrays = { actions = false, contentModels = false, skins = false, targets = { { "desktop", true }, { "mobile", true } } } VALUES.radio = { type = { { "general", true }, { "styles", true } } } VALUES.validate = { LetterID = { { "%u%a+" } }, lang = { { "%l%l%l?", "%l%l%l?-%a+" } }, ["letter-_ID"] = { { "%l[%-_%a]+%a" }, { "[%-_][%-_]" } }, ["letter-id"] = { { "%l[%-%l]+%l" }, { "--" } }, ["letter.id"] = { { "%l[%.%l]+%l" }, { "%.%." } }, namespace = { { "-?[1-9]%d*" } }, page = { { "[^#%[%]<>{}|]+" }, { "[ _][ _]" } }, rootpage = { { "[^/#%[%]<>{}|]+" }, { "[ _][ _]" } } } I18N.texts = { ActiveSpec = { en = "Active specification.", de = "Aktive Spezifikation." }, Assigned = { en = "Assigned", de = "Zuweisung" }, AtLine = { en = "at line", de = "in Zeile" }, BadAssigned = { en = "assignment to sole boolean", de = "Wertzuweisung an boolean" }, BadLines = { en = "Bad definition lines" }, BadNamespace = { en = "Bad namespace", de = "Namensraum ungültig" }, BadTemplateArg = { en = "Template argument no string", de = "Vorlagenparameter kein string" }, BadTemplateArgs = { en = "Template arguments no table", de = "Vorlagenparameter keine table" }, defaultTrue = { en = "default for all", de = "Vorgabe für alle" }, defaultFalse = { en = "activation needed", de = "benötigt Aktivierung" }, Detail = { en = "Property", de = "Eigenschaft" }, hiddenTrue = { en = "hidden", de = "versteckt" }, hiddenFalse = { en = "configurable", de = "konfigurierbar" }, IDmissing = { en = "ID missing", de = "ID erforderlich" }, InvalidHeadID = { en = "Invalid headline ID", de = "Ungültige Überschrift-ID" }, InvalidHeadline = { en = "Invalid headline", de = "Ungültige Überschrift" }, InvalidID = { en = "Invalid gadget ID", de = "Ungültige Gadget ID" }, InvalidResource = { en = "Invalid resource page name", de = "Seitenname der Ressource ungültig" }, InvalidScope = { en = "Scope invalid", de = "Ungültiger Scope" }, MissingValue = { en = "Missing value", de = "Wert fehlt" }, NoClosingBracket = { en = "Closing bracket missing", de = "Schließende Klammer fehlt" }, NoCommentsYet = { en = "Comments not yet available", de = "Kommentare noch nicht möglich" }, NoGadgetID = { en = "Gadget ID missing", de = "Gadget ID erforderlich" }, NoID = { en = "Missing identifier", de = "Bezeichner fehlt" }, NoOptionID = { en = "Option name missing", de = "Optionsname fehlt" }, NoResources = { en = "No resource page name", de = "Kein Seitenname einer Ressource" }, packageTrue = { en = "package support", de = "package-Unterstützung" }, packageFalse = { en = "no package support", de = "keine package-Unterstützung" }, RepeatedID = { en = "Gadget ID repeated", de = "Gadget ID wiederholt" }, RepeatedOption = { en = "Option name repeated", de = "Optionsname wiederholt" }, ResourceLoaderTrue = { en = "supported", de = "unterstützt" }, ResourceLoaderFalse = { en = "not supported", de = "nicht unterstützt" }, SpecMismatch = { en = "Specification mismatch:", de = "Spezifikation abweichend:" }, supportsUrlLoadTrue = { en = "URL loading supported", de = "Laden per URL unterstützt" }, supportsUrlLoadFalse = { en = "URL loading not supported", de = "Laden per URL nicht unterstützt" }, topTrue = { en = "top loading", de = "top-Laden" }, topFalse = { en = "regular loading sequence", de = "normale Ladereihenfolge" }, UnknownOptionID = { en = "Unknown option name", de = "Optionsname unbekannt" }, Whitespace = { en = "Superfluous whitespace", de = "Überflüssiger Whitespace" } } local find = function ( area, at ) -- Check for existing page -- Precondition: -- area -- number, of namespace, or not -- at -- string, with name of page, or mw.title -- Postcondition: -- Returns true if page existing local r, t if area then t = mw.title.makeTitle( area, at ) elseif type( at ) == "table" then t = at end if t and t.protectionLevels.edit then r = true end return r end -- find() local foreignModule = function ( access, advanced, append, alt, alert ) -- Fetch global module -- Precondition: -- access -- string, with name of base module -- advanced -- true, for require(); else mw.loadData() -- append -- string, with subpage part, if any; or false -- alt -- number, of wikidata item of root; or false -- alert -- true, for throwing error on data problem -- Postcondition: -- Returns whatever, probably table -- 2019-10-29 local storage = access local finer = function () if append then storage = string.format( "%s/%s", storage, append ) end end local fun, lucky, r, suited if advanced then fun = require else fun = mw.loadData end GlobalMod.globalModules = GlobalMod.globalModules or { } suited = GlobalMod.globalModules[ access ] if not suited then finer() lucky, r = pcall( fun, "Module:" .. storage ) end if not lucky then if not suited and type( alt ) == "number" and alt > 0 then suited = string.format( "Q%d", alt ) suited = mw.wikibase.getSitelink( suited ) GlobalMod.globalModules[ access ] = suited or true end if type( suited ) == "string" then storage = suited finer() lucky, r = pcall( fun, storage ) end if not lucky and alert then error( "Missing or invalid page: " .. storage, 0 ) end end return r end -- foreignModule() ACTIVE.first = function () -- Initialize site effective definitions local source = "Gadgets-definition" local ns, s, t ACTIVE.start = "Gadget-" if GLOBAL.scope == "site" then s = GLOBAL.config["2302"] if type( s ) == "string" and s ~= "" and s ~= "-" then ns = 2302 source = s ACTIVE.ns = 2300 ACTIVE.start = "" else ns = 8 end elseif GLOBAL.scope == "global" then elseif GLOBAL.scope == "text" then else ns, s = GLOBAL.focus( GLOBAL.scope ) if ns then ACTIVE.ns = ns if s:sub( -1, 1 ) ~= "/" then s = s .. "/" end source = s .. source ACTIVE.start = s .. "Gadget-" end end if ns then t = mw.title.makeTitle( ns, source ) ACTIVE.ns = ACTIVE.ns or ns ACTIVE.source = t.prefixedText ACTIVE.live = find( false, t ) if ACTIVE.live then ACTIVE.story = GLOBAL.frame():expandTemplate{ title = t } ACTIVE.title = t end end end -- ACTIVE.first() COMMONS.factory = function ( all, access ) -- Retrieve global configuration -- Precondition: -- all -- table, with raw tabular data -- access -- string, with keyword -- Postcondition: -- Returns table, with configuration data local r = { } local t = { } local e, s, v table.insert( t, "string" ) if access == "modules" then table.insert( t, "string" ) elseif access == "actions" then table.insert( t, "boolean" ) elseif access == "contentModels" then elseif access == "skins" then table.insert( t, "boolean" ) end for i = 1, #all do e = all[ i ] if type( e ) == "table" and type( e[ 1 ] ) == "string" then s = mw.text.trim( e[ 1 ] ) if s ~= "" and not s:find( "[ \"\n=&|{}%[%]<>#]" ) then v = e[ 2 ] if t[ 2 ] then if type( v ) ~= t[ 2 ] then s = false end elseif access == "contentModels" then v = true end if s then if access == "modules" then r[ s ] = v else e = { } table.insert( e, s ) table.insert( e, v ) table.insert( r, e ) end end end end end -- for i return r end -- COMMONS.factory() COMMONS.fetch = function ( access ) -- Retrieve global configuration -- Precondition: -- access -- string, with keyword -- Postcondition: -- Returns table, with configuration data, or true if unavailable COMMONS.tabDATA = COMMONS.tabDATA or { } if not COMMONS.tabDATA[ access ] then local storage = COMMONS.tabs[ access ] local v if storage then local lucky, data storage = string.format( "%s.tab", storage ) lucky, data = pcall( mw.ext.data.get, storage ) if type( data ) == "table" then data = data.data if type( data ) == "table" then v = data end end end if v then v = COMMONS.factory( v, access ) else v = true end COMMONS.tabDATA[ access ] = v end return COMMONS.tabDATA[ access ] end -- COMMONS.fetch() ERROR.fault = function ( about, area ) -- Register an error -- Precondition: -- about -- string, with message -- area -- string, with field, or not local e = { } ERROR.errors = ERROR.errors or { } table.insert( e, about ) table.insert( e, area ) table.insert( ERROR.errors, e ) end -- ERROR.fault() ERROR.flat = function ( about, align ) -- Format code -- Precondition: -- about -- string, with message -- align -- true, if string requested -- Postcondition: -- Returns mw.html, or string local r = mw.html.create( "code" ) :wikitext( mw.text.nowiki( about ) ) if align then r = tostring( r ) end return r end -- ERROR.flat() ERROR.full = function () -- Retrieve list of all error messages -- Postcondition: -- Returns mw.html, or not local r return r end -- ERROR.full() GLOBAL.features = function () -- Retrieve local configuration -- Postcondition: -- Configuration updated where requested local site = GLOBAL.frame():getTitle() .. "/local" local seek = string.format( "^%s:(.+)$", mw.site.namespaces[ 828 ].name ) site = site:match( seek, 1 ) if find( 828, site ) then local lucky, config = pcall( mw.loadData, site ) if type( config ) == "table" then if type( config.modules ) == "table" then for k, v in pairs( config.modules ) do GLOBAL.modules[ k ] = v end -- for k, v end if type( config.actions ) == "table" then for k, v in pairs( config.actions ) do VALUES.arrays.actions[ k ] = v end -- for k, v end if type( config.IDexceptions ) == "table" then for k, v in pairs( config.IDexceptions ) do GLOBAL.IDexceptions[ k ] = v end -- for k, v end end end end -- GLOBAL.features() GLOBAL.first = function () -- Initialize global and local configuration, if not yet done -- Postcondition: -- Configuration up to date if not GLOBAL.config then local config = COMMONS.fetch( "config" ) if type( config ) ~= "table" then config = { } end GLOBAL.config = config VALUES.full() config = COMMONS.fetch( "modules" ) if type( config ) ~= "table" then config = { } end GLOBAL.modules = config GLOBAL.IDexceptions = { } GLOBAL.features() OPTIONS.first() end end -- GLOBAL.first() GLOBAL.focus = function ( area ) -- Analyze page name in name space -- Precondition: -- area -- string, with page name, not in gadget spaces -- Postcondition: -- Returns: -- 1. namespace number -- 2. normalized page title local i = area:find( ":", 1, true ) local rNS, rT if i then local s if i == 1 then rNS = 0 rT = area:sub( i + 1 ) else local q s = mw.text.trim( area:sub( 1, i - 1 ) ) q = mw.site.namespaces[ s ] if q and q.id ~= 8 and q.id ~= 2300 and q.id ~= 2302 then rNS = q.id end end if rNS then s = mw.text.trim( area:sub( i + 1 ) ) rT = string.format( "%s:%s", mw.site.namespaces[ rNS ].name, s ) end else rNS = 0 rT = area end return rNS, rT end -- GLOBAL.focus() GLOBAL.frame = function ( frame ) -- Retrieve and memorize frame object -- Precondition: -- frame -- frame object, or not -- Postcondition: -- Returns frame object if frame then GLOBAL.Frame = frame end if not GLOBAL.Frame then GLOBAL.Frame = mw.getCurrentFrame() end return GLOBAL.Frame end -- GLOBAL.frame() HTML.feature = function ( at, assigned, about, fault ) -- Format single gadget option table row -- Precondition: -- at -- string, with gadget option name -- assigned -- gadget option value -- about -- table, with gadget option configuration -- fault -- function, as error handler -- Postcondition: -- Returns mw.html object <tr> local r = mw.html.create( "tr" ) local td1 = mw.html.create( "td" ) local td2 = mw.html.create( "td" ) local e, ns, s td1:wikitext( at ) if at:sub( 1, 1 ) == "-" then s = at:sub( 2 ) td1:attr( "data-sort-value", s .. "-" ) else s = at end if about.type == "boolean" then if assigned then s = s .. "True" else s = s .. "False" end td2:wikitext( I18N.fetch( s ) ) elseif type( about.type ) == "table" and type( assigned ) == "table" then if s == "targets" or s == "skins" or s == "rights" or s == "actions" or s == "contentModels" or s == "pageprops" or s == "groups" or s == "type" then for i = 1, #assigned do e = mw.html.create( "code" ) :css( "margin-right", "0.4em" ) :css( "white-space", "nowrap" ) :wikitext( assigned[ i ] ) td2:node( e ) end -- for i elseif s == "namespaces" then local q for i = 1, #assigned do s = assigned[ i ] ns = tonumber( s ) e = mw.html.create( "code" ) :css( "margin-right", "0.4em" ) :wikitext( s ) q = mw.site.namespaces[ ns ] if q then e:attr( "title", q.name .. ":" ) else e:css( "background-color", "#FF0000" ) e:css( "color", "#FFFF00" ) e:css( "font-weight", "bold" ) end td2:node( e ) end -- for i elseif s == "specials" then local space = mw.site.namespaces.special.name local k = #space + 2 for i = 1, #assigned do s = mw.text.nowiki( assigned[ i ] ) e = mw.html.create( "code" ) :css( "margin-right", "0.4em" ) :wikitext( s ) s = GLOBAL.frame():callParserFunction( "#special", assigned[ i ] ) e:attr( "title", s:sub( k ) ) td2:node( e ) end -- for i elseif s == "transcludes" or s == "categories" then local link14 = ( s == "categories" ) for i = 1, #assigned do s = mw.text.nowiki( assigned[ i ] ) if link14 then s = string.format( ":%s:%s", mw.site.namespaces[14].name, s ) end s = string.format( "[[%s]]", s ) e = mw.html.create( "span" ) :css( "margin-right", "0.4em" ) :css( "white-space", "nowrap" ) e:wikitext( s ) td2:node( e ) end -- for i elseif s == "userlangs" or s == "contentlangs" then elseif s == "dependencies" then local hash = COMMONS.tabDATA.modules if type( hash ) ~= "table" then hash = false end for i = 1, #assigned do s = mw.text.nowiki( assigned[ i ] ) e = mw.html.create( "span" ) :css( "margin-right", "0.4em" ) :css( "white-space", "nowrap" ) if hash and hash[ s ] then s = string.format( "[[%s|%s]]", hash[ s ], s ) end e:wikitext( s ) td2:node( e ) end -- for i elseif s == "scripts" or s == "styles" or s == "datas" or s == "peers" then local t for i = 1, #assigned do s = mw.text.nowiki( assigned[ i ] ) t = mw.title.makeTitle( ACTIVE.ns, ACTIVE.start .. s ) e = mw.html.create( "span" ) :css( "margin-right", "0.4em" ) :css( "white-space", "nowrap" ) :wikitext( string.format( "[[%s|%s]]", t.prefixedText, s ) ) td2:node( e ) end -- for i elseif s == "messages" then else td2:wikitext( tostring(assigned) ) end else td2:wikitext( tostring(assigned) ) end if not about.live then r:css( "background-color", "#" .. HTML.bgcolorSleeping ) end r:node( td1 ) :node( td2 ) return r end -- HTML.feature() HTML.features = function ( assigned ) -- Format gadget options table -- Precondition: -- assigned -- table, with gadget options -- Postcondition: -- Returns mw.html object <table> local r = mw.html.create( "table" ) local n = 0 local fault = function ( add ) assigned.ERRORS = assigned.ERRORS or { } table.insert( assigned.ERRORS, add ) end local e, o, s, td, th1, th2 r:addClass( "wikitable" ) if assigned.ID then local t s = mw.text.nowiki( assigned.ID ) t = mw.title.makeTitle( 8, "Gadget-" .. s ) e = mw.html.create( "caption" ) :addClass( "mw-code" ) :attr( "id", s ) :wikitext( string.format( "[[%s|%s]]", t.prefixedText, s ) ) r:newline() :node( e ) if find( false, t ) then s = GLOBAL.frame():callParserFunction( "int", t.text ) td = mw.html.create( "td" ) :attr( "colspan", "2" ) :wikitext( s ) r:newline() :node( mw.html.create( "tr" ) :addClass( "sortbottom" ) :node( td ) ) end else fault( I18N.fetch( "NoID" ) ) end th1 = mw.html.create( "th" ) :wikitext( I18N.fetch( "Detail" ) ) th2 = mw.html.create( "th" ) :addClass( "unsortable" ) :wikitext( I18N.fetch( "Assigned" ) ) r:newline() :node( mw.html.create( "tr" ) :node( th1 ) :node( th2 ) ) for i = 2, #OPTIONS.order - 1 do o = OPTIONS.order[ i ] s = o.sign e = assigned[ s ] if type( e ) ~= "nil" then r:newline() :node( HTML.feature( s, e, o, fault ) ) n = n + 1 end end -- for i if n > 1 then r:addClass( "sortable" ) end if assigned.Comment then td = mw.html.create( "td" ) :addClass( "sortbottom" ) :attr( "colspan", "2" ) :wikitext( assigned.Comment ) r:newline() :node( mw.html.create( "tr" ) :node( td ) ) end if assigned.ERRORS then e = mw.html.create( "ul" ) for i = 1, #assigned.ERRORS do e:node( mw.html.create( "li" ) :wikitext( assigned.ERRORS[ i ] ) ) end -- for i td = mw.html.create( "td" ) :addClass( "error" ) :addClass( "sortbottom" ) :attr( "colspan", "2" ) :node( e ) r:newline() :node( mw.html.create( "tr" ) :node( td ) ) end r:newline() return r end -- HTML.features() HTML.flat = function ( apply ) -- Format coded value -- Precondition: -- assigned -- string, with value -- Postcondition: -- Returns mw.html object <div> local r = mw.html.create( "div" ) :addClass( "mw-code" ) :css( "white-space", "nowrap" ) :css( "overflow", "auto" ) :css( "padding-right", "2em" ) :wikitext( mw.text.nowiki( apply ) ) return r end -- HTML.flat() HTML.folder = function ( assigned ) -- Build gadget options table with resulting definition row and link -- Precondition: -- assigned -- table, with gadget options -- Postcondition: -- Returns mw.html object <div> local e = mw.html.create( "div" ) :wikitext( string.format( "[[%s]]", ACTIVE.source ) ) local g = ROW.fiat( assigned ) local r = mw.html.create( "div" ) :node( HTML.features( assigned ) ) :newline() :node( HTML.flat( g ) ) :newline() :node( e ) local d = ROW.found( assigned.ID ) if d then d = OPTIONS.fair( assigned, d ) e = mw.html.create( "div" ) if d then local diff = mw.html.create( "div" ) e:css( "color", "#FF0000" ) :css( "font-weight", "bold" ) :wikitext( I18N.fetch( "SpecMismatch" ) ) diff:css( "margin-left", "1em" ) :css( "margin-right", "1em" ) for i = 1, #d do diff:node( mw.html.create( "code" ) :wikitext( mw.text.nowiki( d[ i ] ) ) ) end -- for i e:node( diff ) else e:css( "color", "#00A000" ) :css( "font-weight", "bold" ) :wikitext( I18N.fetch( "ActiveSpec" ) ) end r:newline() :node( e ) end return r end -- HTML.folder() I18N.fetch = function ( ask ) -- Retrieve localized text -- Precondition: -- ask -- string, with keyword -- Postcondition: -- Returns string, with translation local e = I18N.texts[ ask ] local r if e then if not I18N.slang then I18N.slang = mw.language.getContentLanguage():getCode() :lower() end r = e[ I18N.slang ] if not e then r = e.en end end return r or string.format( "I18N{%s}", ask ) end -- I18N.fetch() OPTIONS.fair = function ( a1, a2 ) -- Compare two definitions -- Precondition: -- a1 -- table, with definitions -- a2 -- table, with definitions -- Postcondition: -- Returns hash table, of mismatching assignments local l, o, r, s, v1, v2 for i = 1, #OPTIONS.order do o = OPTIONS.order[ i ] if o.live then s = o.sign v1 = a1[ s ] v2 = a2[ s ] if v1 and v2 and v1 ~= v2 then if type( v1 ) == "table" and type( v2 ) == "table" then if #v1 == #v2 then l = false for k = 1, #v1 do if v1[ k ] ~= v2[ k ] then l = true break -- for k end end -- for k if not l then v1 = false v2 = false end end end if v1 ~= v2 then r = r or { } o = { } table.insert( o, v1 ) table.insert( o, v2 ) r[ s ] = o end end end end -- for i return r end -- OPTIONS.fair() OPTIONS.first = function () -- Initialize gadget options configuration -- Postcondition: -- Configuration up to date local e, s, u OPTIONS.hash = { } OPTIONS.order = { } for i = #OPTIONS.config, 1, -1 do e = OPTIONS.config[ i ] if not e.live then s = GLOBAL[ e.sign ] if s == "1" or s == true then e.live = true end end s = e.sign OPTIONS.hash[ s ] = e if e.mode then if e.mode == 1 and not e.lone and e.type ~= "boolean" then s = "-" .. e.sign if not OPTIONS.hash[ s ] then u = { sign = s, live = false, mode = 1, type = e.type, scan = e.scan } table.insert( OPTIONS.order, 1, u ) OPTIONS.hash[ s ] = u table.insert( OPTIONS.config, u ) end end table.insert( OPTIONS.order, 1, e ) end end -- for i end -- OPTIONS.first() ROW.failed = function ( assigned, at ) -- Create line number referenced error in Gadgets-definition -- Precondition: -- assigned -- string, with bad data -- at -- number, with line number -- Postcondition: -- Returns string, with formatted message, leading space local r if type( assigned ) == "string" and type( at ) == "number" then r = mw.html.create( "span" ) :node( ERROR.flat( assigned ) ) :wikitext( string.format( " %s ", I18N.fetch( "AtLine" ) ) ) :node( mw.html.create( "code" ) :wikitext( string.format( "%d", at ) ) ) r = " " .. tostring( r ) end return r or "" end -- ROW.failed() ROW.fetch = function ( all, access ) -- Parse line in Gadgets-definition -- Precondition: -- all -- table, with all line specifications -- .hash -- table, with gadget ID mapping -- .rows -- sequence table, with line content -- access -- number or string, to be accessed -- Postcondition: -- Returns table, with line properties local r if type( all ) == "table" then local errors, slot local fault = function ( add ) errors = errors or { } local s = add if slot then s = s .. ROW.failed( slot, all.hash[ slot ] ) end table.insert( errors, s ) end local s = type( access ) local n if s == "number" then n = access elseif s == "string" then n = all.hash[ access ] slot = access end if n then local story = all.rows[ n ] if type( story ) == "string" and story ~= "" then local i, j, o, opts, ress story = story:gsub( "^%s*%*%s*", "" ) r = { } if type( ROW.comment ) == "table" and type( ROW.comment.suffix ) == "string" and ROW.comment.suffix ~= "" then i = story:find( ROW.comment.suffix ) if i then if i == 1 then r.Comment = story:sub( 2 ) else r.Comment = story:sub( i + 1 ) story = story:sub( 1, i - 1 ) story = mw.text.trim( story ) end r.Comment = mw.text.trim( r.Comment ) end end if r.Comment and not ( ROW.comment.live or ROW.lazy ) then fault( I18N.fetch( "NoCommentsYet" ) ) end if not ROW.lazy then fault( I18N.fetch( "Whitespace" ) ) end i = story:find( "[", 1, true ) if i then if i > 1 then slot = mw.text.trim( story:sub( 1, i - 1 ) ) j = story:find( "]", i + 1, true ) if j then s = mw.text.trim( story:sub( i + 1, j - 1 ) ) opts = mw.text.split( s, "%s*|%s*" ) if #opts < 1 then opts = false end story = mw.text.trim( story:sub( j + 1 ) ) else fault( I18N.fetch( "NoClosingBracket" ) ) story = "" end else slot = "" story = "" end else i = story:find( "|", 1, true ) if i then slot = mw.text.trim( story:sub( 1, i - 1 ) ) story = mw.text.trim( story:sub( i + 1 ) ) else slot = "" story = "" end end if slot == "" then fault( I18N.fetch( "NoGadgetID" ) ) ress = { } else ress = VALUES.feed( story, "%s*|%s*" ) or { } end if opts then local set, v for i = 1, #opts do s = mw.text.trim( opts[ i ] ) j = s:find( "=", i, true ) if j then if j == 1 then set = false o = false s = string.format( "%s %s", ERROR.flat( slot, true ), I18N.fetch( "NoOptionID" ) ) fault( s ) else set = s:sub( j + 1 ) set = mw.text.trim( set ) s = s:sub( 1, j - 1 ) s = mw.text.trim( s ) o = OPTIONS.hash[ s ] end else o = OPTIONS.hash[ s ] set = false end if o then if r[ o.sign ] then s = string.format( "%s|%s", slot, o.sign ) s = string.format( "%s %s", ERROR.flat( s, true ), I18N.fetch( "RepeatedOption" ) ) fault( s ) elseif type( o.type ) == "table" then if set then v = VALUES.feed( set, ",", o.type[ 1 ] == "number" ) else v = false end if type( v ) == "table" then if o.sign == "peers" then ROW.fill( r, v, "peers", fault ) end else s = string.format( "%s|%s=", slot, o.sign ) s = string.format( "%s %s", s, I18N.fetch( "MissingValue" ) ) fault( s ) end elseif o.type == "boolean" then if set then v = false s = string.format( "%s|%s=", slot, o.sign ) s = string.format( "%s %s", s, I18N.fetch( "BadAssigned" ) ) else v = true end elseif o.type == "string" then if set then s = string.format( "%s|%s=", slot, o.sign ) s = string.format( "%s %s", s, I18N.fetch( "MissingValue" ) ) fault( s ) else v = set end end if v then r[ o.sign ] = v end else s = string.format( "%s %s", I18N.fetch( "UnknownOptionID" ), ERROR.flat( s, true ) ) fault( s ) end end -- for i end if #ress > 0 then ROW.fill( r, ress, false, fault ) elseif o and o.sign ~= "peers" then fault( I18N.fetch( "NoResources" ) ) end end end end return r end -- ROW.fetch() ROW.fiat = function ( about ) -- Create line for Gadgets-definition -- Precondition: -- about -- table, with gadget options -- Postcondition: -- Returns string, with line local r if about.ID then local o, set, sources, v r = string.format( "* %s", about.ID ) for i = 2, #OPTIONS.order do o = OPTIONS.order[ i ] if o.live then v = about[ o.sign ] if v then if o.mode == 1 or o.mode == 2 then if set then set = set .. "|" else set = "" end set = set .. o.sign if type( o.type ) == "table" and type( v ) == "table" then set = set .. "=" for k = 1, #v do if k > 1 then set = set .. "," end set = set .. v[ k ] end -- for k end elseif o.mode == 3 then sources = sources or "" for k = 1, #v do sources = string.format( "%s|%s", sources, v[ k ] ) end -- for k end end end end -- for i if set then r = string.format( "%s[%s]", r, set ) end if sources then r = r .. sources end else r = "*" end return r end -- ROW.fiat() ROW.fill = function ( all, assign, ancestor, fault ) -- Distribute resource pages -- Precondition: -- all -- table, with gadget options -- about -- sequence table, with resource pages -- ancestor -- "peers", or not -- fault -- function, as error handler -- Postcondition: -- resource page has been added to all table if type( all ) == "table" and type( assign ) == "table" then local types local src, swap, types if ancestor == "peers" then types = { peers = "%.css$" } else types = { scripts = "%.js$", styles = "%.css$", datas = "%.json$" } end for i = 1, #assign do src = assign[ i ] swap = false for k, v in pairs( types ) do if src:find( v ) then swap = k end end -- for k, v if swap then all[ swap ] = all[ swap ] or { } table.insert( all[ swap ], src ) else fault( string.format( "%s %s", I18N.fetch( "InvalidResource" ), ERROR.flat( src, true ) ) ) end end -- for i end end -- ROW.fill() ROW.first = function ( all, apply ) -- Convert Gadgets-definition lines into tables -- Precondition: -- all -- string, with all Gadgets-definition lines -- apply -- mw.title or string, with origin -- Postcondition: -- Returns table, with -- .rows -- sequence table of line strings -- .hash -- mapping table of gadget ID to lines -- .elts -- sequence table of heading IDs and lines -- .origin -- mw.title or string local r if type( all ) == "string" then local errors local fault = function ( add ) errors = errors or { } table.insert( errors, add ) end local elts = { } local hash = { } local rows = mw.text.split( all, "%s*\n%s*" ) local k, s, scream r = { origin = apply } for i = 1, #rows do s = mw.text.trim( rows[ i ] ) if s ~= "" then if s:sub( 1, 2 ) == "==" then scream = false k, s = s:match( "^(==+)%s*([^%s=].+[^%s=])%s*%1$" ) if s then table.insert( elts, s ) if not VALUES.fine( s, "rootpage" ) then scream = "InvalidHeadID" end else scream = "InvalidHeadline" end if scream then s = I18N.fetch( scream ) .. ROW.failed( "==", i ) fault( s ) end else s = s:gsub( "^%s*%*%s*", "" ) k = s:find( "[%[|]" ) if k then if k == 1 then s = "" else s = mw.text.trim( s:sub( 1, k - 1 ) ) end end if s ~= "" then scream = false table.insert( elts, i ) if hash[ s ] then scream = "RepeatedID" else hash[ s ] = i if not VALUES.fine( s, "rootpage" ) then scream = "InvalidID" end end if scream then s = I18N.fetch( scream ) .. ROW.failed( s, i ) fault( s ) end end end end end -- for i r.rows = rows r.elems = elts r.hash = hash if errors then s = I18N.fetch( "BadLines" ) .. ROW.from( apply ) table.insert( errors, s, 1 ) for i = 1, #errors do ERROR.fault( errors[ i ], "ROW" ) end -- for i end end return r end -- ROW.first() ROW.found = function ( ask ) -- Retrieve single entry from active -- Precondition: -- ask -- string, with entry ID local r if ACTIVE.live and not ACTIVE.def then ACTIVE.def = ROW.first( ACTIVE.story, ACTIVE.title ) end if ACTIVE.def then r = ROW.fetch( ACTIVE.def, ask ) end return r end -- ROW.found() ROW.from = function ( all ) -- Format origin -- Precondition: -- all -- table, with Gadgets-definition representation -- Postcondition: -- Returns string, with hint local r if type( all ) == "table" then if type( all.origin ) == "table" then local s = all.origin.prefixedText if type( s ) == "string" then r = string.format( "[[%s]]", s ) else r = "??????????" end else r = "TEXT" end end return r or "" end -- ROW.from() TEMPLATE.fill = function ( all ) -- Convert template parameters into hash -- Precondition: -- all -- table, with template parameters -- Postcondition: -- Returns table, with options local r = { } if type( all ) == "table" then local errors local fault = function ( add ) r.ERRORS = r.ERRORS or { } table.insert( r.ERRORS, add ) end local n, o, s, seps, vals r = { } for k, v in pairs( all ) do if type( v ) == "string" then o = OPTIONS.hash[ k ] if o then s = type( o.type ) if s == "string" then s = o.type elseif s == "table" then s = string.format( "{%s}", o.type[ 1 ] ) else s = false end if s == "boolean" then if v == "1" then r[ k ] = true else r[ k ] = false end elseif s == "string" then r[ k ] = v elseif s then v = v:gsub( "|", "|" ) :gsub( "|", "|" ) :gsub( "|", "|" ) if o.scan == "page" then seps = "%s*|%s*" else seps = "[%s,|]+" end vals = mw.text.split( v, seps ) for i = 1, #vals do v = mw.text.trim( vals[ i ] ) if v == "" then v = false elseif s == "{number}" then n = tonumber( v ) if n then v = n else s = string.format( "%s %s", I18N.fetch( "BadNamespace" ), ERROR.flat( v, true ) ) v = false end end if v then r[ k ] = r[ k ] or { } table.insert( r[ k ], v ) end end -- for i end else s = string.format( "%s %s", I18N.fetch( "UnknownOptionID" ), ERROR.flat( k, true ) ) fault( s ) end else s = string.format( "%s %s", I18N.fetch( "BadTemplateArg" ), ERROR.flat( k, true ) ) fault( s ) end end -- for k, v else fault( I18N.fetch( "BadTemplateArgs" ) ) end return r end -- TEMPLATE.fill() TEMPLATE.filter = function ( all, apart ) -- Discard ignorable template parameters -- Precondition: -- all -- table, with template parameters -- apart -- string, with ignored template parameters -- Postcondition: -- Returns table, with template parameters local clear = { } local r = { } for i = 1, #apart do clear[ apart[ i ] ] = true end -- for i for k, v in pairs( all ) do if not clear[ k ] then r[ k ] = v end end -- for k, v return r end -- TEMPLATE.filter() VALUES.features = function ( all, adjust, after ) -- Convert list of items -- Precondition: -- all -- string, with list of items -- adjust -- string, with type of item -- after -- string, with separator -- Postcondition: -- Returns table, with items, or not local scan = mw.text.trim( all ) local suitable = adjust[ 1 ] local r, sep, v, vals if after == "," then sep = "%s*,%s*" else sep = "[%s,|]+" end vals = mw.text.split( scan, sep ) for i = 1, #vals do s = mw.text.split( vals[ i ] ) if s == "" then v = false elseif suitable == "number" then v = VALUES.fine( s, "namespace" ) else v = s end if v then r = r or { } table.insert( r, v ) end end -- for i return r end -- VALUES.features() VALUES.feed = function ( analyse, at, ask ) -- Check value -- Precondition: -- analyse -- string, with list -- at -- string, with separator -- ask -- true, requesting number elements, or not -- Postcondition: -- Returns table, or string with error, or nothing local r = mw.text.split( analyse, at ) local s for i = #r, 1, -1 do s = mw.text.trim( r[ i ] ) if s == "" then table.remove( r, i ) elseif ask then if s:match( "^-?%d+$" ) then r[ i ] = tonumber( s ) else r = { } break -- for i end end end -- for i if #r == 0 then r = false end return r end -- VALUES.feed() VALUES.fine = function ( analyse, as ) -- Check value -- Precondition: -- analyse -- string or number, with value -- as -- string, with key of pattern -- Postcondition: -- Returns true, if fine local s = type( analyse ) local n, r if s == "string" then local req = VALUES.validate[ as ] s = type( req ) if s == "table" then local oblis = req[ 1 ] local excls = req[ 2 ] if type( oblis ) == "table" then for i = 1, #oblis do s = oblis[ i ] if type( s ) == "string" then r = analyse:match( string.format( "^%s$", s ) ) if r then r = true break -- for i end end end -- for i end if r and type( excls ) == "table" then for i = 1, #excls do s = excls[ i ] if type( s ) == "string" and analyse:match( s ) then r = false break -- for i end end -- for i end elseif s == "string" then end if type( r ) ~= "boolean" then ERROR.fault( "VALUES.validate: " .. as, "INTERNAL" ) elseif r and as == "namespace" then n = tonumber( analyse ) end elseif s == "number" then n = analyse end if n then if mw.site.namespaces[ n ] then r = n else r = false end end return r end -- VALUES.fine() VALUES.full = function () -- Retrieve list of permitted items -- Postcondition: -- tables with permitted items present for k, v in pairs( VALUES.arrays ) do if not VALUES.arrays[ k ] then VALUES.arrays[ k ] = COMMONS.fetch( k ) or { } end end -- for k, v end -- VALUES.full() MediaWikiGadgetDefinition.f = function ( arglist, frame ) -- Perform standard functionality -- Precondition: -- arglist -- table, with parameters -- frame -- frame object, or not -- Postcondition: -- Returns mw.html, or not local r, s GLOBAL.frame( frame ) if type( arglist.id ) == "string" then GLOBAL.id = mw.text.trim( arglist.id ) end if type( arglist.template ) == "table" then GLOBAL.template = arglist.template if type( GLOBAL.template.ID ) == "string" then GLOBAL.id = mw.text.trim( GLOBAL.template.ID ) end if type( arglist.IGNORE ) == "table" then GLOBAL.template = TEMPLATE.filter( GLOBAL.template, arglist.IGNORE ) end end if GLOBAL.id == "" or GLOBAL.id == "-" then GLOBAL.id = false end s = arglist.use if type( s ) == "string" then s = mw.text.trim( s ) if s ~= "" and s ~= "-" then ACTIVE.story = s GLOBAL.scope = "text" if not GLOBAL.id then GLOBAL.id = "*" end end end if GLOBAL.id then local tbl if type( arglist.strictRows ) == "boolean" then ROWS.lazy = arglist.strictRows end if type( arglist.Build ) == "string" then s = arglist.Build:upper() if s == "JSON" or s == "ROWS" then GLOBAL.syntax = s end end GLOBAL.syntax = GLOBAL.syntax or "HTML" if not GLOBAL.scope and type( arglist.scope ) == "string" then s = mw.text.trim( arglist.scope ) if s == "site" or s == "global" then GLOBAL.scope = s elseif s == "" then GLOBAL.scope = "site" else if s:find( ":", 1, true ) then local ns ns, GLOBAL.scope = GLOBAL.focus( s ) end end if not GLOBAL.scope then ERROR.fault( I18N.fetch( "InvalidScope" ), "#invoke" ) end end GLOBAL.scope = GLOBAL.scope or "site" if arglist.Export and GLOBAL.scope == "site" and GLOBAL.syntax == "HTML" then HTML.export = true end GLOBAL.first() if GLOBAL.scope ~= "global" then ACTIVE.first() end if GLOBAL.template then tbl = TEMPLATE.fill( GLOBAL.template ) r = HTML.folder( tbl ) end else ERROR.fault( I18N.fetch( "IDmissing" ), "#invoke" ) end return r end -- .f() MediaWikiGadgetDefinition.params = function () -- Communicate possible template parameters -- Postcondition: -- Returns mw.html <ul> local r = mw.html.create( "ul" ) local e, o OPTIONS.first() for i = 2, #OPTIONS.order do o = OPTIONS.order[ i ] e = mw.html.create( "li" ) :wikitext( o.sign ) if not o.live then e:css( "text-decoration", "line-through" ) end r:newline() :node( e ) end -- for i return r end -- .params() MediaWikiGadgetDefinition.suggestedvalues = function ( array ) local r if type( VALUES.arrays[ array ] ) == "boolean" then local vals = COMMONS.fetch( array ) if type( vals ) == "table" then local sep = "" local e for i = 1, #vals do e = vals[ i ] if e[ 2 ] then r = string.format( "%s%s\"%s\"", r or "", sep, e[ 1 ] ) sep = ", " end end -- for i end end r = r or "ERROR" return string.format( "[ %s ]", r ) end -- .suggestedvalues() 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.f = function ( frame ) local params = { } if frame.args.TEMPLATE == "1" then params.template = frame:getParent().args if frame.args.IGNORE and frame.args.IGNORE ~="" then params.IGNORE = mw.text.split( frame.args.IGNORE, "%s*|%s*" ) end else if frame.args.Use and frame.args.Use ~="" and frame.args.Use ~="-" then params.use = frame.args.Use else if frame.args.ExportLink == 1 then params.export = true end params.scope = frame.args.Scope end end if frame.args.StrictRows == "1" then params.strictRows = true end if frame.args.ID and frame.args.ID ~= "" and frame.args.ID ~= "-" then params.id = frame.args.ID end if frame.args.Build and frame.args.Build ~="" then params.build = frame.args.Build end return MediaWikiGadgetDefinition.f( params, frame ) or "" end -- p.f p.params = function ( frame ) return MediaWikiGadgetDefinition.params() end -- p.params p.suggestedvalues = function ( frame ) local s = frame.args[ 1 ] if s then s = mw.text.trim( s ) if s == "" then s = false end end return MediaWikiGadgetDefinition.suggestedvalues( s ) end -- p.suggestedvalues 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 setmetatable( p, { __call = function ( func, ... ) setmetatable( p, nil ) return Failsafe end } ) return p