Module:Format TemplateData: Difference between revisions
From Zoophilia Wiki
Jump to navigationJump to search
de>PerfektesChaos 2017-05-24 |
m 38 revisions imported |
||
(20 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
local TemplateData = { serial = "2017- | local TemplateData = { serial = "2017-11-06", | ||
suite = "TemplateData" } | suite = "TemplateData" } | ||
local plaintext = require("Module:Plain text") | |||
--[=[ | --[=[ | ||
improve template:TemplateData | improve template:TemplateData | ||
]=] | ]=] | ||
local Config = { | local Config = { | ||
-- multiple #invoke option names mapped into unique internal fields | -- multiple #invoke option names mapped into unique internal fields | ||
cat | cat = "strange", | ||
cssParams = " | classNoNumTOC = "suppressTOCnum", | ||
debug | -- classParams = "classTable", | ||
docpageCreate = "suffix", | cssParams = "stylesTable", | ||
docpageDetect = "subpage", | cssParWrap = "stylesTabWrap", | ||
msgDescMiss | debug = false, | ||
loudly | docpageCreate = "suffix", | ||
solo | docpageDetect = "subpage", | ||
strange | msgDescMiss = "solo", | ||
stylesTable | -- classTable = false, -- class for params table | ||
subpage | loudly = false, -- show exported element, etc. | ||
suffix | solo = false, -- complaint on missing description | ||
strange = false, -- title of maintenance category | |||
stylesTable = false, -- styles for params table | |||
stylesTabWrap = false, -- styles for params table wrapper | |||
subpage = false, -- pattern to identify subpage | |||
suffix = false, -- subpage creation scheme | |||
suppressTOCnum = false -- class for TOC number suppression | |||
} | } | ||
local Data = { | local Data = { | ||
Line 43: | Line 47: | ||
} | } | ||
local Permit = { | local Permit = { | ||
styles = { required = "border-left: 3px solid black;", | |||
suggested = " | suggested = "border-left: 3px solid #888;", | ||
optional = " | optional = "border-left: 3px solid #ccc", | ||
deprecated = " | deprecated = "border-left: 3px dotted red; background-color: #FDD;", | ||
tableheadbg = "B3B7FF" }, | tableheadbg = "background-color: #B3B7FF;" }, | ||
params = { aliases = "table", | params = { aliases = "table", | ||
autovalue = "string", | autovalue = "string", | ||
Line 84: | Line 88: | ||
} | } | ||
-- | |||
-- Generic utility functions | |||
-- | |||
local function Fault( alert ) | local function Fault( alert ) | ||
Line 91: | Line 97: | ||
-- alert -- string, error message | -- alert -- string, error message | ||
if Data.scream then | if Data.scream then | ||
string.format( "%s *** %s", Data.scream, alert ) | Data.scream = string.format( "%s *** %s", Data.scream, alert ) | ||
else | else | ||
Data.scream = alert | Data.scream = alert | ||
Line 97: | Line 103: | ||
end -- Fault() | end -- Fault() | ||
local function collapseWhitespace ( a ) | |||
-- Collapses whitespace, HTML style. | |||
return a:gsub( "%s*\n%s*", " " ) | |||
:gsub( "%s%s+", " " ) | |||
end -- collapseWhitespace | |||
-------------------------------------------------------------------------------- | |||
-- | |||
local function facet( ask, at ) | local function facet( ask, at ) | ||
Line 158: | Line 142: | ||
local function | local function getLocalizedText( adapt ) | ||
-- Retrieve localized text from system message | -- Retrieve localized text from system message | ||
-- Parameter: | -- Parameter: | ||
Line 164: | Line 148: | ||
-- Returns string, with localized text | -- Returns string, with localized text | ||
return mw.message.new( "templatedata-" .. adapt ):plain() | return mw.message.new( "templatedata-" .. adapt ):plain() | ||
end -- | end -- getLocalizedText() | ||
Line 210: | Line 194: | ||
local function | local function handleNoexportWhitespace( adjust ) | ||
-- | -- Reduces runs of spaces, including newlines, to a single space, so the | ||
-- whole string is on one line. <noexport> blocks are left alone, but the | |||
-- <noexport> tags themselves are removed. | |||
-- adjust -- string | -- adjust -- string | ||
-- Returns string, with adjusted text | -- Returns string, with adjusted text | ||
local r | local r | ||
if adjust:find( "<noexport>", 1, true ) then | if adjust:find( "<noexport>", 1, true ) then | ||
Line 225: | Line 207: | ||
while j do | while j do | ||
if j > 1 then | if j > 1 then | ||
r = r .. | r = r .. collapseWhitespace( adjust:sub( i, j - 1 ) ) | ||
end | end | ||
i = k + 1 | i = k + 1 | ||
Line 239: | Line 221: | ||
r = r .. adjust:sub( i ) | r = r .. adjust:sub( i ) | ||
else | else | ||
r = | r = collapseWhitespace( adjust ) | ||
end | end | ||
return r | return r | ||
end -- | end -- handleNoexportWhitespace() | ||
Line 316: | Line 298: | ||
end | end | ||
if r2 then | if r2 then | ||
for k, v in pairs( r2 ) do | for k, v in pairs( r2 ) do | ||
if v | if v then | ||
local baseCode = k:match( "^%s*(%a%a%a?)-?%a*%s*$" ) | |||
if not baseCode or not mw.language.isKnownLanguageTag( baseCode ) then | |||
Fault( string.format( "Invalid <code>lang=%s</code>", k )) | |||
end | |||
end | end | ||
end -- for k, v | end -- for k, v | ||
Line 393: | Line 376: | ||
-- asked -- true, if mandatory description | -- asked -- true, if mandatory description | ||
-- Returns <block>, with head, or nil | -- Returns <block>, with head, or nil | ||
local para = mw.html.create( " | local para = mw.html.create( "div" ) | ||
local plus, r | local plus, r | ||
if about and about.description then | if about and about.description then | ||
Line 410: | Line 393: | ||
:wikitext( k ) ) | :wikitext( k ) ) | ||
:node( mw.html.create( "br" ) ) | :node( mw.html.create( "br" ) ) | ||
:wikitext( | :wikitext( handleNoexportWhitespace( v ) ) ) | ||
end -- for k, v | end -- for k, v | ||
end | end | ||
Line 554: | Line 537: | ||
if lapsus then | if lapsus then | ||
s = string.format( "params.<code>%s</code>.aliases", access ) | s = string.format( "params.<code>%s</code>.aliases", access ) | ||
Fault( | Fault( getLocalizedText( "invalid-value" ):gsub( "$1", s ) ) | ||
legal = false | legal = false | ||
end | end | ||
Line 567: | Line 550: | ||
local details = { "default", "example", "autovalue" } | local details = { "default", "example", "autovalue" } | ||
local dl = mw.html.create( "dl" ) | local dl = mw.html.create( "dl" ) | ||
local section, show | local dd, section, show | ||
for i = 1, #details do | for i = 1, #details do | ||
s = details[ i ] | s = details[ i ] | ||
show = param[ s ] | show = param[ s ] | ||
if show then | if show then | ||
section = | section = getLocalizedText( "doc-param-" .. s ) | ||
dt = mw.html.create( "dt" ):wikitext( section ) | |||
dd = mw.html.create( "dd" ) | |||
if (string.len(show) < 80) then | |||
dt:cssText("float: left;margin-right: 1.6em;") | |||
end | |||
if param.type == "boolean" then | |||
if show == "0" then | |||
dd:wikitext("<span style=\"color: #610; font-weight: bold;\">False</span> (<code>0</code>)") | |||
elseif show == "1" then | |||
dd:wikitext("<span style=\"color: #050; font-weight: bold;\">True</span> (<code>1</code>)") | |||
else | |||
dd:wikitext( show ) | |||
end | |||
else | |||
dd:wikitext( show ) | |||
end | |||
dl:node( dt ) | |||
:node( dd ) | |||
end | end | ||
end -- i = 1, #details | end -- i = 1, #details | ||
Line 589: | Line 586: | ||
if s == "string" then | if s == "string" then | ||
Data.params[ access ].type = s | Data.params[ access ].type = s | ||
typed:wikitext( | typed:wikitext( getLocalizedText( "doc-param-type-" .. s ) ) | ||
:tag( "br" ) | :tag( "br" ) | ||
typed:node( mw.html.create( "span" ) | typed:node( mw.html.create( "span" ) | ||
Line 596: | Line 593: | ||
Data.lasting = true | Data.lasting = true | ||
else | else | ||
s = | s = getLocalizedText( "doc-param-type-" .. param.type ) | ||
typed:wikitext( s ) | typed:wikitext( s ) | ||
end | end | ||
Line 604: | Line 601: | ||
:wikitext( "INVALID" ) | :wikitext( "INVALID" ) | ||
s = string.format( "params.<code>%s</code>.type", access ) | s = string.format( "params.<code>%s</code>.type", access ) | ||
Fault( | Fault( getLocalizedText( "invalid-value" ):gsub( "$1", s ) ) | ||
legal = false | legal = false | ||
end | end | ||
else | else | ||
typed = mw.html.create( "td" ) | typed = mw.html.create( "td" ) | ||
:wikitext( | :wikitext( getLocalizedText( "doc-param-type-unknown" ) ) | ||
end | end | ||
Line 628: | Line 625: | ||
end | end | ||
status = ranking[ mode ] | status = ranking[ mode ] | ||
ranking = | ranking = getLocalizedText( "doc-param-status-" .. status ) | ||
if mode == 1 or mode == 4 then | if mode == 1 or mode == 4 then | ||
ranking = mw.html.create( "span" ) | ranking = mw.html.create( "span" ) | ||
Line 641: | Line 638: | ||
-- <tr> | -- <tr> | ||
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) | r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) ) | ||
: | :cssText( Permit.styles[ status ] ) | ||
:node( begin ) | :node( begin ) | ||
:node( code ) | :node( code ) | ||
Line 663: | Line 660: | ||
local r | local r | ||
if Data.tree and Data.tree.params then | if Data.tree and Data.tree.params then | ||
local style = | local style = Permit.styles.tableheadbg | ||
local tr = mw.html.create( "tr" ) | local tbl = mw.html.create( "table" ) | ||
:addClass( "wikitable" ) | |||
local tr = mw.html.create( "tr" ) | |||
feat() | feat() | ||
if Data.order and #Data.order > 1 then | |||
tbl:addClass( "sortable" ) | |||
end | |||
-- if Config.classTable then | |||
-- tbl:addClass( Config.classTable ) | |||
-- end | |||
if Config.stylesTable then | |||
tbl:cssText( Config.stylesTable ) | |||
end | |||
tr:node( mw.html.create( "th" ) | tr:node( mw.html.create( "th" ) | ||
:attr( "colspan", "2" ) | :attr( "colspan", "2" ) | ||
: | :cssText( style ) | ||
:wikitext( | :wikitext( getLocalizedText( "doc-param-name" ) ) ) | ||
:node( mw.html.create( "th" ) | :node( mw.html.create( "th" ) | ||
: | :cssText( style ) | ||
:wikitext( | :wikitext( getLocalizedText( "doc-param-desc" ) ) ) | ||
:node( mw.html.create( "th" ) | :node( mw.html.create( "th" ) | ||
: | :cssText( style ) | ||
:wikitext( | :wikitext( getLocalizedText( "doc-param-type" ) ) ) | ||
:node( mw.html.create( "th" ) | :node( mw.html.create( "th" ) | ||
: | :cssText( style ) | ||
:wikitext( | :wikitext( getLocalizedText( "doc-param-status" ) ) ) | ||
tbl:newline() | |||
-- :node( mw.html.create( "thead" ) | |||
:node( tr ) | |||
-- | |||
-- ) | -- ) | ||
:newline() | |||
if Data.order then | if Data.order then | ||
for i = 1, #Data.order do | for i = 1, #Data.order do | ||
tbl:node( feature( Data.order[ i ] ) ) | |||
end -- for i = 1, #Data.order | end -- for i = 1, #Data.order | ||
end | |||
if Config.stylesTabWrap then | |||
r = mw.html.create( "div" ) | |||
:cssText( Config.stylesTabWrap ) | |||
:node( tbl ) | |||
else | |||
r = tbl | |||
end | end | ||
end | end | ||
Line 747: | Line 754: | ||
r = r:gsub( "<noexport>(.*)</noexport>", "" ) | r = r:gsub( "<noexport>(.*)</noexport>", "" ) | ||
end | end | ||
r = plaintext._main(r) | |||
if r:find( "&", 1, true ) then | if r:find( "&", 1, true ) then | ||
r = mw.text.decode( r ) | r = mw.text.decode( r ) | ||
Line 843: | Line 837: | ||
if scope:find( "I18N", 1, true ) then | if scope:find( "I18N", 1, true ) then | ||
if s == "string" then | if s == "string" then | ||
elem = | elem = handleNoexportWhitespace( v ) | ||
else | else | ||
local translated | local translated | ||
Line 850: | Line 844: | ||
if translated and | if translated and | ||
k == "description" then | k == "description" then | ||
elem = { [ 1 ] = | elem = { [ 1 ] = handleNoexportWhitespace( v ), | ||
[ 2 ] = translated } | [ 2 ] = translated } | ||
else | else | ||
elem = | elem = handleNoexportWhitespace( v ) | ||
end | end | ||
else | else | ||
Line 943: | Line 937: | ||
end | end | ||
if Data.leading then | if Data.leading then | ||
local toc = mw.html.create( "div" ) | |||
if Config.suppressTOCnum then | |||
toc:addClass( Config.suppressTOCnum ) | |||
end | |||
toc:css( "margin-top", "0.5em" ) | |||
:wikitext( "__TOC__" ) | |||
r:newline() | r:newline() | ||
: | :node( toc ) | ||
:newline() | :newline() | ||
end | end | ||
Line 951: | Line 951: | ||
if Data.leading then | if Data.leading then | ||
r:node( mw.html.create( "h2" ) | r:node( mw.html.create( "h2" ) | ||
:wikitext( getLocalizedText( "doc-params" ) ) ) | |||
:newline() | :newline() | ||
end | end | ||
Line 983: | Line 983: | ||
local function full() | local function full() | ||
-- Build | -- Build HTML table for display from JSON data, and append an invisible | ||
-- <templatedata> block. | |||
Data.div = mw.html.create( "div" ) | Data.div = mw.html.create( "div" ) | ||
:addClass( "mw-templatedata-doc-wrap" ) | :addClass( "mw-templatedata-doc-wrap" ) | ||
Line 1,008: | Line 1,009: | ||
div:wikitext( Data.strip ) | div:wikitext( Data.strip ) | ||
if Config.loudly then | if Config.loudly then | ||
-- Display raw templatedata table all the time. | |||
Data.div:node( mw.html.create( "hr" ) ) | Data.div:node( mw.html.create( "hr" ) ) | ||
Data.div:node( div ) | |||
else | else | ||
-- Creates an expand link to check raw templatedata table. | |||
local wrapper = mw.html.create( "div" ) | |||
wrapper:addClass( "mw-collapsible" ) | |||
wrapper:addClass( "mw-collapsed" ) | |||
wrapper:css( "font-size", "85%" ) | |||
div:addClass( "mw-collapsible-content" ) | |||
wrapper:wikitext( "'''Test of raw TemplateData output''': " ) | |||
wrapper:node( div ) | |||
Data.div:node( wrapper ) | |||
end | end | ||
end | end | ||
end | end | ||
Line 1,020: | Line 1,030: | ||
local function furnish( adapt, arglist ) | local function furnish( adapt, arglist ) | ||
-- | -- Called by f, this function is the first to do any real work when the | ||
-- module is invoked. | |||
-- Parameter: | -- Parameter: | ||
-- adapt -- table, #invoke parameters | -- adapt -- table, #invoke parameters | ||
-- arglist -- table, template parameters | -- arglist -- table, template parameters | ||
-- Returns string | -- Returns string | ||
local spy="" | --local spy="" | ||
local source | local source | ||
for k, v in pairs( Config ) do | for k, v in pairs( Config ) do | ||
Line 1,033: | Line 1,044: | ||
end -- for k, v | end -- for k, v | ||
Config.loudly = faculty( arglist.debug or adapt.debug ) | Config.loudly = faculty( arglist.debug or adapt.debug ) | ||
if mw.site.server:find( "//de.wikipedia.beta.wmflabs.org", 1, true ) then | --if mw.site.server:find( "//de.wikipedia.beta.wmflabs.org", 1, true ) then | ||
-- Config.loudly = true | |||
end | --end | ||
Data.lazy = faculty( arglist.lazy ) and not Config.loudly | Data.lazy = faculty( arglist.lazy ) and not Config.loudly | ||
Data.leading = faculty( arglist.TOC ) | Data.leading = faculty( arglist.TOC ) | ||
Line 1,048: | Line 1,059: | ||
source = s | source = s | ||
elseif mw.ustring.sub( s, 1, 8 ) == | elseif mw.ustring.sub( s, 1, 8 ) == | ||
mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then | mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then -- <DEL> ' " ` U N I Q | ||
Data.strip = s | Data.strip = s | ||
end | end | ||
Line 1,079: | Line 1,090: | ||
end | end | ||
TemplateData.getPlainJSON( source ) | TemplateData.getPlainJSON( source ) | ||
return spy .. | return finalize() | ||
--return spy .. finalize() | |||
end -- furnish() | end -- furnish() | ||
Line 1,085: | Line 1,097: | ||
TemplateData.failsafe = function ( assert ) | TemplateData.failsafe = function ( assert ) | ||
-- Checks the age of this implementation against some minimum ("assert"). | |||
local r | local r | ||
if not assert or assert <= TemplateData.serial then | if not assert or assert <= TemplateData.serial then | ||
Line 1,133: | Line 1,146: | ||
p.f = function ( frame ) | p.f = function ( frame ) | ||
-- | -- The entry point for templates invoking the module. | ||
local lucky, | -- Just wraps furnish in an exception handler. | ||
local lucky, result | |||
TemplateData.frame = frame | TemplateData.frame = frame | ||
lucky, | lucky, result = pcall( furnish, frame.args, frame:getParent().args ) | ||
if not lucky then | if not lucky then | ||
Fault( "INTERNAL: " .. | Fault( "INTERNAL: " .. result ) | ||
result = failures() | |||
end | end | ||
return | return result | ||
end -- p.f() | end -- p.f() | ||
Latest revision as of 13:18, 3 September 2020
Documentation for this module may be created at Module:Format TemplateData/doc
local TemplateData = { serial = "2017-11-06",
suite = "TemplateData" }
local plaintext = require("Module:Plain text")
--[=[
improve template:TemplateData
]=]
local Config = {
-- multiple #invoke option names mapped into unique internal fields
cat = "strange",
classNoNumTOC = "suppressTOCnum",
-- classParams = "classTable",
cssParams = "stylesTable",
cssParWrap = "stylesTabWrap",
debug = false,
docpageCreate = "suffix",
docpageDetect = "subpage",
msgDescMiss = "solo",
-- classTable = false, -- class for params table
loudly = false, -- show exported element, etc.
solo = false, -- complaint on missing description
strange = false, -- title of maintenance category
stylesTable = false, -- styles for params table
stylesTabWrap = false, -- styles for params table wrapper
subpage = false, -- pattern to identify subpage
suffix = false, -- subpage creation scheme
suppressTOCnum = false -- class for TOC number suppression
}
local Data = {
div = false, -- <div class="mw-templatedata-doc-wrap">
got = false, -- table, initial templatedata object
heirs = false, -- table, params that are inherited
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
slang = false, -- project 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 = {
styles = { required = "border-left: 3px solid black;",
suggested = "border-left: 3px solid #888;",
optional = "border-left: 3px solid #ccc",
deprecated = "border-left: 3px dotted red; background-color: #FDD;",
tableheadbg = "background-color: #B3B7FF;" },
params = { aliases = "table",
autovalue = "string",
default = "string table I18N nowiki",
deprecated = "boolean string",
description = "string table I18N",
example = "string table I18N nowiki",
label = "string table I18N",
inherits = "string",
required = "boolean",
suggested = "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" }
}
--
-- Generic utility functions
--
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 collapseWhitespace ( a )
-- Collapses whitespace, HTML style.
return a:gsub( "%s*\n%s*", " " )
:gsub( "%s%s+", " " )
end -- collapseWhitespace
--------------------------------------------------------------------------------
--
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 = Data.source:find( seek, at )
local r, slice, source
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 getLocalizedText( adapt )
-- Retrieve localized text from system message
-- Parameter:
-- adapt -- string, message ID after "templatedata-"
-- Returns string, with localized text
return mw.message.new( "templatedata-" .. adapt ):plain()
end -- getLocalizedText()
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 handleNoexportWhitespace( adjust )
-- Reduces runs of spaces, including newlines, to a single space, so the
-- whole string is on one line. <noexport> blocks are left alone, but the
-- <noexport> tags themselves are removed.
-- adjust -- string
-- Returns string, with adjusted text
local r
if adjust:find( "<noexport>", 1, true ) then
local i = 1
local j, k = adjust:find( "<noexport>", i, true )
r = ""
while j do
if j > 1 then
r = r .. collapseWhitespace( adjust:sub( i, j - 1 ) )
end
i = k + 1
j, k = adjust:find( "</noexport>", i, true )
if j then
r = r .. adjust:sub( i, j - 1 )
i = k + 1
j, k = adjust:find( "<noexport>", i, true )
else
Fault( "missing </noexport>" )
end
end -- while j
r = r .. adjust:sub( i )
else
r = collapseWhitespace( adjust )
end
return r
end -- handleNoexportWhitespace()
local function faraway( alternatives )
-- Retrieve project 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
if not Data.slang then
Data.slang = mw.language.getContentLanguage():getCode()
end
for k, v in pairs( alternatives ) do
if type( v ) == "string" then
v = mw.text.trim( v )
if v ~= "" then
variants[ k ] = v
n = n + 1
end
end
end -- for k, v
if n > 0 then
for k, v in pairs( variants ) do
if v then
if n == 1 then
r1 = v
elseif k:lower() == Data.slang then
variants[ k ] = nil
r1 = v
r2 = variants
break -- for k, v
end
end
end -- for k, v
if not r1 then
local seek = string.format( "^%s-", Data.slang )
for k, v in pairs( variants ) do
if v and k:lower():match( seek ) then
variants[ k ] = nil
r1 = v
r2 = variants
break -- for k, v
end
end -- for k, v
if not r1 then
local others = mw.language.getFallbacksFor( slang )
table.insert( others, "en" )
for i = 1, #others do
seek = others[ i ]
if variants[ seek ] then
r1 = variants[ seek ]
variants[ seek ] = nil
r2 = variants
break -- for i
end
end -- i = 1, #others
end
if not r1 then
for k, v in pairs( variants ) do
if v then
variants[ k ] = nil
r1 = v
r2 = variants
break -- for k, v
end
end -- for k, v
end
end
if r2 then
for k, v in pairs( r2 ) do
if v then
local baseCode = k:match( "^%s*(%a%a%a?)-?%a*%s*$" )
if not baseCode or not mw.language.isKnownLanguageTag( baseCode ) then
Fault( string.format( "Invalid <code>lang=%s</code>", k ))
end
end
end -- for k, v
end
end
return r1, r2
end -- faraway()
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
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 = { }
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
end
end -- for k, v
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 feasible( about, asked )
-- Create description head
-- Parameter:
-- about -- table, supposed to contain description
-- asked -- true, if mandatory description
-- Returns <block>, with head, or nil
local para = 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" )
if not Config.loudly then
plus:addClass( "templatedata-maintain" )
:css( "display", "none" )
end
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( handleNoexportWhitespace( v ) ) )
end -- for k, v
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 -- 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 = { }
for k, v in pairs( Data.tree.params ) do
i = facet( k, 1 )
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 legal = true
local param = Data.tree.params[ access ]
local ranking = { "required", "suggested", "optional", "deprecated" }
local r = mw.html.create( "tr" )
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" )
: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
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" ) )
end
code:wikitext( s )
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( getLocalizedText( "invalid-value" ):gsub( "$1", s ) )
legal = false
end
end
-- description etc.
s = feasible( param )
if s then
desc:node( s )
end
if param.default or param.example or param.autovalue then
local details = { "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
section = getLocalizedText( "doc-param-" .. s )
dt = mw.html.create( "dt" ):wikitext( section )
dd = mw.html.create( "dd" )
if (string.len(show) < 80) then
dt:cssText("float: left;margin-right: 1.6em;")
end
if param.type == "boolean" then
if show == "0" then
dd:wikitext("<span style=\"color: #610; font-weight: bold;\">False</span> (<code>0</code>)")
elseif show == "1" then
dd:wikitext("<span style=\"color: #050; font-weight: bold;\">True</span> (<code>1</code>)")
else
dd:wikitext( show )
end
else
dd:wikitext( show )
end
dl:node( dt )
:node( dd )
end
end -- i = 1, #details
desc:node( dl )
end
-- type
if param.type then
s = Permit.types[ param.type ]
typed = mw.html.create( "td" )
if s then
if s == "string" then
Data.params[ access ].type = s
typed:wikitext( getLocalizedText( "doc-param-type-" .. s ) )
:tag( "br" )
typed:node( mw.html.create( "span" )
:addClass( "error" )
:wikitext( param.type ) )
Data.lasting = true
else
s = getLocalizedText( "doc-param-type-" .. param.type )
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( getLocalizedText( "invalid-value" ):gsub( "$1", s ) )
legal = false
end
else
typed = mw.html.create( "td" )
:wikitext( getLocalizedText( "doc-param-type-unknown" ) )
end
-- status
if param.required then
mode = 1
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 = getLocalizedText( "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
end
-- <tr>
r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) )
:cssText( Permit.styles[ status ] )
:node( begin )
:node( code )
:node( desc )
:node( typed )
:node( mw.html.create( "td" )
:attr( "data-sort-value", tostring( mode ) )
:node( ranking ) )
: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 style = Permit.styles.tableheadbg
local tbl = mw.html.create( "table" )
:addClass( "wikitable" )
local tr = mw.html.create( "tr" )
feat()
if Data.order and #Data.order > 1 then
tbl:addClass( "sortable" )
end
-- if Config.classTable then
-- tbl:addClass( Config.classTable )
-- end
if Config.stylesTable then
tbl:cssText( Config.stylesTable )
end
tr:node( mw.html.create( "th" )
:attr( "colspan", "2" )
:cssText( style )
:wikitext( getLocalizedText( "doc-param-name" ) ) )
:node( mw.html.create( "th" )
:cssText( style )
:wikitext( getLocalizedText( "doc-param-desc" ) ) )
:node( mw.html.create( "th" )
:cssText( style )
:wikitext( getLocalizedText( "doc-param-type" ) ) )
:node( mw.html.create( "th" )
:cssText( style )
:wikitext( getLocalizedText( "doc-param-status" ) ) )
tbl:newline()
-- :node( mw.html.create( "thead" )
:node( tr )
-- )
:newline()
if Data.order then
for i = 1, #Data.order do
tbl:node( feature( Data.order[ i ] ) )
end -- for i = 1, #Data.order
end
if Config.stylesTabWrap then
r = mw.html.create( "div" )
:cssText( Config.stylesTabWrap )
:node( tbl )
else
r = tbl
end
end
return r
end -- features()
local function finalize()
-- Wrap presentation into frame
-- Returns string
local r
if Data.div then
r = tostring( Data.div )
elseif Data.strip then
r = Data.strip
else
r = ""
end
return r .. failures()
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
-- 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
r = plaintext._main(r)
if r:find( "&", 1, true ) then
r = mw.text.decode( r )
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" 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 = handleNoexportWhitespace( v )
else
local translated
v, translated = faraway( v )
if v then
if translated and
k == "description" then
elem = { [ 1 ] = handleNoexportWhitespace( v ),
[ 2 ] = translated }
else
elem = handleNoexportWhitespace( v )
end
else
elem = false
end
end
if v then
if scope:find( "nowiki", 1, true ) then
elem = mw.text.nowiki( v )
else
v = flat( v )
end
end
else
if k == "params" and not access then
v = nil
elem = nil
elseif k == "format" and not access then
v = mw.text.decode( v )
elem = v
elseif k == "inherits" then
elem = v
if not Data.heirs then
Data.heirs = { }
end
Data.heirs[ slot ] = 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 not Data.params then
Data.params = { }
end
Data.params[ slot ] = { }
tag = Data.params[ slot ]
else
Data.tag = { }
tag = Data.tag
end
end
tag[ k ] = v
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
else
Fault( f() .. " needs to be of <code>object</code> type" )
end
end -- focus()
local function format()
-- Build presented documentation
-- Returns <div>
local r = mw.html.create( "div" )
local s = feasible( Data.tree, true )
if s then
r:node( s )
end
if Data.leading then
local toc = mw.html.create( "div" )
if Config.suppressTOCnum then
toc:addClass( Config.suppressTOCnum )
end
toc:css( "margin-top", "0.5em" )
:wikitext( "__TOC__" )
r:newline()
:node( toc )
:newline()
end
s = features()
if s then
if Data.leading then
r:node( mw.html.create( "h2" )
:wikitext( getLocalizedText( "doc-params" ) ) )
:newline()
end
r:node( s )
end
if Data.tree and Data.tree.format then
local e, style
s = Data.tree.format:lower( Data.tree.format )
if s == "inline" or s == "block" then
style = "i"
else
style = "code"
end
r:node( mw.html.create( "p" )
:wikitext( "Format: " )
:node( mw.html.create( style )
:wikitext( s ) ) )
end
return r
end -- format()
local function free()
-- Remove JSON comment lines
Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([},\"'])",
"%1%3" )
end -- free()
local function full()
-- Build HTML table for display from JSON data, and append an invisible
-- <templatedata> block.
Data.div = mw.html.create( "div" )
:addClass( "mw-templatedata-doc-wrap" )
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( format() )
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
-- Display raw templatedata table all the time.
Data.div:node( mw.html.create( "hr" ) )
Data.div:node( div )
else
-- Creates an expand link to check raw templatedata table.
local wrapper = mw.html.create( "div" )
wrapper:addClass( "mw-collapsible" )
wrapper:addClass( "mw-collapsed" )
wrapper:css( "font-size", "85%" )
div:addClass( "mw-collapsible-content" )
wrapper:wikitext( "'''Test of raw TemplateData output''': " )
wrapper:node( div )
Data.div:node( wrapper )
end
end
end
end -- full()
local function furnish( adapt, arglist )
-- Called by f, this function is the first to do any real work when the
-- module is invoked.
-- Parameter:
-- adapt -- table, #invoke parameters
-- arglist -- table, template parameters
-- Returns string
--local spy=""
local source
for k, v in pairs( Config ) do
if adapt[ k ] and adapt[ k ] ~= "" then
Config[ v ] = adapt[ k ]
end
end -- for k, v
Config.loudly = faculty( arglist.debug or adapt.debug )
--if mw.site.server:find( "//de.wikipedia.beta.wmflabs.org", 1, true ) then
-- Config.loudly = true
--end
Data.lazy = faculty( arglist.lazy ) and not Config.loudly
Data.leading = faculty( arglist.TOC )
if arglist.JSON then
source = arglist.JSON
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 -- <DEL> ' " ` U N I Q
Data.strip = s
end
end
if not source then
Data.title = mw.title.getCurrentTitle()
source = find()
if not source and
Config.subpage and Config.suffix 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
--if source and
-- ( source:find( "|", 1, true ) or
-- source:find( "}}", 1, true ) ) then
-- -- <ref
--spy=string.format( "[[category:%s]]", Config.strange )
--end
end
if not Data.lazy and Config.subpage then
if not Data.title then
Data.title = mw.title.getCurrentTitle()
end
Data.lazy = Data.title.text:match( Config.subpage )
end
TemplateData.getPlainJSON( source )
return finalize()
--return spy .. finalize()
end -- furnish()
TemplateData.failsafe = function ( assert )
-- Checks the age of this implementation against some minimum ("assert").
local r
if not assert or assert <= TemplateData.serial then
r = TemplateData.serial
else
r = false
end
return r
end -- TemplateData.failsafe()
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()
Data.got = mw.text.jsonDecode( Data.source )
if Data.got then
full()
if Data.lasting then
Fault( "deprecated type syntax" )
end
if Data.less then
Fault( Config.solo )
end
elseif not Data.strip then
Fault( "fatal JSON error" )
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 )
-- The entry point for templates invoking the module.
-- Just wraps furnish in an exception handler.
local lucky, result
TemplateData.frame = frame
lucky, result = pcall( furnish, frame.args, frame:getParent().args )
if not lucky then
Fault( "INTERNAL: " .. result )
result = failures()
end
return result
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 TemplateData.failsafe( since ) or ""
end -- p.failsafe()
p.TemplateData = function ()
-- Module interface
return TemplateData
end
return p