Module:Format TemplateData
![]() | This module depends on the following other modules: |
Format TemplateData
– Module with auxilary functions for template documentation, especially by TemplateData.
Core functionality is improved presentation on documentation pages.
Template:Anker Improve template documentation page – MediaWiki disappointing
For presentation of template depiction in VisualEditor agreement was made to abandon all markup and clickable links, permitting all tooltips in all environments. Basically this is reasonable, albeit tooltips with markup and clickable links are supported as HTML application for decades now and JavaScript is present anyway when VisualEditor is used.
- In consequence it was decided, that also presentation on template documentation views never ever is permitted to contain effective links or markup.
- That involved, that on many template documentation pages two separated parameter documentation tables are needed and required to be maintained simultaneously: One plain text version for VisualEditor, and a useful one for complex circumstances, with links and markup and lists and tables. – BTW, VisualEditor has not only tooltips, but also a static GUI view, where the lack of deepening links in parameter description is painful.
This state is indefensible.
Template:Anker Improved presentation
In addition to the simple syntax supported by MediaWiki and presented in the VisualEditor, the following features can be added to the JSON code for the template documentation page. They affect the elements classified as InterfaceText, but are only useful for description fields.
Wikilinks (internal format)
- Using double square brackets pages can be linked as common.
- In VisualEditor, only link title is visible, as it is displayed otherwise.
External links (URL format)
- Open URL are linked as usual by themselves. In VisualEditor they appear as normal text.
- External links enclosed in simple square brackets are displayed normally on the template documentation page. In VisualEditor the title is omitted and the URL is displayed so that the users can c&p it and transfer it to the address field of the browser. There is no other way.
Apostrophs '
for italic and bold
- They can be used to emphasize on the documentation page and are missing in VisualEditor (regular script).
HTML entities
- The following entities can be used:
< > & "
and all numeric formats.
HTML tags
- HTML tags (and the MediaWiki elements that are not replaced in advance) are removed for the VisualEditor. Otherwise, they remain effective.
- Attributes are often included in
"
, which conflicts with the JSON syntax. It is important to make sure that'
is used, which can be a problem with template transclusions.
<noexport>
… </noexport>
- The enclosed areas are not exported to the VisualEditor.
- More complex wiki syntax and extensive explanations can be restricted to the documentation page.
- Within a noexport area, the line structure of the source text is considered. Otherwise everything is running in a single line, as it would also be represented in the VisualEditor.
Templates
- In particular when the template parameter
JSON=
is used, templates can be distributed anywhere in the JSON code. However, the expanded syntax might collide with the JSON syntax.
More effects
- According to the state (required, suggested, optional, deprecated) the table rows are highlighted in light blue, white, gray and pale red.
- When sorting by state, this importance is taken into account and not the alphabetical sequence of the keywords.
- Each parameter can be addressed as a jump destination. The fragment is
#templatedata:
parameter-name. - Missing labels are highlighted as errors.
- A maintenance category is triggered if errors occur.
- If there are no parameters, the element
params:{}
is not required.
Eliminate disadvantages
Two aspects were found to be particularly disturbing in 2013–2017:
- Even if no parameters at all were defined, a table head is always displayed for a table without content. Even more, this is sortable.
- A reduction was rejected with Phabricator. A sortable table of the parameters would be always necessary, even if the table has no rows at all and consists only of the header row.
- This ridiculous statement led to the development of this module in 2016.
- Even if the context does not permit that default values or even AutoValue specifications will be defined ever, a content-free six-line definition list is output for each individual parameter value.
- Phabricator / Phabricator / Phabricator / Phabricator<section end="JSONenhanced" />
- MediaWiki did not even deign to answer the disastrous documentation page situation.
The general comments show that MediaWiki only regards the presentation of TemplateData specifications in the VisualEditor as important. However, someone has to program and maintain the templates and someone needs to generate the template description and make it manageable beside the functionality in the VisualEditor form, but that is beyond ken.
- Two years later the relatively easy task Phabricator has been solved by a community originated patch.
General workflow
- An attempt is made to read the JSON object (string) from passed template parameters.
- If this failed, the source code of the current and the documentation page is searched for
<templatedata>
elements. - Two representations are obtained from the JSON object input:
- A localized version, markup etc. stripped off, in JSON format.
- An HTML structure, basically similar to the MediaWiki representation, possibly with table of the parameters, with enhanced features.
- The result of the template is a visible documentation with markup, followed by a hidden
<templatedata>
element. This is done for the export and corresponds to the MediaWiki guidelines.- If current page has been identified as documentation page the hidden
<templatedata>
is suppressed, and those pages do not appear separately in Special:PagesWithProp/templatedata.
- If current page has been identified as documentation page the hidden
Template:Anker Functions for templates
Details
- f Template:Anker
- Improve TemplateData-presentation; used in Template:Format TemplateData
- Parameters of template transclusion environment (all optional):<section begin="templatePar" />
- 1
- JSON string or
<templatedata>
object - JSON
- JSON string
- (precedes 1)
- Transition from
<templatedata>
objects with pipe symbols needs special attention: Pipes are to be represented as{{!}}
, on double curly brackets one should be encoded by HTML entity. - TOC
1
– Insert table of contents after general purpose descriptions; but before parameter list, if present- Example
- lang
- Language code according to ISO 639 etc.
- lazy
1
– Presentation only, do not generate an effective data block- For general method descriptions.
- debug
1
– developer mode- source
1
– show effective JSON source text (after template expansion) for debugging<section end="templatePar" />
- Parameters of
#invoke
for particular project adaption (all optional):- lang
- Language code according to ISO 639 etc.
- debug
- Development mode, if provided and not equal
0
- cat
- Title of a maintenance category on invalid parameter value etc.
- Deprecated – use configuration module
- docpageCreate
- Pattern for creation of subpage names;
%s/Doku
- Deprecated – use configuration module
- docpageDetect
- Pattern for recognition of subpage names;
/Doku$
- Deprecated – use configuration module
- msgDescMiss
- Localisation: complaint text on missing
description
- Deprecated – use configuration module
- Returns: HTML code; and/or error message, probably with
class="error"
- failsafe Template:Anker
- Version management
Template:Anker Functions for Lua modules (API)
Some functions described above can be used by other modules:
local lucky, TemplateData = pcall( require, "Module:Format TemplateData" )
if type( TemplateData ) == "table" then
TemplateData = TemplateData.TemplateData()
else
-- failure; TemplateData is the error message
return "<span class='error'>" .. TemplateData .. "</span>"
end
- TemplateData.failsafe(atleast)
-
- atleast
optional
nil or minimal version request or"wikidata"
- atleast
- Returns: string or false
- TemplateData.getPlainJSON(adapt)
- Reduce enhanced JSON information to MediaWiki JSON
- adapt
string, with JSON (enhanced)
- adapt
- Returns: string, with JSON (MediaWiki )
- TemplateData.test(adapt, arglist)
- Simulation of template functionality
- adapt
table,#invoke
parameters - arglist
table, template parameters
- adapt
- Returns: string
Usage
Currently focussing on one template only:
Dependencies
Template:Anker Configuration
A local module Module:Format TemplateData/config, if present, facilitates adaptions to the local project.
A table is expected via mw.loadData
. The following entries are optional components:
- catProblem
- Title of a maintenance category on invalid parameter value etc.
- classNoNumTOC
- Name of class for the table of contents; especially to suppress numbering.
nonumtoc
- classTable
- table with classes for the table of parameters.
{ "wikitable" }
- cssParams
- table with CSS assignments for formatting of single parameters
- cssParWrap
- table with CSS assignments for formatting of the entire parameter table
- docpageCreate
- Pattern for creation of subpage names;
%s/Doku
%s/Doku
- docpageDetect
- Pattern for recognition of subpage names;
/Doku$
/Doku$
- help*********
- Link targets for context sensitive help on details
- helpBoolean
- helpContent
- helpDate
- helpFile
- helpFormat
- Link target on help about wikitext transclusion layout
- helpLine
- helpNumber
- helpPage
- helpString
- helpTemplate
- helpURL
- helpUser
- msgDescMiss
- Localisation: complaint text on missing
description
- permit
- table with specification of properties for a single parameter; components:
- boole
- table with specification for boolean presentation
- Two components
true
andfalse
– each one table:- css
- table with CSS for this explanation of the value
- lead
true
– show explanation for0
or1
respectively preceding the valuefalse
– show explanation for0
or1
respectively following the value- show
- explanation; string or
false
to suppress
- css
- table with specifications for rendering of the parameter table; components:
- tablehead
- table with CSS for table head
- required
- table with CSS for
required
- suggested
- table with CSS for
suggested
- optional
- table with CSS for
optional
- deprecated
- table with CSS for
deprecated
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, -- <div class="mw-templatedata-doc-wrap"> 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 <templatedata> 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, -- <templatedata> 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'''<br />" .. Data.scream ) if Config.strange then r = string.format( "%s[[category:%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 = "<noexport>", stop = "</noexport>" }, { start = "<exportonly>", stop = "</exportonly>", 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( "<exportonly>.*</exportonly>", "" ) 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 <code>lang=%s</code>", "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 -- <block>, if to be equipped -- Returns <block>, 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 <tr> 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 | %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 = "☐" }, [true] = { css = boole, lead = true, show = "☑" } } 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 <ul> -- 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|%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|%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( "[[%s|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 <tr> 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.<code>%s</code>", 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.<code>%s</code>.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|%s]]", support, s ) end typed:wikitext( s ) end else Data.params[ access ].type = "unknown" typed:addClass( "error" ) :wikitext( "INVALID" ) s = string.format( "params.<code>%s</code>.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 <code>type</code>" ) legal = false end end typed:addClass( "navigation-not-searchable" ) -- status if param.required then mode = 1 if param.autovalue then Fault( string.format( "autovalued <code>%s</code> required", access ) ) legal = false end if param.default then Fault( string.format( "Defaulted <code>%s</code> required", access ) ) legal = false end if param.deprecated then Fault( string.format( "Required deprecated <code>%s</code>", 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 <code>%s</code>", 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" ) ) -- <tr> 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 <table> for parameters -- Returns <table>, or nil local r if Data.tree and Data.tree.params then local tbl = mw.html.create( "table" ) local tr = mw.html.create( "tr" ) feat() if Data.order and #Data.order > 1 then tbl:addClass( "sortable" ) end if type( Config.classTable ) == "table" then for k, v in pairs( Config.classTable ) do tbl:addClass( v ) end -- for k, v end if type( Config.cssTable ) == "table" then tbl:css( Config.cssTable ) end tr:addClass( "navigation-not-searchable" ) :node( mw.html.create( "th" ) :attr( "colspan", "2" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-name" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-desc" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-type" ) ) ) :node( mw.html.create( "th" ) :css( Permit.css.tablehead ) :wikitext( factory( "doc-param-status" ) ) ) tbl:newline() -- :node( mw.html.create( "thead" ) :node( tr ) -- ) :newline() if Data.order then local leave, s for i = 1, #Data.order do s = Data.order[ i ] if s:sub( 1, 1 ) == "=" then leave = true tbl:node( fatten( s ) ) Data.order[ i ] = false elseif s:match( "[=|]" ) then Fault( string.format( "Bad param <code>%s</code>", 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 = "<code>sets[%d].params[%s]</code>??" Fault( string.format( s, at, mw.text.nowiki( tostring( any ) ) ) ) elseif type( assigned ) == "string" then if not Data.got.params[ assigned ] then s = "<code>sets[%d].params %s</code> is undefined" Fault( string.format( s, at, assigned ) ) end else s = "<code>sets[%d].params[%d] = %s</code>??" 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 = "<code>sets[%d].label</code>??" 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 = "<code>sets[%d][%s]</code>??" 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( "<code>sets[%s][%s]</code>??", k, v ) Fault( s ) end else k = mw.text.nowiki( tostring( k ) ) s = string.format( "<code>sets[%s]</code> ?????", k ) Fault( s ) end end -- for k, v else s = "<code>params</code> required for <code>sets</code>" Fault( s ) end else s = "<code>sets</code> needs to be of <code>object</code> 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( "{{REVISIONID}}" ) 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( "<templatedata>", 1, true ) local r if i then local k = s:find( "</templatedata>", 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( "<noexport>", 1, true ) then r = r:gsub( "<noexport>.*</noexport>", "" ) end if r:find( "<exportonly>", 1, true ) then r = r:gsub( "</?exportonly>", "" ) 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( "<br */?>", "\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( "­", 1, true ) then r = r:gsub( "­", "" ) end end end return r end -- flat() local function flush() -- JSON encode narrowed input; obey unnamed (numerical) parameters -- Returns <templatedata> 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( "<code>params.%s</code>", at ) else r = "''root''" end if a then r = string.format( "%s<code>.%s</code>", 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( " \n", "<br>" ) 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 <code>boolean</code> 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 <code>%s</code> 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 <code>object</code> type" ) end end -- focus() local function format() -- Build formatted element -- Returns <inline> 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", "\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( "N{{_N%s|%s_%s=%s_%s}}N", 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", "\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 <div> 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( "__TOC__" ) 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|%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|%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 <templatedata> 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