Modul:MediaWikiGadgetDefinition
| Vorlagenprogrammierung | Diskussionen | Lua | Unterseiten | |||
| Modul | Deutsch | English
|
Modul: | Dokumentation | ||
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData:
2024-11-15local MediaWikiGadgetDefinition = { suite = "MediaWikiGadgetDefinition",
serial = "2024-11-15",
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:
-- apply -- string, with value
-- Postcondition:
-- Returns mw.html object <div>
local e = mw.html.create( "pre" )
local r = mw.html.create( "div" )
e:addClass( "mw-code" )
:css( "overflow", "auto" )
:css( "margin-right", "0.5em" )
:css( "margin-top", "0.5em" )
:css( "white-space", "nowrap" )
:wikitext( mw.text.nowiki( apply ) )
r:addClass( "mw-highlight mw-highlight-lang-text" )
:addClass( "mw-highlight-copy" )
:css( "margin-right", "2em" )
:css( "padding-top", "1em" )
:newline()
:node( e )
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" )
local p = mw.html.create( "span" )
p:css( "color", "#FF0000" )
:css( "font-weight", "bold" )
:wikitext( I18N.fetch( "SpecMismatch" ) )
e:node( p )
diff:css( "margin-left", "1em" )
:css( "margin-right", "1em" )
for k, v in pairs( d ) do
diff:node( mw.html.create( "code" )
:wikitext( k, " " ) )
for i = 1, #v do
p = v[ i ]
diff:wikitext( " " )
diff:node( mw.html.create( "em" )
:wikitext( mw.text.nowiki( p[ 1 ] ) ) )
diff:wikitext( " " )
diff:node( mw.html.create( "strong" )
:wikitext( mw.text.nowiki( p[ 2 ] ) ) )
end -- for i
diff:node( mw.html.create( "br" ) )
end -- for k, v
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
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.