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, -- 
 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 
 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, --  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
 else
 Data.scream = alert
 end
end -- Fault

local function collapseWhitespace ( a )
	-- Collapses whitespace, HTML style.
 return a:gsub
end -- collapseWhitespace

--

local function facet
 -- 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
 "%%%1" ) )
 local i, k = Data.source:find
 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
 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" )
 r = tostring( e )
 mw.addWarning( "**TemplateData**" .. Data.scream )
 if Config.strange then
 r = string.format
 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.  blocks are left alone, but the
 --  tags themselves are removed.
 -- adjust -- string
 -- Returns string, with adjusted text
 local r
 if adjust:find then
 local i = 1
 local j, k = adjust:find
 r = ""
 while j do
 if j > 1 then
 r = r .. collapseWhitespace( adjust:sub )
 end
 i = k + 1
 j, k = adjust:find
 if j then
 r = r .. adjust:sub
 i = k + 1
 j, k = adjust:find( "" )
 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
 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
 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)
 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
 else
 s = "Circular inherits: " .. k
 end
 end
 end -- for k, v
 Fault( s )
 end
end -- fathers


local function feasible
 -- Create description head
 -- Parameter:
 -- about -- table, supposed to contain description
 -- asked -- true, if mandatory description
 -- Returns , 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" )
 end
 for k, v in pairs( about.description[ 2 ] ) do
 plus:node( mw.html.create( "li" )
 end -- for k, v
 end
 elseif Config.solo and asked then
 para:addClass( "error" )
 Data.less = true
 else
 para = false
 end
 if para then
 if plus then
 r = mw.html.create( "div" )
 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
 if i then
 table.insert
 pointers[ i ] = k
 i = facet
 if i then
 s = "Parameter '%s' detected twice"
 Fault( string.format )
 end
 else
 s = "Parameter '%s' not detected"
 Fault( string.format )
 end
 end -- for k, v
 table.sort( points )
 for i = 1, #points do
 table.insert
 end -- i = 1, #points
 elseif s then
 table.insert
 end
 end
end -- feat


local function feature( access )
 -- Create table row for parameter, check and display violations
 -- Parameter:
 -- access -- string, with name
 -- Returns 
 local mode, s, status
 local fine = function ( a )
 s = mw.text.trim( a )
 return a == s and
 a ~= "" and
 not a:find( "%|=\n" ) and
 not a:find( "%s%s" )
 end
 local begin = mw.html.create( "td" )
 local code = mw.html.create( "code" )
 local desc = mw.html.create( "td" )
 local 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

 -- name and aliases
 code:css
 if not fine( access ) then
 code:addClass( "error" )
 Fault( string.format )
 legal = false
 begin:attr
 end
 code = mw.html.create( "td" )
 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" )
 end
 code:wikitext( s )
 else
 lapsus = true
 code:node( mw.html.create( "code" )
 end
 end -- for k, v
 if lapsus then
 s = string.format
 Fault( getLocalizedText( "invalid-value" ):gsub )
 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(")")
 elseif show == "1" then
 	dd:wikitext(")")
 else
 	dd:wikitext( show )
 end
 else
 	dd:wikitext( show )
 end
 dl:node( dt )
 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 ) )
 typed:node( mw.html.create( "span" )
 Data.lasting = true
 else
 s = getLocalizedText( "doc-param-type-" .. param.type )
 typed:wikitext( s )
 end
 else
 Data.params[ access ].type = "unknown"
 typed:addClass( "error" )
 s = string.format
 Fault( getLocalizedText( "invalid-value" ):gsub )
 legal = false
 end
 else
 typed = mw.html.create( "td" )
 end

 -- status
 if param.required then
 mode = 1
 if param.deprecated then
 Fault( string.format )
 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" )
 if type( param.deprecated ) == "string" then
 ranking:tag( "br" )
 ranking:wikitext( param.deprecated )
 end
 end

 -- 
 r:attr( "id", "templatedata:" .. mw.uri.anchorEncode( access ) )
 if not legal then
 r:css
 end
 return r
end -- feature


local function features
 -- Create  for parameters
 -- Returns , or nil
 local r
 if Data.tree and Data.tree.params then
 local style = Permit.styles.tableheadbg
 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 Config.classTable then
-- tbl:addClass( Config.classTable )
-- end
 if Config.stylesTable then
 tbl:cssText( Config.stylesTable )
 end
 tr:node( mw.html.create( "th" )
 tbl:newline
-- :node( mw.html.create( "thead" )
-- )
 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" )
 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
 if k then
 r = mw.text.trim( s:sub )
 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
 if r:find then
 r = r:gsub
 end
 r = plaintext._main(r)
 if r:find then
 r = mw.text.decode( r )
 end
 end
 return r
end -- flat


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


local function focus( access )
 -- Check components; focus multilingual description, build trees
 -- Parameter:
 -- access -- string, name of parameter, nil for root
 local f = function 
 local r
 if at then
 r = string.format
 else
 r = "''root''"
 end
 if a then
 r = string.format
 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 then
 if scope:find 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 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  bad for %s",
 scope, f )
 Fault( s )
 end
 else
 Fault( "Unknown component " .. f )
 end
 end -- for k, v
 else
 Fault( f .. " needs to be of  type" )
 end
end -- focus


local function format
 -- Build presented documentation
 -- Returns 
 local r = mw.html.create( "div" )
 local s = feasible
 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
 r:newline
 end
 s = features
 if s then
 if Data.leading then
 r:node( mw.html.create( "h2" )
 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" )
 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
 --  block.
 Data.div = mw.html.create( "div" )
 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
 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
 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
 -- 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 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
 if start == "<" then
 Data.strip = s
 elseif start == "{" then
 source = s
 elseif mw.ustring.sub ==
 mw.ustring.char then --  ' " ` 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
 Data.title = mw.title.new( s )
 if Data.title.exists then
 source = find
 end
 end
--if source and
-- ( source:find or
-- source:find ) then
-- -- <ref
--spy=string.format
--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 
 TemplateData.frame = mw.getCurrentFrame
 return furnish
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
 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