-- ----------------------------------  
Lua module implementing the template.  
A merger of the functionality of three templates: , and  
----------------------------< D E P E N D E N C I E S >------------------------------------------------------  
require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;  
----------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------  
local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err_warn_msgs = {}; -- error and warning messages
local excepted_pages = {};
local month_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s_text = {}; -- table of static text strings used to build final rendering
local uncategorized_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized_subpages = {}; -- list of subpages that should not be categorized  
----------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------  
local non_western_digits; -- boolean flag set true when data.digits.enable is true
local this_page = mw.title.getCurrentTitle;  
local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data  
----------------------------< S U B S T I T U T E >----------------------------------------------------------  
Populates numbered arguments in a message string using an argument table.  
local function substitute (msg, args)
return args and mw.message.newRawMessage (msg, args):plain or msg;
end  
----------------------------< tableLength >-----------------------  
Given a 1-D table, return number of elements  
local function tableLength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end  
--[=[-------------------------< M A K E _ W I K I L I N K >----------------------------------------------------  
Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.  
]=]  
local function make_wikilink (link, display, no_link)
if nil == no_link then
if link and ('' ~= link) then
if display and ('' ~= display) then
return table.concat ({*, display, *});
else
return table.concat ({*, link, *});
end
end
return display or ''; -- link not set so return the display text  
else -- no_link
if display and ('' ~= display) then -- if there is display text
return display; -- return that
else
return link or ''; -- return the target article name or empty string
end
end
end  
--[[--------------------------< createTracking >-----------------------  
Return data in track[] ie. tracking categories  
local function createTracking
if not excepted_pages[this_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
if uncategorized_namespaces[this_page.nsText] then
return ''; -- this page not to be categorized so return empty string
end
for _,v in ipairs (uncategorized_subpages) do -- cycle through page name patterns
if this_page.text:match (v) then -- test page name against each pattern
return ''; -- this subpage type not to be categorized so return empty string
end
end
end  
local out = {};
if tableLength(track) > 0 then
for key, _ in pairs(track) do -- loop through table
table.insert (out, make_wikilink (key)); -- and convert category names to links
end
end
return table.concat (out); -- concat into one big string; empty string if table is empty  
end  
----------------------------< inlineError >-----------------------  
Critical error. Render output completely in red. Add to tracking category.  
This function called as the last thing before abandoning this module  
local function inlineError (msg, args)
track[categories.error] = 1
return table.concat ({
'<span>Error in ', -- open the error message span
config.tname, -- insert the local language template name
' template: ',
substitute (msg, args), -- insert the formatted error message
'.</span>', -- close the span
createTracking -- add the category
})
end  
----------------------------< inlineRed >-----------------------  
Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.  
local function inlineRed(msg, trackmsg)
if trackmsg == "warning" then
track[categories.warning] = 1;
elseif trackmsg == "error" then
track[categories.error] = 1;
end  
return '<span>' .. msg .. '</span>'
end  
----------------------------< base62 >-----------------------  
Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr  
local function base62( value )
local r = 1 -- default return value is input value is malformed  
if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
return; -- nil return when value contains extraneous characters
end  
local n = #value -- number of characters in value
local k = 1
local c
r = 0
for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
c = value:byte( i, i )
if c >= 48 and c <= 57 then -- character is digit 0-9
c = c - 48
elseif c >= 65 and c <= 90 then -- character is ascii a-z
c = c - 55
else -- must be ascii A-Z
c = c - 61
end
r = r + c * k -- accumulate this base62 character's value
k = k * 62 -- bump for next
end -- for i  
return r
end  
----------------------------< D E C O D E _ D A T E >--------------------------------------------------------  
Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else  
local function decode_date (date_str)
local patterns = {
['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
};  
local t = {};  
if non_western_digits then -- this wiki uses non-western digits?
date_str = mw.ustring.gsub (date_str, '%d', digits); -- convert this wiki's non-western digits to western digits
end  
if date_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
return date_str, 'iso';
end  
for k, v in pairs (patterns) do
local c1, c2, c3 = mw.ustring.match (date_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold  
if c1 then -- set on match
t = { -- translate unspecified captures to y, m, and d
[patterns[k][2]] = c1, -- fill the table of captures with the captures
[patterns[k][3]] = c2, -- take index names from src_pattern table and assign sequential captures
[patterns[k][4]] = c3,
};
if month_num[t.m] then -- when month not already a number
t.m = month_num[t.m]; -- replace valid month name with a number
else
return nil, 'iso'; -- not a valid date form because month not valid
end  
return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
end
end
return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end  
----------------------------< makeDate >-----------------------  
Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
mdy, dmy, iso, ymd  
on entry, year, month, day are presumed to be correct for the date that they represent; all are required