Module:TemplatePar explained

local TemplatePar = --[=[ Template parameter utility * assert * check * count * countNotEmpty * downcase * duplicates * match * valid * verify * TemplatePar * failsafe ]=]

local Local = local Failsafe = TemplateParlocal GlobalMod = Local

-- Module globalsLocal.messagePrefix = "lua-module-TemplatePar-"Local.L10nDef = Local.L10nDef.en = Local.patterns = Local.boolean = Local.patternCJK = false

local foreignModule = function (access, advanced, append, alt, alert) -- Fetch global module -- Precondition: -- access -- string, with name of base module -- advanced -- true, for require; else mw.loadData -- append -- string, with subpage part, if any; or false -- alt -- number, of wikidata item of root; or false -- alert -- true, for throwing error on data problem -- Postcondition: -- Returns whatever, probably table -- 2020-01-01 local storage = access local finer = function if append then storage = string.format("%s/%s", storage, append) end end local fun, lucky, r, suited if advanced then fun = require else fun = mw.loadData end GlobalMod.globalModules = GlobalMod.globalModules or suited = GlobalMod.globalModules[access ] if not suited then finer lucky, r = pcall(fun, "Module:" .. storage) end if not lucky then if not suited and type(alt)

"number" and alt > 0 then suited = string.format("Q%d", alt) suited = mw.wikibase.getSitelink(suited) GlobalMod.globalModules[access ] = suited or true end if type(suited)

"string" then storage = suited finer lucky, r = pcall(fun, storage) end if not lucky and alert then error("Missing or invalid page: " .. storage) end end return rend -- foreignModule

local function Foreign(access) -- Access standardized library -- Precondition: -- access -- string, with name of base module -- Postcondition: -- Return library table, or not -- Uses: local r if Local[access ] then r = Local[access ] else local bib = foreignModule(access, true, false, TemplatePar.globals[access ], false) if type(bib)

"table" and type(bib[access ])

"function" then bib = bib[access ] if type(bib)

"table" then r = bib Local[access ] = bib end end end return rend -- Foreign

local function containsCJK(analyse) -- Is any CJK character present? -- Precondition: -- analyse -- string -- Postcondition: -- Return false iff no CJK present -- Uses: -- >< Local.patternCJK -- mw.ustring.char -- mw.ustring.match local r = false if not Local.patternCJK then Local.patternCJK = mw.ustring.char(91, 13312, 45, 40959, 131072, 45, 178207, 93) end if mw.ustring.match(analyse, Local.patternCJK) then r = true end return rend -- 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 -- Foreign -- FileMedia.isFile -- FileMedia.isType local r if attempt and attempt ~= "" then local FileMedia = Foreign("FileMedia") if FileMedia and type(FileMedia.isFile)

"function" and type(FileMedia.isType)

"function" then 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 rend -- facility

local function factory(say) -- Retrieve localized message string in content language -- Precondition: -- say -- string; message ID -- Postcondition: -- Return some message string -- Uses: -- > Local.messagePrefix -- > Local.L10nDef -- mw.message.new -- mw.language.getContentLanguage -- Module:Multilingual -- Foreign -- TemplatePar.framing -- Multilingual.tabData local m = mw.message.new(Local.messagePrefix .. say) local r = false if m:isBlank then local c = mw.language.getContentLanguage:getCode local l10n = Local.L10nDef[c ] if l10n then r = l10n[say ] else local MultiL = Foreign("Multilingual") if MultiL and type(MultiL.tabData)

"function" then local lang r, lang = MultiL.tabData("I18n/Module:TemplatePar", say, false, TemplatePar.framing) end end if not r then r = Local.L10nDef.en[say ] end else m:inLanguage(c) r = m:plain end if not r then r = string.format("(((%s)))", say) end return rend -- factory

local function faculty(accept, attempt) -- Check string as possible boolean -- Precondition: -- accept -- string; requirement -- boolean -- boolean+ -- attempt -- string; to be tested -- Postcondition: -- Return error keyword, or false -- Uses: -- Module:TemplUtl -- Foreign -- TemplUtl.faculty local r r = mw.text.trim(attempt):lower if r

"" then if accept

"boolean+" then r = "empty" else r = false end elseif Local.boolean[r ] or r:match("^[01%-]+$") then r = false else local TemplUtl = Foreign("TemplUtl") if TemplUtl and type(TemplUtl.faculty)

"function" then r = TemplUtl.faculty(r, "-") if r

"-" then r = "invalid" else r = false end else r = "invalid" end end return rend -- faculty

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 rend -- failure

local function fair(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 -- fair

local function familiar(accept, attempt) -- Check string as possible language name or list -- Precondition: -- accept -- string; requirement -- lang -- langs -- langW -- langsW -- lang+ -- langs+ -- langW+ -- langsW+ -- attempt -- string; to be tested -- Postcondition: -- Return error keyword, or false -- Uses: -- Module:Multilingual -- Foreign -- Multilingual.isLang local r if attempt and attempt ~= "" then local MultiL = Foreign("Multilingual") if MultiL and type(MultiL.isLang)

"function" then local lazy = accept:find("W", 1, true) if accept:find("s", 1, true) then local group = mw.text.split(attempt, "%s+") r = false for i = 1, #group do if not MultiL.isLang(group[i ], lazy) then r = "invalid" break -- for i end end -- for i elseif MultiL.isLang(attempt, lazy) then r = false else r = "invalid" end else r = "missing" end elseif accept:find("+", 1, true) then r = "empty" else r = false end return rend -- familiar

local function far(accept, attempt) -- Check string as possible URL -- Precondition: -- accept -- string; requirement -- url -- url+ -- attempt -- string; to be tested -- Postcondition: -- Return error keyword, or false -- Uses: -- Module:URLutil -- Foreign -- URLutil.isWebURL local r if attempt and attempt ~= "" then local URLutil = Foreign("URLutil") if URLutil and type(URLutil.isWebURL)

"function" then if URLutil.isWebURL(attempt) then r = false else r = "invalid" end else r = "missing" end elseif accept:find("+", 1, true) then r = "empty" else r = false end return rend -- far

local function fast(accept, attempt) -- Check string as possible date or time -- Precondition: -- accept -- string; requirement -- datetime -- datetime+ -- datetime/y -- datetime/y+ -- datetime/ym -- datetime/ym+ -- datetime/ymd -- datetime/ymd+ -- attempt -- string; to be tested -- Postcondition: -- Return error keyword, or false -- Uses: -- Module:DateTime -- Foreign -- DateTime.DateTime local r r = mw.text.trim(attempt) if r

"" then if accept:find("+", 1, true) then r = "empty" else r = false end else local DateTime = Foreign("DateTime") if type(DateTime)

"table" then local d = DateTime(attempt) if type(d)

"table" then if accept:find("/", 1, true) then r = "invalid" if accept:sub(1, 10)

"datetime/y" then if d.year then r = false if accept:sub(1, 11)

"datetime/ym" then if d.month then if accept:sub(1, 12)

"datetime/ymd" then if not d.dom then r = "invalid" end end else r = "invalid" end end end end else r = false end else r = "invalid" end else r = "invalid" end end return rend -- fast

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 rend -- 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: -- > Local.patterns -- failure -- mw.text.trim -- faculty -- fast -- facility -- familiar -- far -- fair -- containsCJK local r = false local s = false local show = nil local scan = false local stuff = mw.text.trim(analyze) 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 = Local.patterns[s ] end if type(scan)

"string" then if s

"n" or s

"0,0" or s

"0.0" then if not stuff:match("[0-9]") and not stuff: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(stuff) 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("^boolean%+?$") then r = faculty(s, stuff) n = true elseif s:match("^datetime/?y?m?d?%+?$") then r = fast(s, stuff) n = true elseif s:match("^image%+?:?$") or s:match("^file%+?:?$") then r = facility(s, stuff) n = true elseif s:match("langs?W?%+?") then r = familiar(s, stuff) n = true elseif s:match("url%+?") then r = far(s, stuff) n = true end-- datetime+-- iso8631+-- line+ 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(fair, stuff, scan) if legal then if not got then if s

"aa" then got = containsCJK(stuff) 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 rend -- 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, r for k, v in pairs(haystack) do if k

needle then r = true end end -- for k, v return r or falseend -- 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 -- TemplatePar.framing -- frame:getParent local g, k, v local r = if options.low then g = TemplatePar.downcase(options) else g = TemplatePar.framing 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 rend -- 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("%%!", "|") :gsub("%%%(%(", "") :gsub("\\n", string.char(10)) options.pattern = sub options.key = nil else options.key = story options.pattern = nil end end return rend -- 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 rend -- fill

local function finalize(submit, options) -- 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 -- Postcondition: -- Return string or false -- Uses: -- TemplatePar.framing -- factory local r = false if submit then local lazy = false local learn = false local show = false local opt, s 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 local sniffer = "" if lazy then show = "" lazy = false end if TemplatePar.framing:preprocess(sniffer)

"" then if s

"1" then show = "*" else show = s end learn = true 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 local e = mw.html.create("span") :attr("class", "error") :wikitext("@@@") if learn then local max = 1000000000 local id = math.floor(os.clock * max) local sign = string.format("error_%d", id) local btn = mw.html.create("span") local top = mw.html.create("div") e:attr("id", sign) btn:css :wikitext(">>>") sign = string.format("%s", sign, tostring(btn)) top:wikitext(sign, " ", submit) mw.addWarning(tostring(top)) end show = tostring(e) 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 if learn and r then -- r = fatal(r) end s = opt.cat if type(s)

"string" then local link 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 link = true end elseif st

"table" then for i = 1, #opt.errNS do if opt.errNS[i ]

ns then link = true break -- for i end end -- for i end else link = true end if link then local cats, i if not r then r = "" end if s:find("@@@") then if type(opt.template)

"string" then s = s:gsub("@@@", opt.template) end end 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("%s", r, s) end end -- for i end end end return rend -- 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 falseend -- 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 r = false local lack for k, v in pairs(got) do if k

"" then lack = true break -- for k, v elseif not finder(valid, k) then r = fault(r, k) end end -- for k, v if lack then r = failure("unavailable", false, options) elseif 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 rend -- 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 rend -- 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 rend -- 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; #invoke environment, or false -- Postcondition: -- Return string with error message as configured; -- false if valid -- Uses: -- TemplatePar.framing -- fold -- fetch -- fix -- finalize local duty, r if frame then TemplatePar.framing(frame) end 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)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 rend -- 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 rend -- 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 = local r = form(false, options, frame) if not r then local s options = 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) else s = frame.args[1 ] or "" r = tonumber(s) if (r) then s = r end if action

"valid" then r = TemplatePar.valid(s, options) 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 rend -- 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 rend -- 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 rend -- 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) -- Check validity of one particular template parameter -- Precondition: -- access -- id of parameter in template transclusion -- string or number -- 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 -- TemplatePar.downcase -- TemplatePar.framing -- 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 = TemplatePar.framing:getParent end r = formatted(params, access, options) else r = failure("noname", false, options) end return finalize(r, options)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

TemplatePar.framing = function(frame) -- Ensure availability of frame object -- Precondition: -- frame -- object; #invoke environment, or false -- Postcondition: -- Return frame object -- Uses: -- >< Local.frame if not Local.frame then if type(frame)

"table" and type(frame.args)

"table" and type(frame.getParent)

"function" and type(frame:getParent)

"table" and type(frame:getParent.getParent)

"function" and type(frame:getParent:getParent)

"nil" then Local.frame = frame else Local.frame = mw.getCurrentFrame end end return Local.frameend -- TemplatePar.framing

Failsafe.failsafe = function (atleast) -- Retrieve versioning and check for compliance -- Precondition: -- atleast -- string, with required version -- or wikidata|item|~|@ or false -- Postcondition: -- Returns string -- with queried version/item, also if problem -- false -- if appropriate -- 2020-08-17 local since = atleast local last = (since

"~") local linked = (since

"@") local link = (since

"item") local r if last or link or linked or since

"wikidata" then local item = Failsafe.item since = false if type(item)

"number" and item > 0 then local suited = string.format("Q%d", item) if link then r = suited else local entity = mw.wikibase.getEntity(suited) if type(entity)

"table" then local seek = Failsafe.serialProperty or "P348" local vsn = entity:formatPropertyValues(seek) if type(vsn)

"table" and type(vsn.value)

"string" and vsn.value ~= "" then if last and vsn.value

Failsafe.serial then r = false elseif linked then if mw.title.getCurrentTitle.prefixedText

mw.wikibase.getSitelink(suited) then r = false else r = suited end else r = vsn.value end end end end end end if type(r)

"nil" then if not since or since <= Failsafe.serial then r = Failsafe.serial else r = false end end return rend -- Failsafe.failsafe

-- Provide external accesslocal 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 -- p.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 = local r = form(false, options, frame) if not r then options = r = form(true, options, frame) end return r or ""end -- p.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 -- p.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 -- p.countNotEmpty

function p.match(frame) -- Combined analysis of parameters and their values -- Precondition: -- frame -- object; #invoke environment -- Postcondition: -- Return string with error message or "" -- Uses: -- TemplatePar.framing -- mw.text.trim -- mw.ustring.lower -- failure -- form -- TemplatePar.downcase -- figure -- feasible -- fault -- finalize local r = false local options = local k, v, s local params = TemplatePar.framing(frame) 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 ] = 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 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 s = "%s, "%s"" errMiss = string.format(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) end end return r or ""end -- p.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 -- p.valid

p.failsafe = function (frame) -- Versioning interface local s = type(frame) local since if s

"table" then since = frame.args[1 ] elseif s

"string" then since = frame end if since then since = mw.text.trim(since) if since

"" then since = false end end return Failsafe.failsafe(since) or ""end -- p.failsafe

function p.TemplatePar -- Retrieve function access for modules -- Postcondition: -- Return table with functions return TemplateParend -- p.TemplatePar

setmetatable(p,)

return p