Module:TemplatePar/sandbox

--[=[ TemplatePar 2015-02-14

Template parameter utility

  • assert
  • check
  • count
  • countNotEmpty
  • downcase()
  • match
  • valid
  • verify()
  • TemplatePar()

]=]

-- Module globals

local TemplatePar = { }

local MessagePrefix = "lua-module-TemplatePar-"

local L10nDef = {}

L10nDef.en = {

badPattern = "#invoke:TemplatePar pattern syntax error",

dupOpt = "#invoke:TemplatePar repeated optional parameter",

dupRule = "#invoke:TemplatePar conflict key/pattern",

empty = "Error in template * undefined value for mandatory",

invalid = "Error in template * invalid parameter",

invalidPar = "#invoke:TemplatePar invalid parameter",

minmax = "#invoke:TemplatePar min > max",

missing = "#invoke:TemplatePar missing library",

multiSpell = "Error in template * multiple spelling of parameter",

noMSGnoCAT = "#invoke:TemplatePar neither message nor category",

noname = "#invoke:TemplatePar missing parameter name",

notFound = "Error in template * missing page",

tooLong = "Error in template * parameter too long",

tooShort = "Error in template * parameter too short",

undefined = "Error in template * mandatory parameter missing",

unknown = "Error in template * unknown parameter name",

unknownRule = "#invoke:TemplatePar unknown rule"

}

L10nDef.de = {

badPattern = "#invoke:TemplatePar Syntaxfehler des pattern",

dupOpt = "#invoke:TemplatePar Optionsparameter wiederholt",

dupRule = "#invoke:TemplatePar Konflikt key/pattern",

empty = "Fehler bei Vorlage * Pflichtparameter ohne Wert",

invalid = "Fehler bei Vorlage * Parameter ungültig",

invalidPar = "#invoke:TemplatePar Ungültiger Parameter",

minmax = "#invoke:TemplatePar min > max",

multiSpell = "Fehler bei Vorlage * Mehrere Parameter-Schreibweisen",

noMSGnoCAT = "#invoke:TemplatePar weder Meldung noch Kategorie",

noname = "#invoke:TemplatePar Parameter nicht angegeben",

notFound = "Fehler bei Vorlage * Seite fehlt",

tooLong = "Fehler bei Vorlage * Parameter zu lang",

tooShort = "Fehler bei Vorlage * Parameter zu kurz",

undefined = "Fehler bei Vorlage * Pflichtparameter fehlt",

unknown = "Fehler bei Vorlage * Parametername unbekannt",

unknownRule = "#invoke:TemplatePar Unbekannte Regel"

}

local Patterns = {

[ "ASCII" ] = "^[ -~]*$",

[ "ASCII+" ] = "^[ -~]+$",

[ "ASCII+1" ] = "^[!-~]+$",

[ "n" ] = "^[%-]?[0-9]*$",

[ "n>0" ] = "^[0-9]*[1-9][0-9]*$",

[ "N+" ] = "^[%-]?[1-9][0-9]*$",

[ "N>0" ] = "^[1-9][0-9]*$",

[ "x" ] = "^[0-9A-Fa-f]*$",

[ "x+" ] = "^[0-9A-Fa-f]+$",

[ "X" ] = "^[0-9A-F]*$",

[ "X+" ] = "^[0-9A-F]+$",

[ "0,0" ] = "^[%-]?[0-9]*,?[0-9]*$",

[ "0,0+" ] = "^[%-]?[0-9]+,[0-9]+$",

[ "0,0+?" ] = "^[%-]?[0-9]+,?[0-9]*$",

[ "0.0" ] = "^[%-]?[0-9]*[%.]?[0-9]*$",

[ "0.0+" ] = "^[%-]?[0-9]+%.[0-9]+$",

[ "0.0+?" ] = "^[%-]?[0-9]+[%.]?[0-9]*$",

[ ".0+" ] = "^[%-]?[0-9]*[%.]?[0-9]+$",

[ "ID" ] = "^[A-Za-z]?[A-Za-z_0-9]*$",

[ "ID+" ] = "^[A-Za-z][A-Za-z_0-9]*$",

[ "ABC" ] = "^[A-Z]*$",

[ "ABC+" ] = "^[A-Z]+$",

[ "Abc" ] = "^[A-Z]*[a-z]*$",

[ "Abc+" ] = "^[A-Z][a-z]+$",

[ "abc" ] = "^[a-z]*$",

[ "abc+" ] = "^[a-z]+$",

[ "aBc+" ] = "^[a-z]+[A-Z][A-Za-z]*$",

[ "w" ] = "^%S*$",

[ "w+" ] = "^%S+$",

[ "base64" ] = "^[A-Za-z0-9%+/]*$",

[ "base64+" ] = "^[A-Za-z0-9%+/]+$",

[ "aa" ] = "[%a%a].*[%a%a]",

[ "pagename" ] = string.format( "^[^#<>%%[%%]|{}%c-%c%c]+$",

1, 31, 127 ),

[ "+" ] = "%S"

}

local patternCJK = false

local function containsCJK( s )

-- Is any CJK character present?

-- Precondition:

-- s -- string

-- Postcondition:

-- Return false iff no CJK present

-- Uses:

-- >< patternCJK

-- mw.ustring.char()

-- mw.ustring.match()

local r = false

patternCJK = patternCJK or mw.ustring.char(91,

13312, 45, 40959,

131072, 45, 178207,

93 )

if mw.ustring.match( s, patternCJK ) then

r = true

end

return r

end -- containsCJK()

local function facility( accept, attempt )

-- Check string as possible file name or other source page

-- Precondition:

-- accept -- string; requirement

-- file

-- file+

-- file:

-- file:+

-- image

-- image+

-- image:

-- image:+

-- attempt -- string; to be tested

-- Postcondition:

-- Return error keyword, or false

-- Uses:

-- Module:FileMedia

-- FileMedia.isType()

local r

if attempt and attempt ~= "" then

local lucky, FileMedia = pcall( require, "Module:FileMedia" )

if type( FileMedia ) == "table" then

FileMedia = FileMedia.FileMedia()

local s, live = accept:match( "^([a-z]+)(:?)%+?$" )

if live then

if FileMedia.isType( attempt, s ) then

if FileMedia.isFile( attempt ) then

r = false

else

r = "notFound"

end

else

r = "invalid"

end

elseif FileMedia.isType( attempt, s ) then

r = false

else

r = "invalid"

end

else

r = "missing"

end

elseif accept:match( "%+$" ) then

r = "empty"

else

r = false

end

return r

end -- facility()

local function factory( say )

-- Retrieve localized message string in content language

-- Precondition:

-- say -- string; message ID

-- Postcondition:

-- Return some message string

-- Uses:

-- > MessagePrefix

-- > L10nDef

-- mw.language.getContentLanguage()

-- mw.message.new()

local c = mw.language.getContentLanguage():getCode()

local m = mw.message.new( MessagePrefix .. say )

local r = false

if m:isBlank() then

local l10n = L10nDef[ c ] or L10nDef[ "en" ]

r = l10n[ say ]

else

m:inLanguage( c )

r = m:plain()

end

r = r or string.format( "(((%s)))", say )

return r

end -- factory()

local function failsafe( story, scan )

-- Test for match (possibly user-defined with syntax error)

-- Precondition:

-- story -- string; parameter value

-- scan -- string; pattern

-- Postcondition:

-- Return nil, if not matching, else non-nil

-- Uses:

-- mw.ustring.match()

return mw.ustring.match( story, scan )

end -- failsafe()

local function failure( spec, suspect, options )

-- Submit localized error message

-- Precondition:

-- spec -- string; message ID

-- suspect -- string or nil; additional information

-- options -- table or nil; optional details

-- options.template

-- Postcondition:

-- Return string

-- Uses:

-- factory()

local r = factory( spec )

if type( options ) == "table" then

if type( options.template ) == "string" then

if #options.template > 0 then

r = string.format( "%s (%s)", r, options.template )

end

end

end

if suspect then

r = string.format( "%s: %s", r, suspect )

end

return r

end -- failure()

local function fault( store, key )

-- Add key to collection string and insert separator

-- Precondition:

-- store -- string or nil or false; collection string

-- key -- string or number; to be appended

-- Postcondition:

-- Return string; extended

local r

local s

if type( key ) == "number" then

s = tostring( key )

else

s = key

end

if store then

r = string.format( "%s; %s", store, s )

else

r = s

end

return r

end -- fault()

local function feasible( analyze, options, abbr )

-- Check content of a value

-- Precondition:

-- analyze -- string to be analyzed

-- options -- table or nil; optional details

-- options.pattern

-- options.key

-- options.say

-- abbr -- true: abbreviated error message

-- Postcondition:

-- Return string with error message as configured;

-- false if valid or no answer permitted

-- Uses:

-- > Patterns

-- failure()

-- mw.text.trim()

-- facility()

-- failsafe()

-- containsCJK()

local r = false

local s = false

local show = nil

local scan = false

if type( options.pattern ) == "string" then

if options.key then

r = failure( "dupRule", false, options )

else

scan = options.pattern

end

else

if type( options.key ) == "string" then

s = mw.text.trim( options.key )

else

s = "+"

end

if s ~= "*" then

scan = Patterns[ s ]

end

if type( scan ) == "string" then

if s == "n" or s == "0,0" or s == "0.0" then

if not analyze:match( "[0-9]" ) and

not analyze:match( "^%s*$" ) then

scan = false

if options.say then

show = string.format( "'%s'", options.say )

end

if abbr then

r = show

else

r = failure( "invalid", show, options )

end

end

end

elseif s ~= "*" then

local op, n, plus = s:match( "([]=?)([-0-9][%S]*)(+?)" )

if op then

n = tonumber( n )

if n then

local i = tonumber( analyze )

if i then

if op == "<" then

i = ( i < n )

elseif op == "<=" then

i = ( i <= n )

elseif op == ">" then

i = ( i > n )

elseif op == ">=" then

i = ( i >= n )

elseif op == "==" then

i = ( i == n )

elseif op == "!=" then

i = ( i ~= n )

else

n = false

end

end

if not i then

r = "invalid"

end

elseif plus then

r = "undefined"

end

elseif s:match( "^image%+?:?$" ) or

s:match( "^file%+?:?$" ) then

r = facility( s, analyze )

n = true

elseif s:match( "langW?%+?" ) then

n = "lang"

-- lang lang+

-- langW langW+

end

if not n and not r then

r = "unknownRule"

end

if r then

if options.say then

show = string.format( "'%s' %s", options.say, s )

else

show = s

end

if abbr then

r = show

else

r = failure( r, show, options )

end

end

end

end

if scan then

local legal, got = pcall( failsafe, analyze, scan )

if legal then

if not got then

if s == "aa" then

got = containsCJK( analyze )

end

if not got then

if options.say then

show = string.format( "'%s'", options.say )

end

if abbr then

r = show

else

r = failure( "invalid", show, options )

end

end

end

else

r = failure( "badPattern",

string.format( "%s *** %s", scan, got ),

options )

end

end

return r

end -- feasible()

local function fed( haystack, needle )

-- Find needle in haystack map

-- Precondition:

-- haystack -- table; map of key values

-- needle -- any; identifier

-- Postcondition:

-- Return true iff found

local k, v

for k, v in pairs( haystack ) do

if k == needle then

return true

end

end -- for k, v

return false

end -- fed()

local function fetch( light, options )

-- Return regular table with all parameters

-- Precondition:

-- light -- true: template transclusion; false: #invoke

-- options -- table; optional details

-- options.low

-- Postcondition:

-- Return table; whitespace-only values as false

-- Uses:

-- TemplatePar.downcase()

-- mw.getCurrentFrame()

-- frame:getParent()

local g, k, v

local r = { }

if options.low then

g = TemplatePar.downcase( options )

else

g = mw.getCurrentFrame()

if light then

g = g:getParent()

end

g = g.args

end

if type( g ) == "table" then

r = { }

for k, v in pairs( g ) do

if type( v ) == "string" then

if v:match( "^%s*$" ) then

v = false

end

else

v = false

end

if type( k ) == "number" then

k = tostring( k )

end

r[ k ] = v

end -- for k, v

else

r = g

end

return r

end -- fetch()

local function figure( append, options )

-- Extend options by rule from #invoke strings

-- Precondition:

-- append -- string or nil; requested rule

-- options -- table; details

-- ++ .key

-- ++ .pattern

-- Postcondition:

-- Return sequence table

local r = options

if type( append ) == "string" then

local story = mw.text.trim( append )

local sub = story:match( "^/(.*%S)/$" )

if type( sub ) == "string" then

sub = sub:gsub( "%%!", "|" )

sub = sub:gsub( "%%%(%(", "{{" )

sub = sub:gsub( "%%%)%)", "}}" )

options.pattern = sub

options.key = nil

else

options.key = story

options.pattern = nil

end

end

return r

end -- figure()

local function fill( specified )

-- Split requirement string separated by '='

-- Precondition:

-- specified -- string or nil; requested parameter set

-- Postcondition:

-- Return sequence table

-- Uses:

-- mw.text.split()

local r

if specified then

local i, s

r = mw.text.split( specified, "%s*=%s*" )

for i = #r, 1, -1 do

s = r[ i ]

if #s == 0 then

table.remove( r, i )

end

end -- for i, -1

else

r = { }

end

return r

end -- fill()

local function finalize( submit, options, frame )

-- Finalize message

-- Precondition:

-- submit -- string or false or nil; non-empty error message

-- options -- table or nil; optional details

-- options.format

-- options.preview

-- options.cat

-- options.template

-- frame -- object, or false

-- Postcondition:

-- Return string or false

-- Uses:

-- factory()

local r = false

if submit then

local opt, s

local lazy = false

local show = false

if type( options ) == "table" then

opt = options

show = opt.format

lazy = ( show == "" or show == "0" or show == "-" )

s = opt.preview

if type( s ) == "string" and

s ~= "" and s ~= "0" and s ~= "-" then

if lazy then

show = ""

lazy = false

end

frame = frame or mw.getCurrentFrame()

if frame:preprocess( "{{REVISIONID}}" ) == "" then

if s == "1" then

show = "*"

else

show = s

end

end

end

else

opt = { }

end

if lazy then

if not opt.cat then

r = string.format( "%s %s",

submit, factory( "noMSGnoCAT" ) )

end

else

r = submit

end

if r and not lazy then

local i

if not show or show == "*" then

show = "@@@"

end

i = show:find( "@@@", 1, true )

if i then

-- No gsub() since r might contain "%3" (e.g. URL)

r = string.format( "%s%s%s",

show:sub( 1, i - 1 ),

r,

show:sub( i + 3 ) )

else

r = show

end

end

s = opt.cat

if type( s ) == "string" then

if opt.errNS then

local ns = mw.title.getCurrentTitle().namespace

local st = type( opt.errNS )

if st == "string" then

local space = string.format( ".*%%s%d%%s.*", ns )

local spaces = string.format( " %s ", opt.errNS )

if spaces:match( space ) then

opt.errNS = false

end

elseif st == "table" then

for i = 1, #opt.errNS do

if opt.errNS[ i ] == ns then

opt.errNS = false

break -- for i

end

end -- for i

end

end

if opt.errNS then

r = ""

else

r = r or ""

if s:find( "@@@" ) then

if type( opt.template ) == "string" then

s = s:gsub( "@@@", opt.template )

end

end

local i

local cats = mw.text.split( s, "%s*#%s*" )

for i = 1, #cats do

s = mw.text.trim( cats[ i ] )

if #s > 0 then

r = string.format( "%sCategory:%s", r, s )

end

end -- for i

end

end

end

return r

end -- finalize()

local function finder( haystack, needle )

-- Find needle in haystack sequence

-- Precondition:

-- haystack -- table; sequence of key names, downcased if low

-- needle -- any; key name

-- Postcondition:

-- Return true iff found

local i

for i = 1, #haystack do

if haystack[ i ] == needle then

return true

end

end -- for i

return false

end -- finder()

local function fix( valid, duty, got, options )

-- Perform parameter analysis

-- Precondition:

-- valid -- table; unique sequence of known parameters

-- duty -- table; sequence of mandatory parameters

-- got -- table; sequence of current parameters

-- options -- table or nil; optional details

-- Postcondition:

-- Return string as configured; empty if valid

-- Uses:

-- finder()

-- fault()

-- failure()

-- fed()

local k, v

local r = false

for k, v in pairs( got ) do

if not finder( valid, k ) then

r = fault( r, k )

end

end -- for k, v

if r then

r = failure( "unknown",

string.format( "'%s'", r ),

options )

else -- all names valid

local i, s

for i = 1, #duty do

s = duty[ i ]

if not fed( got, s ) then

r = fault( r, s )

end

end -- for i

if r then

r = failure( "undefined", r, options )

else -- all mandatory present

for i = 1, #duty do

s = duty[ i ]

if not got[ s ] then

r = fault( r, s )

end

end -- for i

if r then

r = failure( "empty", r, options )

end

end

end

return r

end -- fix()

local function flat( collection, options )

-- Return all table elements with downcased string

-- Precondition:

-- collection -- table; k=v pairs

-- options -- table or nil; optional messaging details

-- Postcondition:

-- Return table, may be empty; or string with error message.

-- Uses:

-- mw.ustring.lower()

-- fault()

-- failure()

local k, v

local r = { }

local e = false

for k, v in pairs( collection ) do

if type ( k ) == "string" then

k = mw.ustring.lower( k )

if r[ k ] then

e = fault( e, k )

end

end

r[ k ] = v

end -- for k, v

if e then

r = failure( "multiSpell", e, options )

end

return r

end -- flat()

local function fold( options )

-- Merge two tables, create new sequence if both not empty

-- Precondition:

-- options -- table; details

-- options.mandatory sequence to keep unchanged

-- options.optional sequence to be appended

-- options.low downcased expected

-- Postcondition:

-- Return merged table, or message string if error

-- Uses:

-- finder()

-- fault()

-- failure()

-- flat()

local i, e, r, s

local base = options.mandatory

local extend = options.optional

if #base == 0 then

if #extend == 0 then

r = { }

else

r = extend

end

else

if #extend == 0 then

r = base

else

e = false

for i = 1, #extend do

s = extend[ i ]

if finder( base, s ) then

e = fault( e, s )

end

end -- for i

if e then

r = failure( "dupOpt", e, options )

else

r = { }

for i = 1, #base do

table.insert( r, base[ i ] )

end -- for i

for i = 1, #extend do

table.insert( r, extend[ i ] )

end -- for i

end

end

end

if options.low and type( r ) == "table" then

r = flat( r, options )

end

return r

end -- fold()

local function form( light, options, frame )

-- Run parameter analysis on current environment

-- Precondition:

-- light -- true: template transclusion; false: #invoke

-- options -- table or nil; optional details

-- options.mandatory

-- options.optional

-- frame -- object, or false

-- Postcondition:

-- Return string with error message as configured;

-- false if valid

-- Uses:

-- fold()

-- fetch()

-- fix()

-- finalize()

local duty, r

if type( options ) == "table" then

if type( options.mandatory ) ~= "table" then

options.mandatory = { }

end

duty = options.mandatory

if type( options.optional ) ~= "table" then

options.optional = { }

end

r = fold( options )

else

options = { }

duty = { }

r = { }

end

if type( r ) == "table" then

local got = fetch( light, options )

if type( got ) == "table" then

r = fix( r, duty, got, options )

else

r = got

end

end

return finalize( r, options, frame )

end -- form()

local function format( analyze, options )

-- Check validity of a value

-- Precondition:

-- analyze -- string to be analyzed

-- options -- table or nil; optional details

-- options.say

-- options.min

-- options.max

-- Postcondition:

-- Return string with error message as configured;

-- false if valid or no answer permitted

-- Uses:

-- feasible()

-- failure()

local r = feasible( analyze, options, false )

local show

if options.min and not r then

if type( options.min ) == "number" then

if type( options.max ) == "number" then

if options.max < options.min then

r = failure( "minmax",

string.format( "%d > %d",

options.min,

options.max ),

options )

end

end

if #analyze < options.min and not r then

show = " <" .. options.min

if options.say then

show = string.format( "%s '%s'", show, options.say )

end

r = failure( "tooShort", show, options )

end

else

r = failure( "invalidPar", "min", options )

end

end

if options.max and not r then

if type( options.max ) == "number" then

if #analyze > options.max then

show = " >" .. options.max

if options.say then

show = string.format( "%s '%s'", show, options.say )

end

r = failure( "tooLong", show, options )

end

else

r = failure( "invalidPar", "max", options )

end

end

return r

end -- format()

local function formatted( assignment, access, options )

-- Check validity of one particular parameter in a collection

-- Precondition:

-- assignment -- collection

-- access -- id of parameter in collection

-- options -- table or nil; optional details

-- Postcondition:

-- Return string with error message as configured;

-- false if valid or no answer permitted

-- Uses:

-- mw.text.trim()

-- format()

-- failure()

local r = false

if type( assignment ) == "table" then

local story = assignment.args[ access ] or ""

if type( access ) == "number" then

story = mw.text.trim( story )

end

if type( options ) ~= "table" then

options = { }

end

options.say = access

r = format( story, options )

end

return r

end -- formatted()

local function furnish( frame, action )

-- Prepare #invoke evaluation of .assert() or .valid()

-- Precondition:

-- frame -- object; #invoke environment

-- action -- "assert" or "valid"

-- Postcondition:

-- Return string with error message or ""

-- Uses:

-- form()

-- failure()

-- finalize()

-- TemplatePar.valid()

-- TemplatePar.assert()

local options = { mandatory = { "1" },

optional = { "2",

"cat",

"errNS",

"low",

"max",

"min",

"format",

"preview",

"template" },

template = string.format( "#invoke:%s|%s|",

"TemplatePar",

action )

}

local r = form( false, options, frame )

if not r then

local s

options = { cat = frame.args.cat,

errNS = frame.args.errNS,

low = frame.args.low,

format = frame.args.format,

preview = frame.args.preview,

template = frame.args.template

}

options = figure( frame.args[ 2 ], options )

if type( frame.args.min ) == "string" then

s = frame.args.min:match( "^%s*([0-9]+)%s*$" )

if s then

options.min = tonumber( s )

else

r = failure( "invalidPar",

"min=" .. frame.args.min,

options )

end

end

if type( frame.args.max ) == "string" then

s = frame.args.max:match( "^%s*([1-9][0-9]*)%s*$" )

if s then

options.max = tonumber( s )

else

r = failure( "invalidPar",

"max=" .. frame.args.max,

options )

end

end

if r then

r = finalize( r, options, frame )

else

s = frame.args[ 1 ] or ""

r = tonumber( s )

if ( r ) then

s = r

end

if action == "valid" then

r = TemplatePar.valid( s, options, frame )

elseif action == "assert" then

r = TemplatePar.assert( s, "", options )

end

end

end

return r or ""

end -- furnish()

TemplatePar.assert = function ( analyze, append, options )

-- Perform parameter analysis on a single string

-- Precondition:

-- analyze -- string to be analyzed

-- append -- string: append error message, prepending

-- false or nil: throw error with message

-- options -- table; optional details

-- Postcondition:

-- Return string with error message as configured;

-- false if valid

-- Uses:

-- format()

local r = format( analyze, options )

if ( r ) then

if ( type( append ) == "string" ) then

if ( append ~= "" ) then

r = string.format( "%s
%s", append, r )

end

else

error( r, 0 )

end

end

return r

end -- TemplatePar.assert()

TemplatePar.check = function ( options )

-- Run parameter analysis on current template environment

-- Precondition:

-- options -- table or nil; optional details

-- options.mandatory

-- options.optional

-- Postcondition:

-- Return string with error message as configured;

-- false if valid

-- Uses:

-- form()

return form( true, options, false )

end -- TemplatePar.check()

TemplatePar.count = function ()

-- Return number of template parameters

-- Postcondition:

-- Return number, starting at 0

-- Uses:

-- mw.getCurrentFrame()

-- frame:getParent()

local k, v

local r = 0

local t = mw.getCurrentFrame():getParent()

local o = t.args

for k, v in pairs( o ) do

r = r + 1

end -- for k, v

return r

end -- TemplatePar.count()

TemplatePar.countNotEmpty = function ()

-- Return number of template parameters with more than whitespace

-- Postcondition:

-- Return number, starting at 0

-- Uses:

-- mw.getCurrentFrame()

-- frame:getParent()

local k, v

local r = 0

local t = mw.getCurrentFrame():getParent()

local o = t.args

for k, v in pairs( o ) do

if not v:match( "^%s*$" ) then

r = r + 1

end

end -- for k, v

return r

end -- TemplatePar.countNotEmpty()

TemplatePar.downcase = function ( options )

-- Return all template parameters with downcased name

-- Precondition:

-- options -- table or nil; optional messaging details

-- Postcondition:

-- Return table, may be empty; or string with error message.

-- Uses:

-- mw.getCurrentFrame()

-- frame:getParent()

-- flat()

local t = mw.getCurrentFrame():getParent()

return flat( t.args, options )

end -- TemplatePar.downcase()

TemplatePar.valid = function ( access, options, frame )

-- Check validity of one particular template parameter

-- Precondition:

-- access -- id of parameter in template transclusion

-- string or number

-- options -- table or nil; optional details

-- frame -- object; #invoke environment

-- Postcondition:

-- Return string with error message as configured;

-- false if valid or no answer permitted

-- Uses:

-- mw.text.trim()

-- TemplatePar.downcase()

-- frame:getParent()

-- formatted()

-- failure()

-- finalize()

local r = type( access )

if r == "string" then

r = mw.text.trim( access )

if #r == 0 then

r = false

end

elseif r == "number" then

r = access

else

r = false

end

if r then

local params

if type( options ) ~= "table" then

options = { }

end

if options.low then

params = TemplatePar.downcase( options )

else

params = frame:getParent()

end

r = formatted( params, access, options )

else

r = failure( "noname", false, options )

end

return finalize( r, options, frame )

end -- TemplatePar.valid()

TemplatePar.verify = function ( options )

-- Perform #invoke parameter analysis

-- Precondition:

-- options -- table or nil; optional details

-- Postcondition:

-- Return string with error message as configured;

-- false if valid

-- Uses:

-- form()

return form( false, options, false )

end -- TemplatePar.verify()

-- Provide external access

local p = {}

function p.assert( frame )

-- Perform parameter analysis on some single string

-- Precondition:

-- frame -- object; #invoke environment

-- Postcondition:

-- Return string with error message or ""

-- Uses:

-- furnish()

return furnish( frame, "assert" )

end -- .assert()

function p.check( frame )

-- Check validity of template parameters

-- Precondition:

-- frame -- object; #invoke environment

-- Postcondition:

-- Return string with error message or ""

-- Uses:

-- form()

-- fill()

local options = { optional = { "all",

"opt",

"cat",

"errNS",

"low",

"format",

"preview",

"template" },

template = "#invoke:TemplatePar|check|"

}

local r = form( false, options, frame )

if not r then

options = { mandatory = fill( frame.args.all ),

optional = fill( frame.args.opt ),

cat = frame.args.cat,

errNS = frame.args.errNS,

low = frame.args.low,

format = frame.args.format,

preview = frame.args.preview,

template = frame.args.template

}

r = form( true, options, frame )

end

return r or ""

end -- .check()

function p.count( frame )

-- Count number of template parameters

-- Postcondition:

-- Return string with digits including "0"

-- Uses:

-- TemplatePar.count()

return tostring( TemplatePar.count() )

end -- .count()

function p.countNotEmpty( frame )

-- Count number of template parameters which are not empty

-- Postcondition:

-- Return string with digits including "0"

-- Uses:

-- TemplatePar.countNotEmpty()

return tostring( TemplatePar.countNotEmpty() )

end -- .countNotEmpty()

function p.match( frame )

-- Combined analysis of parameters and their values

-- Postcondition:

-- Return string with error message or ""

-- Uses:

-- mw.text.trim()

-- mw.ustring.lower()

-- failure()

-- form()

-- TemplatePar.downcase()

-- figure()

-- feasible()

-- fault()

-- finalize()

local r = false

local options = { cat = frame.args.cat,

errNS = frame.args.errNS,

low = frame.args.low,

format = frame.args.format,

preview = frame.args.preview,

template = frame.args.template

}

local k, v, s

local params = { }

for k, v in pairs( frame.args ) do

if type( k ) == "number" then

s, v = v:match( "^ *([^=]+) *= *(%S.*%S*) *$" )

if s then

s = mw.text.trim( s )

if s == "" then

s = false

end

end

if s then

if options.low then

s = mw.ustring.lower( s )

end

if params[ s ] then

s = params[ s ]

s[ #s + 1 ] = v

else

params[ s ] = { v }

end

else

r = failure( "invalidPar", tostring( k ), options )

break -- for k, v

end

end

end -- for k, v

if not r then

s = { }

for k, v in pairs( params ) do

s[ #s + 1 ] = k

end -- for k, v

options.optional = s

r = form( true, options, frame )

end

if not r then

local errMiss, errValues, lack, rule

local targs = frame:getParent().args

options.optional = nil

if options.low then

targs = TemplatePar.downcase()

else

targs = frame:getParent().args

end

errMiss = false

errValues = false

for k, v in pairs( params ) do

options.say = k

errValue = false

s = targs[ k ]

if s then

if s == "" then

lack = true

else

lack = false

end

else

s = ""

lack = true

end

for r, rule in pairs( v ) do

options = figure( rule, options )

r = feasible( s, options, true )

if r then

if lack then

if errMiss then

errMiss = string.format( "%s, '%s'",

errMiss, k )

else

errMiss = string.format( "'%s'", k )

end

elseif not errMiss then

errValues = fault( errValues, r )

end

break -- for r, rule

end

end -- for s, rule

end -- for k, v

r = ( errMiss or errValues )

if r then

if errMiss then

r = failure( "undefined", errMiss, options )

else

r = failure( "invalid", errValues, options )

end

r = finalize( r, options, frame )

end

end

return r or ""

end -- .match()

function p.valid( frame )

-- Check validity of one particular template parameter

-- Precondition:

-- frame -- object; #invoke environment

-- Postcondition:

-- Return string with error message or ""

-- Uses:

-- furnish()

return furnish( frame, "valid" )

end -- .valid()

function p.TemplatePar()

-- Retrieve function access for modules

-- Postcondition:

-- Return table with functions

return TemplatePar

end -- .TemplatePar()

return p