Module:Format TemplateData

local TemplateData = { suite = "TemplateData", serial = "2022-03-10", item  = 46997995 } --[==[ improve template:TemplateData ]==] local Failsafe = TemplateData

local Config = { -- multiple option names mapped into unique internal fields basicCnf = { catProblem         = "strange", classMultiColumns  = "selMultClm", classNoNumTOC      = "suppressTOCnum", classTable         = "classTable", cssParWrap         = "cssTabWrap", cssParams          = "cssTable", docpageCreate      = "suffix", docpageDetect      = "subpage", helpBoolean        = "support4boolean", helpContent        = "support4content", helpDate           = "support4date", helpFile           = "support4wiki-file-name", helpFormat         = "supportFormat", helpLine           = "support4line", helpNumber         = "support4number", helpPage           = "support4wiki-page-name", helpString         = "support4string", helpTemplate       = "support4wiki-template-name", helpURL            = "support4url", helpUser           = "support4wiki-user-name", msgDescMiss        = "solo", tStylesTOCnum      = "stylesTOCnum", tStylesMultiColumns = "stylesMultClm" }, classTable    = { "wikitable" },    -- classes for params table debugmultilang = "C0C0C0", loudly        = false,    -- show exported element, etc.    solo           = false,    -- complaint on missing description strange       = false,    -- title of maintenance category cssTable      = false,    -- styles for params table cssTabWrap    = false,    -- styles for params table wrapper debug         = false, subpage       = false,    -- pattern to identify subpage suffix        = false,    -- subpage creation scheme suppressTOCnum = false,   -- class for TOC number suppression jsonDebug     = "json-code-lint"    -- class for jsonDebug tool } local Data = { div    = false,    -- got    = false,    -- table, initial templatedata object heirs  = false,    -- table, params that are inherited jump   = false,    -- source position at end of "params" less   = false,    -- main description missing lasting = false,   -- old syntax encountered lazy   = false,    -- doc mode; do not generate effective leading = false,   -- show TOC -- low     = false,    -- 1= mode order  = false,    -- parameter sequence params = false,    -- table, exported parameters scream = false,    -- error messages sibling = false,   -- TOC juxtaposed slang  = nil,      -- project/user language code slim   = false,    -- JSON reduced to plain source = false,    -- JSON input strip  = false,    -- evaluation tag    = false,    -- table, exported root element title  = false,    -- page tree   = false     -- table, rewritten templatedata object } local Permit = { builder = { after          = "block", align          = "block", block          = "block", compressed     = "block", dense          = "block", grouped        = "inline", half           = "inline", indent         = "block", inline         = "inline", last           = "block", lead           = "block", newlines       = "*", spaced         = "inline" }, colors = { bg          = "FFFFFF", fg         = "000000", tableheadbg = "B3B7FF", required   = "EAF3FF", suggested  = "FFFFFF", optional   = "EAECF0", deprecated = "FFCBCB" }, params = { aliases         = "table", autovalue      = "string", default        = "string table I18N nowiki", deprecated     = "boolean string I18N", description    = "string table I18N", example        = "string table I18N nowiki", label          = "string table I18N", inherits       = "string", required       = "boolean", style          = "string table", suggested      = "boolean", suggestedvalues = "string table number boolean", type           = "string" }, root   = { description = "string table I18N", format     = "string", maps       = "table", params     = "table", paramOrder = "table", sets       = "table" }, search = "[{,]%%s*(['\"])%s%%1%%s*:%%s*%%{",    types   = { boolean                   = true,                content                   = true,                date                      = true,                line                      = true,                number                    = true,                string                    = true,                unknown                   = true,                url                       = true,                ["wiki-file-name"]        = true,                ["wiki-page-name"]        = true,                ["wiki-template-name"]    = true,                ["wiki-user-name"]        = true,                ["unbalanced-wikitext"]   = true,                ["string/line"]           = "line",                ["string/wiki-page-name"] = "wiki-page-name",                ["string/wiki-user-name"] = "wiki-user-name" } }

local function Fault( alert ) -- Memorize error message -- Parameter: --    alert  -- string, error message if Data.scream then Data.scream = string.format( "%s *** %s", Data.scream, alert ) else Data.scream = alert end end -- Fault

local function Fetch( ask, allow ) -- Fetch module -- Parameter: --    ask    -- string, with name --                      "/global" --                      "Multilingual" --                      "Text" --                      "WLink" --    allow  -- true: no error if unavailable -- Returns table of module -- error: Module not available local sign = ask local r, stem if sign:sub( 1, 1 ) == "/" then sign = TemplateData.frame:getTitle .. sign else stem = sign sign = "Module:" .. stem end if TemplateData.extern then r = TemplateData.extern[ sign ] else TemplateData.extern = { } end if not r then local lucky, g = pcall( require, sign ) if type( g ) == "table" then if stem and  type( g[ stem ] ) == "function" then r = g[ stem ] else r = g           end TemplateData.extern[ sign ] = r       elseif not allow then error( string.format( "Fetch(%s) %s", sign, g ), 0 ) end end return r end -- Fetch

local function Foreign -- Guess human language -- Returns slang, or not if type( Data.slang ) == "nil" then local Multilingual = Fetch( "Multilingual", true ) if Multilingual and type( Multilingual.userLangCode ) == "function" then Data.slang = Multilingual.userLangCode else Data.slang = mw.language.getContentLanguage:getCode :lower end end if Data.slang and mw.ustring.codepoint( Data.slang, 1, 1 ) > 122 then Data.slang = false end return Data.slang end -- Foreign

local function facet( ask, at ) -- Find physical position of parameter definition in JSON -- Parameter: --    ask  -- string, parameter name --    at   -- number, physical position within definition -- Returns number, or nil local seek = string.format( Permit.search,                               ask:gsub( "%%", "%%%%" )                                   :gsub( "([%-.+*?^$%[%]])", "%%%1" ) )   local i, k, r, slice, source if not Data.jump then Data.jump = Data.source:find( "params", 2 ) if Data.jump then Data.jump = Data.jump + 7 else Data.jump = 1 end end i, k = Data.source:find( seek, at + Data.jump ) while i and  not r do        source = Data.source:sub( k + 1 ) slice = source:match( "^%s*\"([^\"]+)\"s*:" )        if not slice then            slice = source:match( "^%s*'([^']+)'%s*:" )        end        if ( slice and Permit.params[ slice ] )   or           source:match( "^%s*%}" ) then            r = k        else            i, k = Data.source:find( seek,  k )        end    end    -- while i    return r end -- facet

local function facilities( apply ) -- Retrieve details of suggestedvalues -- Parameter: --    apply  -- table, with plain or enhanced values --              .suggestedvalues  -- table|string|number, or more -- Returns --    1  -- table, with suggestedvalues --    2  -- table, with CSS map, or not --    3  -- string, with class, or not --    4  -- string, with templatestyles, or not local elements = apply.suggestedvalues local s       = type( elements ) local r1, r2, r3, r4   if s == "table" then local values = elements.values if type( values ) == "table" then r1 = values if type( elements.scroll ) == "string" then r2 = r2 or  { } r2.height  = apply.scroll r2.overflow = "auto" end if type( elements.minwidth ) == "string" then local s = type( elements.maxcolumns ) r2 = r2 or  { } r2["column-width"] = elements.minwidth if s == "string" or                   s == "number" then s = tostring( elements.maxcolumns ) r2["column-count"] = s               end if type( Config.selMultClm ) == "string" then r3 = Config.selMultClm end if type( Config.stylesMultClm ) == "string" then local src = Config.stylesMultClm .. "/styles.css" r4 = TemplateData.frame :extensionTag( "templatestyles",                                                   nil,                                                    { src = src } ) end end elseif elements and  elements ~= "" then r1 = elements end elseif s == "string" then s = mw.text.trim( about ) if s ~= "" then r1 = { } table.insert( r1,                         { code = s } ) end elseif s == "number" then r1 = { } table.insert( r1,                     { code = tostring( elements ) } ) end return r1, r2, r3, r4 end -- facilities

local function factory( adapt ) -- Retrieve localized text from system message -- Parameter: --    adapt  -- string, message ID after "templatedata-" -- Returns string, with localized text local o = mw.message.new( "templatedata-" .. adapt ) if Foreign then o:inLanguage( Data.slang ) end return o:plain 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 failures -- Retrieve error collection and category -- Returns string local r   if Data.scream then local e = mw.html.create( "span" ) :addClass( "error" ) :wikitext( Data.scream ) r = tostring( e ) mw.addWarning( "TemplateData " .. Data.scream ) if Config.strange then r = string.format( "%s",                              r,                               Config.strange ) end else r = "" end return r end -- failures

local function fair( adjust ) -- Reduce text to one line of plain text, or noexport wikitext blocks --    adjust  -- string -- Returns string, with adjusted text local f   = function ( a ) return a:gsub( "%s*\n%s*", " " ) :gsub( "%s%s+", " " ) end local tags = { { start = " ", stop = " " }, { start = " ", stop = " ", l    = false } }   local r = adjust local i, j, k, s, tag for m = 1, 2 do       tag = tags[ m ] if r:find( tag.start, 1, true ) then s    = r            r     = "" i    = 1 tag.l = true j, k = s:find( tag.start, i, true ) while j do               if j > 1 then r = r .. f( s:sub( i, j - 1 ) ) end i   = k + 1 j, k = s:find( tag.stop, i, true ) if j then if m == 1 then r = r .. s:sub( i, j - 1 ) end i   = k + 1 j, k = s:find( tag.start, i, true ) else Fault( "missing " .. tag.stop ) end end   -- while j            r = r .. s:sub( i ) elseif m == 1 then r = f( r ) end end -- for m   if tags[ 2 ].l then r = r:gsub( " .* ", "" ) end return r end -- fair

local function fancy( advance, alert ) -- Present JSON source -- Parameter: --    advance  -- true, for nice --    alert    -- true, for visible -- Returns string local r   if Data.source then local support = Config.jsonDebug local css if advance then css = { height = "6em", resize = "vertical" } r  = { [ 1 ] = "syntaxhighlight", [ 2 ] = Data.source, lang = "json", style = table.concat( css, ";" ) } if alert then r.class( support ) end r = TemplateData.frame:callParserFunction( "#tag", r ) else css = { [ "font-size" ]  = "77%", [ "line-height" ] = "1.35" } if alert then css.resize = "vertical" else css.display = "none" end r = mw.html.create( "pre" ) :addClass( support ) :css( css ) :wikitext( mw.text.encode( Data.source ) ) r = tostring( r ) end r = "\n".. r   else r = "" end return r end -- fancy

local function faraway( alternatives ) -- Retrieve best language version from multilingual text -- Parameter: --    alternatives  -- table, to be evaluated -- Returns --    1  -- string, with best match --    2  -- table of other versions, if any local n = 0 local variants = { } local r1, r2   for k, v in pairs( alternatives ) do        if type( v ) == "string" then v = mw.text.trim( v ) if v ~= "" and  type( k ) == "string" then k = k:lower variants[ k ] = v               n             = n + 1 end end end -- for k, v   if n > 0 then local Multilingual = Fetch( "Multilingual", true ) if Multilingual and type( Multilingual.i18n ) == "function" then local show, slang = Multilingual.i18n( variants ) if show then r1 = show variants[ slang ] = nil r2 = variants end end if not r1 then Foreign for k, v in pairs( variants ) do               if n == 1 then r1 = v               elseif Data.slang == k then variants[ k ] = nil r1 = v                   r2 = variants end end -- for k, v       end if r2 and Multilingual then for k, v in pairs( r2 ) do               if v  and  not Multilingual.isLang( k, true ) then Fault( string.format( "%s ", "Invalid", k ) ) end end -- for k, v       end end return r1, r2 end -- faraway

local function fashioned( about, asked, assign ) -- Create description head -- Parameter: --    about   -- table, supposed to contain description --    asked   -- true, if mandatory description --    assign  --, if to be equipped -- Returns, with head, or nil local para = assign or mw.html.create( "div" ) local plus, r   if about and about.description then if type( about.description ) == "string" then para:wikitext( about.description ) else para:wikitext( about.description[ 1 ] ) plus = mw.html.create( "ul" ) plus:css( "text-align", "left" ) for k, v in pairs( about.description[ 2 ] ) do               plus:node( mw.html.create( "li" )                                  :node( mw.html.create( "code" ) :wikitext( k ) )                                 :node( mw.html.create( "br" ) )                                  :wikitext( fair( v ) ) ) end -- for k, v           if Config.loudly then plus = mw.html.create( "div" ) :css( "background-color",                                   "#" .. Config.debugmultilang ) :node( plus ) else plus:addClass( "templatedata-maintain" ) :css( "display", "none" ) end end elseif Config.solo and asked then para:addClass( "error" ) :wikitext( Config.solo ) Data.less = true else para = false end if para then if plus then r = mw.html.create( "div" ) :node( para ) :node( plus ) else r = para end end return r end -- fashioned

local function fatten( access ) -- Create table row for sub-headline -- Parameter: --    access  -- string, with name -- Returns local param    = Data.tree.params[ access ] local sub, sort = access:match( "(=+)%s*(%S.*)$" ) local headline = mw.html.create( string.format( "h%d", #sub ) ) local r        = mw.html.create( "tr" ) local td       = mw.html.create( "td" ) :attr( "colspan", "5" ) :attr( "data-sort-value", "!" .. sort ) local s   if param.style then s = type( param.style ) if s == "table" then td:css( param.style ) elseif s == "string" then td:cssText( param.style ) end end s = fashioned( param, false, headline ) if s then headline = s   else headline:wikitext( sort ) end td:node( headline ) r:node( td ) return r end -- fatten

local function fathers -- Merge params with inherited values local n = 0 local p = Data.params local t = Data.tree.params local p2, t2   for k, v in pairs( Data.heirs ) do        n = n + 1 end -- for k, v   for i = 1, n do        if Data.heirs then for k, v in pairs( Data.heirs ) do               if v  and  not Data.heirs[ v ] then n              = n - 1 t[ k ].inherits = nil Data.heirs[ k ] = nil p2             = { } t2             = { } if p[ v ] then for k2, v2 in pairs( p[ v ] ) do                           p2[ k2 ] = v2                        end -- for k2, v2                        if p[ k ] then for k2, v2 in pairs( p[ k ] ) do                               if type( v2 ) ~= "nil" then p2[ k2 ] = v2                               end end -- for k2, v2                       end p[ k ] = p2                       for k2, v2 in pairs( t[ v ] ) do                            t2[ k2 ] = v2                        end -- for k2, v2                        for k2, v2 in pairs( t[ k ] ) do                            if type( v2 ) ~= "nil" then t2[ k2 ] = v2                           end end -- for k2, v2                       t[ k ] = t2                    else Fault( "No params[] inherits " .. v ) end end end -- for k, v       end end -- i = 1, n   if n > 0 then local s       for k, v in pairs( Data.heirs ) do            if v then if s then s = string.format( "%s &#124; %s", s, k ) else s = "Circular inherits: " .. k               end end end -- for k, v       Fault( s ) end end -- fathers

local function favorize -- Local customization issues local boole = { ["font-size"] = "125%" } local l, cx = pcall( mw.loadData,                        TemplateData.frame:getTitle .. "/config" ) local scripting, style TemplateData.ltr = not mw.language.getContentLanguage:isRTL if TemplateData.ltr then scripting = "left" else scripting = "right" end boole[ "margin-" .. scripting ] = "3em" Permit.boole = { [false] = { css = boole, lead = true, show = "&#x2610;" }, [true] = { css  = boole, lead = true, show = "&#x2611;" } } Permit.css  = { } for k, v in pairs( Permit.colors ) do       if k == "tableheadbg" then k = "tablehead" end if k == "fg" then style = "color" else style = "background-color" end Permit.css[ k ] = { } Permit.css[ k ][ style ] = "#" .. v   end -- for k, v    if type( cx ) == "table" then local c, s       if type( cx.permit ) == "table" then if type( cx.permit.boole ) == "table" then if type( cx.permit.boole[ true ] ) == "table" then Permit.boole[ false ] = cx.permit.boole[ false ] end if type( cx.permit.boole[ true ] ) == "table" then Permit.boole[ true ] = cx.permit.boole[ true ] end end if type( cx.permit.css ) == "table" then for k, v in pairs( cx.permit.css ) do                   if type( v ) == "table" then Permit.css[ k ] = v                   end end -- for k, v           end end for k, v in pairs( Config.basicCnf ) do           s = type( cx[ k ] ) if s == "string" or  s == "table" then Config[ v ] = cx[ k ] end end -- for k, v   end if type( Config.subpage ) ~= "string" or       type( Config.suffix ) ~= "string" then local got = mw.message.new( "templatedata-doc-subpage" ) local suffix if got:isDisabled then suffix = "doc" else suffix = got:plain end if type( Config.subpage ) ~= "string" then Config.subpage = string.format( "/%s$", suffix ) end if type( Config.suffix ) ~= "string" then Config.suffix = string.format( "%%s/%s", suffix ) end end end -- favorize

local function feasible( all, at, about ) -- Deal with suggestedvalues within parameter -- Parameter: --    all    -- parameter details --              .default --              .type --    at     -- string, with parameter name --    about  -- suggestedvalues  -- table, --                                  value and possibly description --                                  table may have elements: --                                   .code    -- mandatory --                                   .label   -- table|string --                                   .support -- table|string --                                   .icon    -- string --                                   .class   -- table|string --                                   .css     -- table --                                   .style   -- string --                                   .less    -- true: suppress code -- Returns --    1: mw.html object  --    2: sequence table with values, or nil local h = { } local e, r1, r2, s, v   if #about > 0 then for i = 1, #about do           e = about[ i ] s = type( e ) if s == "table" then if type( e.code ) == "string" then s = mw.text.trim( e.code ) if s == "" then e = nil else e.code = s                   end else e = nil s = string.format( "params.%s.%s[%d] %s",                                      at,                                       "suggestedvalues",                                       i,                                       "MISSING 'code:'" ) end elseif s == "string" then s = mw.text.trim( e ) if s == "" then e = nil s = string.format( "params.%s.%s[%d] EMPTY",                                      at, "suggestedvalues", i ) Fault( s ) else e = { code = s } end elseif s == "number" then e = { code = tostring( e ) } else s = string.format( "params.%s.%s[%d] INVALID",                                  at, "suggestedvalues", i ) Fault( s ) e = false end if e then v = v or  { } table.insert( v, e ) if h[ e.code ] then s = string.format( "params.%s.%s REPEATED %s",                                      at,                                       "suggestedvalues",                                       e.code ) Fault( s ) else h[ e.code ] = true end end end -- for i   else Fault( string.format( "params.%s.suggestedvalues %s", at, "NOT AN ARRAY" ) ) end if v then local code, d, k, less, story, swift, t, u       r1 = mw.html.create( "ul" ) r2 = { } for i = 1, #v do           u = mw.html.create( "li" ) e = v[ i ] table.insert( r2, e.code ) story = false less = ( e.less == true ) if not less then swift = e.code if e.support then local scream, support s = type( e.support ) if s == "string" then support = e.support elseif s == "table" then support = faraway( e.support ) else scream = "INVALID" end if support then s = mw.text.trim( support ) if s == "" then scream = "EMPTY" elseif s:find( "[%[%]|%<%>]" ) then scream = "BAD PAGE" else support = s                       end end if scream then s = string.format( "params.%s.%s[%d].support %s",                                          at,                                           "suggestedvalues",                                           i,                                           scream ) Fault( s ) else swift = string.format( "%s",                                              support, swift ) end end if all.type:sub( 1, 5 ) == "wiki-" and swift == e.code then local rooms = { file = 6, temp = 10, user = 2 } local ns = rooms[ all.type:sub( 6, 9 ) ] or  0 t = mw.title.makeTitle( ns, swift ) if t and t.exists then swift = string.format( "%s",                                              t.prefixedText, swift ) end end if e.code == all.default then k = 800 else k = 300 end code = mw.html.create( "code" ) :css( "font-weight", tostring( k ) ) :css( "white-space", "nowrap" ) :wikitext( swift ) u:node( code ) end if e.class then s = type( e.class ) if s == "string" then u:addClass( e.class ) elseif s == "table" then for k, s in pairs( e.class ) do                       u:addClass( s ) end -- for k, s               else s = string.format( "params.%s.%s[%d].class INVALID",                                      at, "suggestedvalues", i ) Fault( s ) end end if e.css then if type( e.css ) == "table" then u:css( e.css ) else s = string.format( "params.%s.%s[%d].css INVALID",                                      at, "suggestedvalues", i ) Fault( s ) end end if e.style then if type( e.style ) == "string" then u:cssText( e.style ) else s = string.format( "params.%s.%s[%d].style INVALID",                                      at, "suggestedvalues", i ) Fault( s ) end end if all.type == "wiki-file-name" and  not e.icon then e.icon = e.code end if e.label then s = type( e.label ) if s == "string" then s = mw.text.trim( e.label ) if s == "" then s = string.format( "params.%s.%s[%d].label %s",                                          at,                                           "suggestedvalues",                                           i,                                           "EMPTY" ) Fault( s ) else story = s                   end elseif s == "table" then story = faraway( e.label ) else s = string.format( "params.%s.%s[%d].label INVALID",                                      at, "suggestedvalues", i ) Fault( s ) end end s = false if type( e.icon ) == "string" then t = mw.title.makeTitle( 6, e.icon ) if t and t.file.exists then local g = mw.html.create( "span" ) s = string.format( "16px", t.prefixedText ) g:attr( "role", "presentation" ) :wikitext( s ) s = tostring( g ) end end if not s and  not less  and  e.label then s = mw.ustring.char( 0x2013 ) end if s then d = mw.html.create( "span" ) :wikitext( s ) if TemplateData.ltr then if not less then d:css( "margin-left", "0.5em" ) end if story then d:css( "margin-right", "0.5em" ) end else if not less then d:css( "margin-right", "0.5em" ) end if story then d:css( "margin-left", "0.5em" ) end end u:node( d ) end if story then u:wikitext( story ) end r1:newline :node( u ) end -- for i   end if not r1 and  v ~= false then Fault( string.format( "params.%s.suggestedvalues INVALID", at ) ) r1 = mw.html.create( "code" ) :addClass( "error" ) :wikitext( "INVALID" ) end return r1, r2 end -- feasible

local function feat -- Check and store parameter sequence if Data.source then local i = 0 local s       for k, v in pairs( Data.tree.params ) do            if i == 0 then Data.order = { } i = 1 s = k           else i = 2 break -- for k, v           end end -- for k, v       if i > 1 then local pointers = { } local points  = { } local given   = { } for k, v in pairs( Data.tree.params ) do               i = facet( k, 1 ) if type( v ) == "table" then if type( v.label ) == "string" then s = mw.text.trim( v.label ) if s == "" then s = k                       end else s = k                   end if given[ s ] then if given[ s ] == 1 then local scream = "Parameter label '%s' detected multiple times" Fault( string.format( scream, s ) ) given[ s ] = 2 end else given[ s ] = 1 end end if i then table.insert( points, i ) pointers[ i ] = k                   i = facet( k, i ) if i then s = "Parameter '%s' detected twice" Fault( string.format( s, k ) ) end else s = "Parameter '%s' not detected" Fault( string.format( s, k ) ) end end -- for k, v           table.sort( points ) for i = 1, #points do               table.insert( Data.order,  pointers[ points[ i ] ] ) end -- i = 1, #points elseif s then table.insert( Data.order, s ) end end end -- feat

local function feature( access ) -- Create table row for parameter, check and display violations -- Parameter: --    access  -- string, with name -- Returns local mode, s, status local fine   = function ( a ) s = mw.text.trim( a ) return a == s and a ~= "" and not a:find( "%|=\n" ) and not a:find( "%s%s" ) end local begin  = mw.html.create( "td" ) local code   = mw.html.create( "code" ) local desc   = mw.html.create( "td" ) local eager  = mw.html.create( "td" ) local legal  = true local param  = Data.tree.params[ access ] local ranking = { "required", "suggested", "optional", "deprecated" } local r      = mw.html.create( "tr" ) local styles = "mw-templatedata-doc-param-" local sort, typed

for k, v in pairs( param ) do       if v == "" then param[ k ] = false end end -- for k, v

-- label sort = param.label or access if sort:match( "^%d+$" ) then begin:attr( "data-sort-value",                   string.format( "%05d", tonumber( sort ) ) ) end begin:css( "font-weight", "bold" ) :wikitext( sort )

-- name and aliases code:css( "font-size", "92%" ) :css( "white-space", "nowrap" ) :wikitext( access ) if not fine( access ) then code:addClass( "error" ) Fault( string.format( "Bad ID params. ", access ) ) legal = false begin:attr( "data-sort-value", " " .. sort ) end code = mw.html.create( "td" ) :addClass( styles .. "name" ) :node( code ) if access:match( "^%d+$" ) then code:attr( "data-sort-value",                  string.format( "%05d", tonumber( access ) ) ) end if type( param.aliases ) == "table" then local lapsus, syn for k, v in pairs( param.aliases ) do           code:tag( "br" ) if type( v ) == "string" then if not fine( v ) then lapsus = true code:node( mw.html.create( "span" )                                     :addClass( "error" )                                      :css( "font-style", "italic" )                                      :wikitext( "string" ) ) :wikitext( s ) else syn = mw.html.create( "span" ) :addClass( styles .. "alias" ) :css( "white-space", "nowrap" ) :wikitext( s ) code:node( syn ) end else lapsus = true code:node( mw.html.create( "code" )                                 :addClass( "error" )                                  :wikitext( type( v ) ) ) end end -- for k, v       if lapsus then s = string.format( "params. .aliases", access ) Fault( factory( "invalid-value" ):gsub( "$1", s )  ) legal = false end end

-- description etc.   s = fashioned( param ) if s then desc:node( s ) end if param.style then s = type( param.style ) if s == "table" then desc:css( param.style ) elseif s == "string" then desc:cssText( param.style ) end end if param.suggestedvalues or      param.default or       param.example or       param.autovalue then local details = { "suggestedvalues", "default", "example", "autovalue" } local dl     = mw.html.create( "dl" ) local dd, section, show for i = 1, #details do           s    = details[ i ] show = param[ s ] if show then dd     = mw.html.create( "dd" ) section = factory( "doc-param-" .. s ) if param.type == "boolean"  and ( show == "0" or show == "1" ) then local boole = Permit.boole[ ( show == "1" ) ] if boole.lead == true then dd:node( mw.html.create( "code" )                                       :wikitext( show ) ) :wikitext( " " ) end if type( boole.show ) == "string" then local v = mw.html.create( "span" ) :attr( "aria-hidden", "true" ) :wikitext( boole.show ) if boole.css then v:css( boole.css ) end dd:node( v ) end if type( boole.suffix ) == "string" then dd:wikitext( boole.suffix ) end if boole.lead == false then dd:wikitext( " " ) :node( mw.html.create( "code" )                                       :wikitext( show ) ) end elseif s == "suggestedvalues" then local v, css, class, ts = facilities( param ) if v then local ul                       ul, v = feasible( param, access, v ) if v then dd:newline :node( ul ) if css then dd:css( css ) if class then dd:addClass( class ) end if ts then dd:newline dd:node( ts ) end end Data.params[ access ].suggestedvalues = v                       end end else dd:wikitext( show ) end dl:node( mw.html.create( "dt" )                               :wikitext( section ) ) :node( dd ) end end -- i = 1, #details desc:node( dl ) end

-- type if type( param.type ) == "string" then param.type = mw.text.trim( param.type ) if param.type == "" then param.type = false end end if param.type then s    = Permit.types[ param.type ] typed = mw.html.create( "td" ) :addClass( styles .. "type" ) if s then if s == "string" then Data.params[ access ].type = s               typed:wikitext( factory( "doc-param-type-" .. s ) ) :tag( "br" ) typed:node( mw.html.create( "span" )                                  :addClass( "error" )                                   :wikitext( param.type ) ) Data.lasting = true else local support = Config[ "support4" .. param.type ] s = factory( "doc-param-type-" .. param.type ) if support then s = string.format( "%s", support, s ) end typed:wikitext( s ) end else Data.params[ access ].type = "unknown" typed:addClass( "error" ) :wikitext( "INVALID" ) s = string.format( "params. .type", access ) Fault( factory( "invalid-value" ):gsub( "$1", s )  ) legal = false end else typed = mw.html.create( "td" ) :wikitext( factory( "doc-param-type-unknown" ) ) Data.params[ access ].type = "unknown" if param.default then Data.params[ access ].default = nil Fault( "Default value requires " ) legal = false end end typed:addClass( "navigation-not-searchable" ) -- status if param.required then mode = 1 if param.autovalue then Fault( string.format( "autovalued  required", access ) ) legal = false end if param.default then Fault( string.format( "Defaulted  required", access ) ) legal = false end if param.deprecated then Fault( string.format( "Required deprecated ", access ) ) legal = false end elseif param.deprecated then mode = 4 elseif param.suggested then mode = 2 else mode = 3 end status = ranking[ mode ] ranking = factory( "doc-param-status-" .. status ) if mode == 1 or  mode == 4 then ranking = mw.html.create( "span" ) :css( "font-weight", "bold" ) :wikitext( ranking ) if type( param.deprecated ) == "string" then ranking:tag( "br" ) ranking:wikitext( param.deprecated ) end if param.suggested and  mode == 4 then s = string.format( "Suggesting deprecated ",                               access ) Fault( s ) legal = false end end eager:attr( "data-sort-value", tostring( mode ) ) :node( ranking ) :addClass( string.format( "%sstatus-%s %s", styles, status, "navigation-not-searchable" ) )

--    r:attr( "id",  "templatedata:" .. mw.uri.anchorEncode( access ) ) :css( Permit.css[ status ] ) :addClass( styles .. status ) :node( begin ) :node( code ) :node( desc ) :node( typed ) :node( eager ) :newline if not legal then r:css( "border", "#FF0000 3px solid" ) end return r end -- feature

local function features -- Create ",                                          s ) )                else                    tbl:node( feature( s ) )                end            end -- for i = 1, #Data.order            if leave then                for i = #Data.order, 1, -1 do                    if not Data.order[ i ] then                        table.remove( Data.order, i )                    end                end -- for i = #Data.order, 1, -1            end            Data.tag.paramOrder = Data.order        end        if Config.cssTabWrap or Data.scroll then            r = mw.html.create( "div" )            if type( Config.cssTabWrap ) == "table" then                r:css( Config.cssTabWrap )            elseif type( Config.cssTabWrap ) == "string" then                -- deprecated                r:cssText( Config.cssTabWrap )            end            if Data.scroll then                r:css( "height",   Data.scroll ) :css( "overflow", "auto" ) end r:node( tbl ) else r = tbl end end return r end -- features

local function fellow( any, assigned, at ) -- Check sets[] parameter and issue error message, if necessary -- Parameter: --    any       -- should be number --    assigned  -- parameter name --    at        -- number, of set local s   if type( any ) ~= "number" then s = " ??" Fault( string.format( s,                             at, mw.text.nowiki( tostring( any ) ) ) ) elseif type( assigned ) == "string" then if not Data.got.params[ assigned ] then s = " is undefined" Fault( string.format( s, at, assigned ) ) end else s = " ??" Fault( string.format( s, k,  type( assigned ) ) ) end end -- fellow

local function fellows -- Check sets[] and issue error message, if necessary local s   if type( Data.got.sets ) == "table" then if type( Data.got.params ) == "table" then for k, v in pairs( Data.got.sets ) do               if type( k ) == "number" then if type( v ) == "table" then for ek, ev in pairs( v ) do                           if ek == "label" then s = type( ev ) if s ~= "string" and s ~= "table" then s = " ??" Fault( string.format( s, k ) ) end elseif ek == "params" and type( ev ) == "table" then for pk, pv in pairs( ev ) do                                   fellow( pk, pv, k ) end -- for pk, pv                           else ek = mw.text.nowiki( tostring( ek ) ) s = " ??" Fault( string.format( s, k, ek ) ) end end -- for ek, ev                   else k = mw.text.nowiki( tostring( k ) ) v = mw.text.nowiki( tostring( v ) ) s = string.format( " ??",                                          k, v ) Fault( s ) end else k = mw.text.nowiki( tostring( k ) ) s = string.format( " ?????", k ) Fault( s ) end end -- for k, v       else s = " required for  " Fault( s ) end else s = " needs to be of   type" Fault( s ) end end -- fellows

local function finalize( advance ) -- Wrap presentation into frame -- Parameter: --    advance  -- true, for nice -- Returns string local r, lapsus if Data.div then r = tostring( Data.div ) elseif Data.strip then r = Data.strip else lapsus = true r     = "" end r = r .. failures if Data.source then local live = ( advance or lapsus ) if not live then live = TemplateData.frame:preprocess( "" ) live = ( live == "" ) end if live then r = r .. fancy( advance, lapsus ) end end return r end -- finalize

local function find -- Find JSON data within page source (title) -- Returns string, or nil local s = Data.title:getContent local i, j = s:find( " ", 1, true ) local r   if i then local k = s:find( " ", j, true ) if k then r = mw.text.trim( s:sub( j + 1, k - 1 ) ) end end return r end -- find

local function flat( adjust ) -- Remove formatting from text string for VE   -- Parameter: --    arglist  -- string, to be stripped, or nil -- Returns string, or nil local r   if adjust then r = adjust:gsub( "\n", " " ) if r:find( " ", 1, true ) then r = r:gsub( " .* ", "" ) end if r:find( " ", 1, true ) then r = r:gsub( "", "" ) end if r:find( "''", 1, true ) then r = r:gsub( "'", "" ):gsub( "", "" ) end if r:find( "<", 1, true ) then local Text = Fetch( "Text" ) r = Text.getPlain( r:gsub( "", "\r\n" ) ) end if r:find( "[", 1, true ) then local WLink = Fetch( "WLink" ) if WLink.isBracketedURL( r ) then r = r:gsub( "%[([hf]tt?ps?://%S+) [^%]]+%]", "%1" ) end r = WLink.getPlain( r ) end if r:find( "&", 1, true ) then r = mw.text.decode( r ) if r:find( "&shy;", 1, true ) then r = r:gsub( "&shy;", "" ) end end end return r end -- flat

local function flush -- JSON encode narrowed input; obey unnamed (numerical) parameters -- Returns JSON string local r   if Data.tag then r = mw.text.jsonEncode( Data.tag ):gsub( "%}$", "," ) else r = "{" end r = r .. "\n\"params\":{" if Data.order then local sep = "" local s       for i = 1, #Data.order do            s   = Data.order[ i ] r  = string.format( "%s%s\n%s:%s",                                 r,                                 sep,                                 mw.text.jsonEncode( s ),                                 mw.text.jsonEncode( Data.params[ s ] ) ) sep = ",\n" end -- for i = 1, #Data.order end r = r .. "\n}\n}" return r end -- flush

local function focus( access ) -- Check components; focus multilingual description, build trees -- Parameter: --    access  -- string, name of parameter, nil for root local f = function ( a, at ) local r                   if at then r = string.format( " ", at ) else r = "root" end if a then r = string.format( "%s ", r, a ) end return r               end local parent if access then parent = Data.got.params[ access ] else parent = Data.got end if type( parent ) == "table" then local elem, got, permit, s, scope, slot, tag, target if access then permit = Permit.params if type( access ) == "number" then slot = tostring( access ) else slot = access end else permit = Permit.root end for k, v in pairs( parent ) do           scope = permit[ k ] if scope then s = type( v ) if s == "string" and  k ~= "format" then v = mw.text.trim( v ) end if scope:find( s, 1, true ) then if scope:find( "I18N", 1, true ) then if s == "string" then elem = fair( v ) elseif s == "table" then local translated v, translated = faraway( v ) if v then if translated and k == "description" then elem = { [ 1 ] = fair( v ), [ 2 ] = translated } else elem = fair( v ) end else elem = false end end if type( v ) == "string" then if k == "deprecated" then if v == "1" then v = true elseif v == "0" then v = false end elem = v                           elseif scope:find( "nowiki", 1, true ) then elem = mw.text.nowiki( v ) elem = elem:gsub( "&#13;\n", " " ) v   = v:gsub( string.char( 13 ),  "" ) else v = flat( v ) end elseif s == "boolean" then if scope:find( "boolean", 1, true ) then elem = v                           else s = "Type  bad for " .. f( k, slot ) Fault( s ) end end else if k == "params" and  not access then v   = nil elem = nil elseif k == "format" and  not access then elem = mw.text.decode( v ) v   = nil elseif k == "inherits" then elem = v                           if not Data.heirs then Data.heirs = { } end Data.heirs[ slot ] = v                           v                  = nil elseif k == "style" then elem = v                           v    = nil elseif s == "string" then v   = mw.text.nowiki( v ) elem = v                       else elem = v                       end end if type( elem ) ~= "nil" then if not target then if access then if not Data.tree.params then Data.tree.params = { } end Data.tree.params[ slot ] = { } target = Data.tree.params[ slot ] else Data.tree = { } target   = Data.tree end end target[ k ] = elem elem       = false end if type( v ) ~= "nil" then if not tag then if access then if type( v ) == "string" and v.sub( 1, 1 ) == "=" then v = nil else if not Data.params then Data.params = { } end Data.params[ slot ] = { } tag = Data.params[ slot ] end else Data.tag = { } tag     = Data.tag end end if type( v ) ~= "nil" and k ~= "suggestedvalues" then tag[ k ] = v                       end end else s = string.format( "Type  bad for %s",                                       scope,  f( k, slot ) ) Fault( s ) end else Fault( "Unknown component " .. f( k, slot ) ) end end -- for k, v       if not access  and Data.got.sets then fellows end else Fault( f .. " needs to be of  type" ) end end -- focus

local function format -- Build formatted element -- Returns local source = Data.tree.format:lower local r, s   if source == "inline"  or  source == "block" then r = mw.html.create( "i" ) :wikitext( source ) else local code if source:find( "|", 1, true ) then local scan = "^[\n ]*%{%{[\n _]*|[\n _]*=[\n _]*%}%}[\n ]*$" if source:match( scan ) then code = source:gsub( "\n", "N" ) else s = mw.text.nowiki( source ):gsub( "\n", "&#92;n" ) s = tostring( mw.html.create( "code" )                                    :wikitext( s ) ) Fault( "Invalid format " .. s ) source = false end else local words = mw.text.split( source, "%s+" ) local show, start, support, unknown for i = 1, #words do               s = words[ i ] if i == 1 then start = s               end support = Permit.builder[ s ] if support == start or                   support == "*" then Permit.builder[ s ] = true elseif s:match( "^[1-9]%d?" ) and Permit.builder.align then Permit.builder.align = tonumber( s ) else if unknown then unknown = string.format( "%s %s", unknown, s ) else unknown = s                   end end end -- i = 1, #words if unknown then s = tostring( mw.html.create( "code" )                                    :css( "white-space", "nowrap" )                                     :wikitext( s ) ) Fault( "Unknown/misplaced format keyword " .. s ) source = false start = false end if start == "inline" then if Permit.builder.half == true then show = "inline half" code = "" elseif Permit.builder.grouped == true then show = "inline grouped" code = "" elseif Permit.builder.spaced == true then show = "inline spaced" code = "" end if Permit.builder.newlines == true then show = show or "inline" code = code or "" show = show .. " newlines" code = string.format( "N%sN", code ) end elseif start == "block" then local space = ""     -- amid "|" and name local spaced = " "   -- preceding "=" local spacer = " "   -- following "=" local suffix = "N"   -- closing "}}" on new line show = "block" if Permit.builder.indent == true then start = " " show = "block indent" else start = "" end if Permit.builder.compressed == true then spaced = "" spacer = "" show  = show .. " compressed" if Permit.builder.last == true then show = show .. " last" else suffix = "" end else if Permit.builder.lead == true then show = show .. " lead" space = " " end if type( Permit.builder.align ) ~= "string" then local n                       s = " align" if Permit.builder.align == true then n = 0 if type( Data.got ) == "table" and type( Data.got.params ) == "table" then for k, v in pairs( Data.got.params ) do                                   if type( v ) == "table"  and not v.deprecated and type( k ) == "string" then k = mw.ustring.len( k ) if k > n then n = k                                       end end end -- for k, v                           end else n = Permit.builder.align if type( n ) == "number" and  n > 1 then s = string.format( "%s %d", s, n ) else n = 0   -- How comes? end end if n > 1 then spaced = string.rep( "_", n - 1 )  .. " "                       end show = show .. s                   elseif Permit.builder.after == true then spaced = "" show  = show .. " after" elseif Permit.builder.dense == true then spaced = "" spacer = "" show  = show .. " dense" end if Permit.builder.last == true then suffix = spacer show  = show .. " last" end end code = string.format( "NN",                                     start,                                      space,                                      spaced,                                      spacer,                                      suffix ) if show == "block" then show = "block newlines" end end if show then r = mw.html.create( "span" ) :wikitext( show ) end end if code then source = code:gsub( "N", "\n" ) code  = mw.text.nowiki( code ):gsub( "N", "&#92;n" ) code  = mw.html.create( "code" ) :css( "margin-left", "1em" ) :css( "margin-right", "1em" ) :wikitext( code ) if r then r = mw.html.create( "span" ) :node( r ) :node( code ) else r = code end end end if source and Data.tag then Data.tag.format = source end return r end -- format

local function formatter -- Build presented documentation -- Returns local r = mw.html.create( "div" ) local x = fashioned( Data.tree, true, r ) local s   if x then r = x   end if Data.leading then local toc = mw.html.create( "div" ) local shift if Config.suppressTOCnum then toc:addClass( Config.suppressTOCnum ) if type( Config.stylesTOCnum ) == "string" then local src = Config.stylesTOCnum .. "/styles.css" s = TemplateData.frame:extensionTag( "templatestyles",                                                    nil,                                                     { src = src } ) r:newline :node( s ) end end toc:addClass( "navigation-not-searchable" ) :css( "margin-top", "0.5em" ) :wikitext( "" ) if Data.sibling then local block = mw.html.create( "div" ) if TemplateData.ltr then shift = "right" else shift = "left" end block:css( "float", shift ) :wikitext( Data.sibling ) r:newline :node( block ) :newline end r:newline :node( toc ) :newline if shift then r:node( mw.html.create( "div" )                          :css( "clear", shift ) ) :newline end end s = features if s then if Data.leading then r:node( mw.html.create( "h" .. Config.nested )                          :wikitext( factory( "doc-params" ) ) ) :newline end r:node( s ) end if Data.shared then local global = mw.html.create( "div" ) :attr( "id", "templatedata-global" ) local shift if TemplateData.ltr then shift = "right" else shift = "left" end global:css( "float", shift ) :wikitext( string.format( "%s", Data.shared, "Global" ) ) r:newline :node( global ) end if Data.tree and Data.tree.format then local e = format if e then local show = "Format" if Config.supportFormat then show = string.format( "%s",                                     Config.supportFormat, show ) end r:node( mw.html.create( "p" )                          :addClass( "navigation-not-searchable" )                           :wikitext( show .. ": " )                          :node( e ) ) end end return r end -- formatter

local function free -- Remove JSON comment lines if Data.source:find( "//", 1, true ) then Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([{},\"'])",                         "%1%3" ) end end -- free

local function full -- Build survey table from JSON data, append invisible Data.div = mw.html.create( "div" ) :addClass( "mw-templatedata-doc-wrap" ) if Permit.css.bg then Data.div:css( Permit.css.bg ) end if Permit.css.fg then Data.div:css( Permit.css.fg ) end focus if Data.tag then if type( Data.got.params ) == "table" then for k, v in pairs( Data.got.params ) do               focus( k ) end -- for k, v           if Data.heirs then fathers end end end Data.div:node( formatter ) if not Data.lazy then Data.slim = flush if TemplateData.frame then local div  = mw.html.create( "div" ) local tdata = { [ 1 ] = "templatedata", [ 2 ] = Data.slim } Data.strip = TemplateData.frame:callParserFunction( "#tag",                                                               tdata ) div:wikitext( Data.strip ) if Config.loudly then Data.div:node( mw.html.create( "hr" )                                     :css( { height = "7ex" } ) ) else div:css( "display", "none" ) end Data.div:node( div ) end end if Data.lasting then Fault( "deprecated type syntax" ) end if Data.less then Fault( Config.solo ) end end -- full

local function furnish( adapt, arglist ) -- Analyze transclusion -- Parameter: --    adapt    -- table, #invoke parameters --    arglist  -- table, template parameters -- Returns string local source favorize -- deprecated: for k, v in pairs( Config.basicCnf ) do       if adapt[ k ]  and  adapt[ k ] ~= "" then Config[ v ] = adapt[ k ] end end -- for k, v   if arglist.heading  and  arglist.heading:match( "^[3-6]$" ) then Config.nested = arglist.heading else Config.nested = "2" end Config.loudly = faculty( arglist.debug or adapt.debug ) Data.lazy    = faculty( arglist.lazy )  and  not Config.loudly Data.leading = faculty( arglist.TOC ) if Data.leading and arglist.TOCsibling then Data.sibling = mw.text.trim( arglist.TOCsibling ) end if arglist.lang then Data.slang = arglist.lang:lower elseif adapt.lang then Data.slang = adapt.lang:lower end if arglist.JSON then source = arglist.JSON elseif arglist.Global then source = TemplateData.getGlobalJSON( arglist.Global,                                            arglist.Local ) elseif arglist[ 1 ] then local s    = mw.text.trim( arglist[ 1 ] ) local start = s:sub( 1, 1 ) if start == "<" then Data.strip = s       elseif start == "{" then source = s       elseif mw.ustring.sub( s, 1, 8 ) == mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then Data.strip = s       end end if type( arglist.vertical ) == "string" and arglist.vertical:match( "^%d*%.?%d+[emprx]+$" ) then Data.scroll = arglist.vertical end if not source then Data.title = mw.title.getCurrentTitle source = find if not source and not Data.title.text:match( Config.subpage ) then local s = string.format( Config.suffix,                                    Data.title.prefixedText ) Data.title = mw.title.new( s ) if Data.title.exists then source = find end end end if not Data.lazy then if not Data.title then Data.title = mw.title.getCurrentTitle end Data.lazy = Data.title.text:match( Config.subpage ) end if type( source ) == "string" then TemplateData.getPlainJSON( source ) end return finalize( faculty( arglist.source ) ) end -- furnish

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 -- 2020-08-17   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 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

TemplateData.getGlobalJSON = function ( access, adapt ) -- Retrieve TemplateData from a global repository (JSON) -- Parameter: --    access  -- string, with page specifier (on WikiMedia Commons) --    adapt   -- JSON string or table with local overrides -- Returns true, if succeeded local plugin = Fetch( "/global" ) local r   if type( plugin ) == "table"  and type( plugin.fetch ) == "function" then local s, got = plugin.fetch( access, adapt ) if got then Data.got   = got Data.order = got.paramOrder Data.shared = s           r           = true full else Fault( s ) end end return r end -- TemplateData.getGlobalJSON

TemplateData.getPlainJSON = function ( adapt ) -- Reduce enhanced JSON data to plain text localized JSON -- Parameter: --    adapt  -- string, with enhanced JSON -- Returns string, or not if type( adapt ) == "string" then Data.source = adapt free local lucky lucky, Data.got = pcall( mw.text.jsonDecode, Data.source ) if type( Data.got ) == "table" then full elseif not Data.strip then local scream = type( Data.got ) if scream == "string" then scream = Data.got else scream = "Data.got: " .. scream end Fault( "fatal JSON error: " .. scream ) end end return Data.slim end -- TemplateData.getPlainJSON

TemplateData.test = function ( adapt, arglist ) TemplateData.frame = mw.getCurrentFrame return furnish( adapt, arglist ) end -- TemplateData.test

-- Export local p = { }

p.f = function ( frame ) -- Template call local lucky, r   TemplateData.frame = frame lucky, r = pcall( furnish, frame.args, frame:getParent.args ) if not lucky then Fault( "INTERNAL: " .. r ) r = failures end return r end -- p.f

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

p.TemplateData = function -- Module interface return TemplateData end

return p