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)
"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)
"function" then bib = bib[access ] if type(bib)
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" 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)
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
"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)
"-" 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)
"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)
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)
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
"table" then local d = DateTime(attempt) if type(d)
"datetime/y" then if d.year then r = false if accept:sub(1, 11)
"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)
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 s = mw.text.trim(options.key) else s = "+" end if s ~= "*" then scan = Local.patterns[s ] end if type(scan)
"n" 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
"!=" 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
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
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)
"string" then if v:match("^%s*$") then v = false end else v = false end if type(k)
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 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
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)
"" or show
"-") s = opt.preview if type(s)
"" then if s
"*" 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 space = string.format(".*%%s%d%%s.*", ns) local spaces = string.format(" %s ", opt.errNS) if spaces:match(space) then link = true end elseif st
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)
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 ]
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
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)
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 r = else r = extend end else if #extend
"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 local got = fetch(light, options) if type(got)
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 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)
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)
"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.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
"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)
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
0 then r = false end elseif r
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.getParent)
"table" 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 link = (since
"wikidata" then local item = Failsafe.item since = false if type(item)
"table" then local seek = Failsafe.serialProperty or "P348" local vsn = entity:formatPropertyValues(seek) if type(vsn)
"string" and vsn.value ~= "" then if last and vsn.value
mw.wikibase.getSitelink(suited) then r = false else r = suited end else r = vsn.value end end end end end end if type(r)
-- 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)
"" 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
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
"string" then since = frame end if since then since = mw.text.trim(since) if since
function p.TemplatePar -- Retrieve function access for modules -- Postcondition: -- Return table with functions return TemplateParend -- p.TemplatePar
setmetatable(p,)
return p