--[=[ TemplatePar 2015-02-14 Template parameter utility * assert * check * count * countNotEmpty * downcase * match * valid * verify * TemplatePar ]=]
-- Module globalslocal TemplatePar = local MessagePrefix = "lua-module-TemplatePar-"local L10nDef = L10nDef.en = L10nDef.de = local Patterns = 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 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 -- FileMedia.isType local r if attempt and attempt ~= "" then local lucky, FileMedia = pcall(require, "Module:FileMedia") if type(FileMedia)
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 rend -- 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)
"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 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: -- > 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 s = mw.text.trim(options.key) else s = "+" end if s ~= "*" then scan = Patterns[s ] end if type(scan)
"n" 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
"!=" 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
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
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)
"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("%%!", "|") sub = sub:gsub("%%%(%(", "") 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, 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)
"" or show
"-") s = opt.preview if type(s)
"" then if s
"*" 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 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
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)
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 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 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)
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, 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 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, frame) 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, 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
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
-- 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 -- .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 -- .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 = local k, v, s local params = 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 errValue = false 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 -- .valid
function p.TemplatePar -- Retrieve function access for modules -- Postcondition: -- Return table with functions return TemplateParend -- .TemplatePar
return p