Module:String

-- -- -- This module houses helper functions for use on strings. In general this module -- is loaded as the "string" variable in other modules for conistency. -- Most string functions will accept a `table` or `number` besides a string -- using `parseDualArg`. -- See the comments on the functions for futher details. -- The `stringable` type repersents the types `string, `table`, and `number`. -- [ CONTENTS ]- -- *function: ucfirst(s: stringable) -- *function: lcfirst(s: stringable) -- *function: toCamelCase(s: stringable) -- *function: bold(s: stringable) -- *function: italic(s: stringable) -- *function: underline(s: stringable) -- *function: blankCell -- *function: infoNeeded -- *function: reverseSymbol(s: string, reverse?: boolean) -- *function: pcall(func: function, ...params?: any) -- *function: makeImage(name: string, options?: table) -- *function: makeTitle(s: string, title: string, options?: table) -- *function: parseDualArg(s: string | table | number) -- *function: wrapLink(dest: stringable, alt: stringable) -- *function: parseUrlQuery(s: stringable) -- *function: isStringable(s: string | table | number) -- *function: matchAll(s: string, t: table) -- *function: gsubAll(s: string, t: table) -- *function: urlQueryToString(t: table, url?: stringable) -- *function: getUrlParam(param: string) -- *function: externalUrl(url: stringable, query: string | table, alt?: stringable, fragment?: string) -- *function: stringUtil(s: stringable)  -- *function: fullUrl(page: stringable, query: string | table, alt?: stringable, fragment?: string) -- *function: _repeat(s: stringable, delimeter: number, sep?: stringable) -- *function: wrapHtml(innerText: stringable, tag: string, attrs?: table) -- *function: wrapTag(startText: stringable, tag: string | table) -- *function: _formatShortNumber(number: string|number) -- *function: _toNumber(str: string|number) -- *function: _formatNum(num: string|number) -- *function: error(msg: string, ...params?: any) -- *function: centerText(s: string) -- *function: trimWhitespace(s: string) -- *function: _toRoman(s: string) -- *function: _toArabic(s: string) -- *function: _lorem(num: number) -- *function: _delDoubleSpace(text: string) -- [ TODO ]- -- *None yet. - local p = {} local lang = mw.getContentLanguage

local getArgs = require('Module:Arguments').getArgs local yesno = require('Module:Yesno') local libUtil = require('Module:LibraryUtil') local table = require('Module:Table')

table.merge(_G, libUtil)

--Load string library if the module is loaded as "string" in another module p.format = string.format p.rep = string.rep p.gsub = string.gsub p.len = string.len p.sub = string.sub p.char = string.char p.gmatch = string.gmatch p.lower = string.lower p.upper = string.upper p.find = string.find p.match = string.match p.reverse = string.reverse p.byte = string.byte

-- Set 'type' function to custom one to force use in other modules local _type = type type = nil local function __type(v) local tp = _type(v) local __t = (getmetatable(v) or {}).__type if __t ~= nil then if _type(__t) == "function" then return __t(v, tp) else return __t end else return tp	end end _G.type = __type

- -- function: matchNum(s: string, pattern: string, pos?: number|boolean, escape?: boolean) -- -- Gets the number of all matches in `s` from `pattern`. -- If no matches are found, it returns -1. - function p.matchNum(...) local s, pattern, pos, escape = ... checkTypeArgs({ 'string', 'string', { 'number', 'boolean', nilOk=true }, { 'boolean', nilOk=true } }, ...) local t = {} if type(pos) == "boolean" then escape = pos pos = nil end for match in s:gmatch(pattern) do		table.insert(t, match) end return #t == 0 and -1 or #t end

- -- function: charAt(s: string, pos?: number) -- -- Returns the character `s` at the posistion of `pos`. `pos` will default to 1. -- `pos` maybe fed a negative number to count from the rear of `s`. - function p.charAt(...) local s, pos = ... checkTypeArgs({ 'string', { 'number', nilOk=true } }, ...) pos = pos or 1 return s:sub(pos, pos) end

- -- function: escape(s: string) -- -- Escapes any special characters in `s` that are used in patterns. - function p.escape(...) local s = ... checkTypeArgs({ 'string' }, ...) return (s:gsub('([%*%+%-%(%)%[%]%%%$%^%?%.])', function(m) return '%'..m end)) end

- -- function: includes(s: string, pattern: string, pos?: number|boolean, escape?: boolean) -- -- Checks if `s` has `pattern` inside of it. `pos` can be set to where to start searching. - function p.includes(...) local s, pattern, pos, escape = ... checkTypeArgs({ 'string', 'string', { 'number', 'boolean', nilOk=true }, { 'boolean', nilOk=true } }, ...) if type(pos) == "boolean" then escape = pos pos = nil end pos = pos or 1 return not not s:match(escape and p.escape(pattern) or pattern, pos) end

- -- function: matchAll(s: string, pattern: string, escape?: boolean) -- -- Gets all matches of `s` with `string.gmatch` with `pattern` as the matcher. - function p.matchAll(...) local s, pattern, escape = ... checkTypeArgs({ 'string', 'string', { 'boolean', nilOk=true } }, ...) pattern = escape and p.escape(pattern) or pattern local n = p.matchNum(s, pattern, escape) local ret = { n=n, origPattern=pattern } local function callMatcher(f) return (function(...)			return { ... }			end)(f) end local matcher = s:gmatch(escape and p.escape(pattern) or pattern) for i = 1, n do		table.push(ret, callMatcher(matcher)) end return ret end

- -- function: ucfirst(s: string) - function p._ucfirst(s) checkType('ucfirst', 1, s, 'string') if s == nil then s = "" end return s:gsub("(%w)(%w*)", function(a,b) return string.upper(a)..b end) end - -- function: lcfirst(s: string) - function p._lcfirst(s) checkType('lcfirst', 1, s, 'string') if s == nil then s = "" end return s:gsub("(%w)(%w*)", function(a,b) return string.lower(a)..b end) end p.lcfirst = p._lcfirst p.ucfirst = p._ucfirst

-- function: toCamelCase(s: string) -- -- converts a string to camel case

function p.toCamelCase(s) checkType('toCamelCase', 1, s, 'string') return s		:gsub('(%w)(%w*)', 			function(a, b) 				return table.concat{ 					a:upper, 					b,				} 			end		) :gsub('[^%a]+', '') :gsub('^(%w)(%w*)', 			function(a, b) 				return table.concat{ 					a:lower, 					b,				} 			end		) end - -- function: bold(s: stringable) -- -- Makes bold text - function p.bold(s) return table.concat{ , p.parseDualArg(s)  } end

- -- function: italic(s: stringable) -- -- Makes italic text - function p.italic(s) return table.concat{ ', p.parseDualArg(s) ' } end

- -- function: underline(s: stringable) -- -- Makes underlined text - function p.underline(s) return table.concat{ ' ', p.parseDualArg(s) ' ' } end

- -- function: blankCell -- -- Makes a blank table cell, made for use in ` ` tags -- See for CSS and further details - function p.blankCell return p.wrapHtml('&empty;', 'div', {			class={"cellTemplate", "blankCell"}, 			title="This cell is intentionally left blank", 			['data-sort-value']=0		}) end - -- function: infoNeeded -- -- Makes info needed text - function p.infoNeeded return(tostring( mw.html.create('div') :tag('font') :attr({ color="#910000" }) :wikitext('More Info Needed') :done :done :wikitext('') :done )	) end

- -- function: reverseSymbol(s: string, reverse?: boolean) -- -- Reverses the symbols in a given string - function p.reverseSymbol(s, reverse) if type(s) ~= "string" then error(string.format('bad argument #1 to \"reverseSymbol\": string expected, got %s', type(s)), 2) end local replacementSymbols = { ['['] = ']';			[']'] = '[';			['('] = ')';			[')'] = '(';			['{'] = '}';			['}'] = '{';			['<'] = '>';			['>'] = '<';			['/'] = '\\';			['\\'] = '/';		} 	local s = string.gsub(s, '([%[%]{}%(%)<>\\/])', replacementSymbols) if yesno(reverse, false) then return s:reverse else return s	end end --- -- function: pcall(f: function [function to call], ...params?: any [function parameters]) -- -- Returns all values by the called function if no error was raised. -- If there was an error in calling the function, it returns a formatted error message. --- function p.pcall(f, ...) checkType('pcall', 1, f, 'function') local success, response = pcall(f, ...) return success and response or p.error(response) end

--- -- function: makeImage(name: string | table, table, options: table) -- -- Creates a wikitext image element. You can specify the file extention by adding -- `. ` at the end of the file name. -- [ OPTIONS ]-- -- Specify one of these fields below in the `options` argument to configure the -- output of this function. -- -- *size: ? - -- *extension: ? - -- *vertAlign: ? - -- *horizAlign: ? - -- *link: ? - -- *alt: ? - -- *page: ? - -- *class: ? - -- *noLink: ? - -- *format: ? - -- *caption: ? - -- *lang: ? - -- *upright: ? - --- function p.makeImage(name, options) checkType('makeImage', 1, name, {'string', 'table'}) checkType('makeImage', 2, options, {'table'}, true) name = p.parseDualArg(name)

local size, extension, vertAlign, horizAlign = unpack{ options.size or options.s or 21, options.extension or options.ext or options.e or 'png', options.vertalign or options.vertical_align or options.vAlign or options.v_align or options.vl, options.horizalign or options.horizontal_align or options.hAlign or options.h_align or options.hl, }	local link = options.link or options.l	local alt = options.alt or options.a	local page = options.page or options.p	local class = options.class or options.cl	local noLink = options.nolink or options.nl	local format = options.format or options.form or options.f	local caption = options.caption or options.cap or options.c	local lang = options.langauge or options.lang or options.lan local upright = options.upright or options.ur	vertAlign = vertAlign and vertAlign:lower horizAlign = horizAlign and horizAlign:lower format = format and format:lower

local hl_aliases = { ['l']='left'; ['le']='left'; ['lef']='left'; ['left']='left'; ['r']='right'; ['ri']='right'; ['rig']='right'; ['righ']='right'; ['right']='right'; ['c']='center'; ['ce']='center'; ['cen']='center'; ['cent']='center'; ['cente']='center'; ['center']='center'; ['cnt']='center'; ['cntr']='center'; ['centr']='center'; ['cnter']='center'; ['ct']='center'; ['n']='none'; ['no']='none'; ['non']='none'; ['none']='none'; }	local vl_aliases = { ['bl']='baseline'; ['basel']='baseline'; ['baselin']='baseline'; ['bline']='baseline'; ['baseline']='baseline'; ['baslin']='baseline'; ['baseli']='baseline'; ['s']='sub'; ['su']='sub'; ['sub']='sub'; ['sup']='super'; ['supr']='super'; ['sper']='super'; ['spr']='super'; ['super']='super'; ['t']='top'; ['to']='top'; ['top']='top'; ['tt']='text-top'; ['t-t']='text-top'; ['txt-tp']='text-top'; ['text-top']='text-top'; ['texttop']='text-top'; ['txttp']='text-top'; ['text-tp']='text-top'; ['m']='middle'; ['md']='middle'; ['mid']='middle'; ['midle']='middle'; ['mdle']='middle'; ['middle']='middle'; ['midway']='middle'; ['b']='bottom'; ['bottom']='bottom'; ['bt']='bottom'; ['btm']='bottom'; ['bot']='bottom'; ['botom']='bottom'; ['tb']='text-bottom'; ['t-b']='text-bottom'; ['txt-bt']='text-bottom'; ['text-bot']='text-bottom'; ['text-botom']='text-bottom'; ['txt-bottom']='text-bottom'; ['text-bottom']='text-bottom'; }	local format_aliases = { ['t']='thumb'; ['tn']='thumb'; ['thmb']='thumb'; ['thumb']='thumb'; ['thumbnail']='thumb'; ['tmb']='thumb'; ['tmbnl']='thumb'; ['fl']='frameless'; ['frmlss']='frameless'; ['frameless']='frameless'; ['fless']='frameless'; ['framel']='frameless'; ['frmless']='frameless'; ['framelss']='frameless'; ['f']='frame'; ['fr']='frame'; ['fra']='frame'; ['fram']='frame'; ['frame']='frame'; ['frm']='frame'; ['frme']='frame'; ['b']='border'; ['bo']='border'; ['br']='border'; ['bdr']='border'; ['bord']='border'; ['border']='border'; ['bordr']='border'; }	local vertAlign, horizAlign, format = vl_aliases[vertAlign], hl_aliases[horizAlign], format_aliases[format] --customFieldError(extension == '', 'ext', 2, 'makeImage', 'File extension expected')

if name:find('^.*%..+$') then name, extension = name:match('^(.*)%.(.+)$') end local size = (type(size) == "number" or size:match('^%d-(px)$')) and table.concat{ tonumber((tostring(size):gsub('px', ''))), "px" } or type(size) == "table" and #size ~= 0 and table.concat{ tonumber(size[1]), 'x', tonumber(size[2]), 'px' } or size return table.concat{ '[[File:',		--Filename		name,		'.',		--Extension		extension,		--File format		format and '|' or ,		format or ,		--Vertical Alignment		vertAlign and '|' or ,		vertAlign or ,		--Horizontal Alignment		horizAlign and '|' or ,		horizAlign or ,		--Size		'|',		size,		--Link/No Link		(noLink 			and '|link=' 			or table.concat{				link and '|link=' or ,				link or ,			}		),		--Class		class and '|class=' or ,		p.parseDualArg(class or ),		--Alt		alt and '|alt=' or ,		p.parseDualArg(alt or ),		--Page number		tonumber(page) and '|page=' or ,		tonumber(page) and page or ,		--Langauge		lang and '|lang=' or ,		lang or ,		--Upright		(yesno(upright)			and '|upright'		or tonumber(upright)			and table.concat{ '|upright=', tonumber(upright) }		or upright == 			and '|upright='		or 		),		--Caption		caption and '|' or ,		caption or ,		--Finish		']]', } end

--- -- function: makeTitle(s: string, title: string [the title to make the element with], options: table) -- -- Returns a html ` ` element with `s` as its inner text and `title` as -- its `title` attribute. -- ---[ OPTIONS ]-- -- Specify one of the fields below in the `options` argument -- to configure the output of this function. --- function p.makeTitle(s, title, options) checkType('makeTitle', 1, s, {'string', 'table'}) checkType('makeTitle', 2, title, {'string', 'table', 'nil'}) checkType('makeTitle', 3, options, {'table', 'nil'}) local options = options or {} local disableAbbr, ignoreTitleNil = unpack{ options.disableAbbr or options.noAbbr, options.ignoreTitleNil or options.ignoreTitle or options.ignTitle }	if ignoreTitleNil and not title then return s end return p.wrapHtml(s, disableAbbr and ' ' or ' ', { title=p.parseDualArg(title) }) end

--- -- function: parseDualArg(arg: stringable [argument to parse]) -- -- Parses the argument to a string if it is a table using `table.concat` -- and the `sep` field if the table has one. Else, it returns the argument. -- If `arg` is a number, it converts it a string. --- function p.parseDualArg(arg) if arg == nil then return; end checkType('parseDualArg', 1, arg, {'string', 'table', 'number'}) if type(arg) == "number" then arg = tostring(arg) end return type(arg) == 'table' and table.concat(arg, arg.sep or arg.s or '') or arg end p.makeHover = p.makeTitle

--- -- function: wrapLink(val: string, dest: string) -- -- Makes a wikitext link --- function p.wrapLink(page, displayText) checkType('wrapLink', 1, page, {'string', 'table' }, true) checkType('wrapLink', 2, displayText, {'string', 'table' }, true) if not page or page ==  then return  end --customArgError(p.parseDualArg(page):match('[%[%]%{%}<>]'), 'wrapLink', 1, 'Invalid page name %q', page) local page = p.parseDualArg(page) local displayText = p.parseDualArg(displayText) return table.concat{  or , 		displayText and displayText or , 		

} end p.makeLink = p.wrapLink --- -- function: parseUrlQuery(s: string | table [query to parse]) -- -- Parses a url query string into a table in the following format: a format where the parameter name is -- the field, and the value of that field the parameter. --- function p.parseUrlQuery(s) local s = p.isStringable(s) and p.parseDualArg(s) or '' if type(query) == "table" then return setmetatable(mt, s) end s = p.parseDualArg(s):gsub('^[%?&]+', ''):gsub(' ', '+') --Remove preceding unesscessary params if s:match('^(https?:%/%/%S-)(%?.-=.+)$') then url, s = s:match('^(https?:%/%/%S-)(%?.*)$') s = p.parseDualArg(s):gsub('^[%?&]+', ''):gsub(' ', '+') elseif not s:match('^(%w-)=(.*)$') and not s:match('^(https?:%/%/%S-)(%?.*)$') then error(string.format('Syntax error in parsing URL query: invalid URL query %q', s), 2) end local tmp = {} if s == '' then error('Syntax error in parsing URL query: query is empty', 2) end --Detect errors if s:match('%&$') then error('Syntax error in parsing URL query: trailing \"&\" found in input', 2) end local proto = {} --Metatable methods function proto:extend(t, k)		checkType('extend', 1, t, { 'table', 'string' }) if type(t) == "table" and k == nil then for key,v in pairs(t) do				tmp[key] = v			end else tmp[t] = k		end return tmp, url end

function proto:tostring(addUrl) ret = p.urlQueryToString(tmp, addUrl and url or nil) if not addUrl then return ret, url else return ret end end function proto:getParam(k) checkType('getParam', 1, k, {'string', 'number', 'function', 'boolean', 'table'}) if type(k) == 'table' then local ret = {} for i, v in ipairs(k) do				--customFieldError(tmp[v] == nil, i, 1, 'getParam', 'attempted to get a non-existent query parameter %q', v)				ret[v] = tmp[v] end return ret, url else customArgError(tmp[k] == nil, 'getParam', 1, 'attempted to get a non-existent query parameter %q', k)			return tmp[k], url end end function proto:setUrl(s, isInternal) checkType('setUrl', 1, s, 'string') url = isInternal and tostring(mw.uri.fullUrl(s)) or s		return tmp, url end function proto:setFragment(s) checkType('setFragment', 1, s, 'string') proto.fragment = s		url = table.concat{ url, '#', mw.uri.anchorEncode(mw.uri.anchorDecode(s)) } return tmp, url end

local mt = { __index = function(t, k) 			return proto[k] end, }	--Check if the url query is only 1 param long if s:match('^(%w-)=([^&]*)$') then param, value = s:match('^%??(%w-)=(.*)$') tmp[param] = value --Else use multiple method elseif #mw.text.split(s, '&') > 1 then s = mw.text.split(s, '&') for i = 1, #s, 1 do			local param, value = s[i]:match('^(%w-)=(.*)$') --Detect if param is empty if param == "" or not param then error('Syntax error in parsing URL query at index #'..i..': url parameter name expected', 2) end value = type(value) ~= 'nil' and value or '' value = type(value) == 'string' and mw.uri.encode(mw.uri.decode(value)) or value tmp[param] = value end end setmetatable(tmp, mt) --Return return tmp, url end

--- -- function: isStringable(v: any [value to check]) -- -- Checks if the value if the value is able to be turned into a string -- through any method with its data intact. --- function p.isStringable(v) if type(v) == "string" or (type(v) == "table" and #v ~= 0 and v[1]) or type(v) == "number" then return true else return false end end

--- -- function: urlQueryToString(s: table [query table to parse], url?: string | table) -- -- Parses a query table in the following format into a string, where the following -- format can be: a format where the parameter name is the field, and the value -- of that field the parameter. --- function p.urlQueryToString(t, url) --Exit if the string is a query in string format if type(t) == "string" and t:match('^[%?&]+(.-)=(.*)$') then return t	end checkType('urlQueryToString', 1, t, { 'table' }) --Variables local ret, j = {}, 0 for k, v in pairs(t) do		j = j + 1 v = mw.uri.encode(mw.uri.decode(p.isStringable(v) and p.parseDualArg(v) or '')) if j == 1 then ret[#ret+1] = table.concat{ '?', k, '=', v } else ret[#ret+1] = table.concat{ '&', k, '=', v } end end --Return return table.concat{ p.parseDualArg(url or ), table.concat(ret, ) } end

-- -- function: getUrlParam(url: string | table, param: string, default: any) -- -- gets a url query parameter from a string --- function p.getUrlParam(url, param, default) checkType('getUrlParam', 1, url, { 'string', 'table' }) checkType('getUrlParam', 2, param, { 'string' })

local query, url = p.parseUrlQuery(url) return query[param] ~= nil and query[param] or default end

-- -- function: externalUrl(url: stringable, query: string | table, alt?: stringable) -- -- Makes a external link with an optional query and alternate text --- function p.externalUrl(url, query, alt, fragment) checkType('externalUrl', 1, url, { 'string', 'table' }) checkType('externalUrl', 2, query, { 'string', 'table', 'nil' }) checkType('externalUrl', 3, alt, { 'string', 'table', 'nil' }) checkType('externalUrl', 4, alt, { 'string', 'table', 'nil' }) local url = p.parseDualArg(url) query = type(query) == 'string' and p.parseUrlQuery(query) or query query = p.urlQueryToString(query or {}) fragment = p.parseDualArg(fragment) if not url:match('^https?:%/%/') then url = table.concat{ 'https://', url } end return p.wrapHtml{ {			alt and '[' or '', url:gsub(' ', '_'), query or '', alt and ' ' or '', alt and alt or '', alt and ']' or '', },		' ',		{ class="plainlinks" }, } end

function p._externalUrl(frame) local args = getArgs(frame) local url, alt, fragment = args[1], args[2], args[3] local quey = {} for k, v in pairs(args) do		if type(k) == "table" then query[k] = v		end end return p.pcall(p.externalUrl, url, query, alt, fragment) end

--- -- function: stringUtil(s: stringable) -- -- Takes the inputted string and opens a string utility interface with many methods --- function p.stringUtil(s) checkType('stringUtil', 1, s, 'string', true) local s = s ~= nil and s or '' local proto = {} local t = {} t.value = s	local val = t.value local mt = { __index = function(self, index) return proto[index] end, __tostring = function return val end, __concat = function(a, b) 			if t == a then return val..b			elseif t == b then return val..a			end end, }		local tags = { 'a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'head', 'header', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'meta', 'meter', 'nav', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', }	local color_list = { ['aqua'] = "#55FFFF", ['black'] = "#000000", ['blue'] = "#5555FF", ['bronze'] = "#803600", ['orange'] = "#ff921e", ['darkgray'] = "#5C5C5C", ['darkgreen'] = "#009600", ['darkpurple'] = "#AA00AA", ['darkred'] = "#910000", ['gold'] = "#FFAA00", ['gray'] = "#AAAAAA", ['green'] = "#55FF55", ['lightpurple'] = "#FF55FF", ['red'] = "#FF5555", ['silver'] = "#C0C0C0", ['turquoise'] = "#00AAAA", ['white'] = "#FFFFFF", ['yellow'] = "#FFFF55", ['pink'] = "#FF55FF", ['lightpurple'] = "#FF55FF", }	local function addHtmlTag(fName, cb, ...) local vArgs = {...} checkType(fName, 1, vArgs[1], { 'string', 'table' }) if type(vArgs[1]) == "table" then for i = 1, #vArgs, 1 do				v = vArgs[i] checkType(fName, i, v, 'table') local text, name, attrs = unpack{ v.text or v.t or v[1], v.tag or v[2], v.attrs or v.a or v[3], }				mw.log(text, name, attrs) val = cb(text, name, attrs) end else local text, name, attrs = unpack{ vArgs[1], vArgs[2], vArgs[3], }			checkType(fName, 1, text, { 'string', 'table', 'number' }) checkType(fName, 2, name, 'string') checkType(fName, 3, attrs, { 'string', 'table', 'nil' }) val = cb(text, name, attrs) end end local function call(f, ...) local success, res = pcall(f, ...) if not success then error(res:gsub('argument #(%d+)', function(num) return 'argument #'..num-1 end), 3) else return res end end for i, v in ipairs(tags) do		proto[v] = function(self, attrs) val = call(p.wrapHtml, val, v, attrs) return self end end function proto:wrapTag(name, attrs) val = call(p.wrapHtml, val, name, attrs) mw.log(debug.traceback:match("^.-in function '(%w-)'")) return self end function proto:tag(text, name, attrs) if val == '' then val = call(p.wrapHtml, text, name, attrs) else self:wrapTag(val, name, attrs) end return self end function proto:tagBefore(...) addHtmlTag(			'tagBefore',			function(text, name, attrs)				return table.concat{ p.wrapHtml(text, name, attrs), val }			end,			...		) return self end function proto:tagAfter(...) checkType(1, ({ ... })[1], { 'string', 'table' }) addHtmlTag(			'tagAfter', 			function(text, name, attrs)				return table.concat{ val, p.wrapHtml(text, name, attrs) }			end,			...		) return self end function proto:tagPrepend(...) addHtmlTag(			'tagPrepend', 			function(text, name, attrs)				return not val:match('^.+$')					and table.concat{ p.wrapHtml(text, name, attrs), val }					or val:gsub( '^', table.concat{ '', p.wrapHtml(text, name, attrs) } )			end,			...		) return self end function proto:tagAppend(...) addHtml(			'tagAppend', 			function(text, name, attrs)				return not val:match('^.+$')					and table.concat{ val, p.wrapHtml(text, name, attrs) }					or val:gsub( '$', table.concat{ p.wrapHtml(text, name, attrs), '' } )			end,			...		) return self end function proto:error return self:wrapTag(' ', { class='error' }) end function proto:color(color) val = p.wrapHtml(val, ' ', { color=color_list[color:gsub('%s*', ''):lower] or color }) return self end function proto:after(...) checkType(1, ({ ... })[1], { 'string', 'table', 'number' }) val = table.concat{ val, p.parseDualArg({ ... }) } return self end function proto:before(...) checkType('before', 1, ({ ... })[1], { 'string', 'table', 'number' }) val = table.concat{ p.parseDualArg({ ... }), val } return self end function proto:fullUrl(page, query, fragment) val = p.fullUrl(page, query, val, fragment) return self end function proto:externalUrl(page, query, fragment) val = p.externalUrl(page, query, val, fragment) return self end function proto:centerText val = p.centerText(val) return self end function proto:trimWhiteSpace val = p.trimWhitespace(val) return self end function proto:ucfirst val = p.ucfirst(val) return self end function proto:lcfirst val = p.lcfirst(val) return self end function proto:toCamelCase val = p.toCamelCase(val) return self end function proto:wrapLink(dest) val = p.makeLink(dest, val) return self end function proto:yesno(default) val = yesno(val, default) return self end function proto:makeTitle(title, options) val = p.makeTitle(val, title, options) return self end function proto:rep(d, sep) val = call(p._repeat, val, d, sep) return self end function proto:gsub(pattern, repl, limit) val = call(mw.ustring.gsub, val, pattern, repl or '', limit) return self end function proto:gmatch(pattern) val = call(mw.ustring.gmatch, val, pattern) return self end function proto:len val = mw.ustring.len(val) return self end function proto:match(pattern, init) val = call(mw.ustring.match, val, pattern, init) return self end function proto:reverse(reverseSymbols) checkType('reverse', 1, reverseSymbols, 'boolean', true) val = reverseSymbols and p.reverseSymbol(val, true) or val:reverse return self end function proto:sub(i, j)		val = mw.ustring.sub(val, i, j)		return self end function proto:lower val = mw.ustring.lower(val) return self end function proto:upper val = mw.ustring.upper(val) return self end function proto:log mw.log(val) return self end function proto:split(pattern) checkType('split', 1, pattern, 'string') return mw.text.split(val, pattern) end --local t2 = {}	t2.html = {}	setmetatable(t2, htmlMT)	local p2 = {}	function proto:createHtml(tag)		t2.html = mw.html.create(tag):wikitext(val)		return t2	end	function t2:endHtml		t2.html:done		return proto	end

function proto:tostring return val end setmetatable(t, mt) return t end

--- -- function: fullUrl(page: string, query: string | table, alt: string | table) -- -- Makes a full url --- function p.fullUrl(page, query, alt, fragment) if not page then page = '2034890239833333' end if page == true and type(query) == "table" then page, query, alt, fragment = unpack{ query.page or query.p or query[1], query.query or query.quer or query.q or query[2], query.alt or query.a or query[3], query.fragment or query.frag or query.f or query[4], }	end checkType('fullUrl', 1, page, { 'string', 'table' }) checkType('fullUrl', 2, query, { 'string', 'table', 'nil' }) checkType('fullUrl', 3, alt, { 'string', 'table', 'nil' }) local page = p.parseDualArg(page) query = type(query) == 'string' and p.parseUrlQuery(query) or query query = type(query) == 'table' and p.urlQueryToString(query) or query

return table.concat{ alt and '[' or '', tostring(mw.uri.fullUrl(page:gsub(' ', '_'), query)):gsub('2034890239833333', ''), (fragment and fragment ~= ) and '#' or , (fragment and fragment ~= ) and p.parseDualArg(fragment) or , alt and ' ' or '', alt and (p.parseDualArg(alt)) or '', alt and ']' or '', } end

function p._fullUrl(frame) local query = {} local args = getArgs(frame) for arg, value in pairs(args) do		if type(arg) == "string" then query[arg] = value end end return p.pcall(p.fullUrl, args[1], query, args[2], args[3]) end --- -- Template:Repeat -- -- Repeats the given string a specified number of times --- function p.repeatF(frame) local args = getArgs(frame, {removeBlanks = false}) local s = args[1] or "" local num = args[2] or "1" local sep = args[3] if num:match('[^%d%-%+]+') then return p._error(string.format('number expected, got "%s"', tonumber(num))) elseif 0 >= tonumber(num) then return p._error(string.format('repeat delimiter must be greater than 0, got %s', num)) end return p._repeat(s, num, sep) end --- -- Template:Repeat Module Access Point --- function p._repeat(s, num, sep) checkType('repeat', 1, s, { "string", "number", "table" }) customArgError(tonumber(num) == nil, 'repeat', 2, 'argument #2 is not convertable to a number') checkType('repeat', 3, sep, { "string", "number", "table" }, true) local num = tonumber(num) or 1 customArgError(num 1e+308, 'repeat', 2, 'number out of range')

local t = {} for i = 1, num ,1 do		t[i] = type(s) ~= 'string' and p.parseDualArg(s) or s 	end return table.concat(t, sep) end

- -- Html Wrapping Function -- -- Allows for easy creating of html elements in a lua format -- This function is meant to make single html elements. -- Use mw.html.create for more complex elements. - local function parseHtmlAttrs(attrs) local attrsTable = {} for k, v in pairs(attrs) do		if type(k) ~= "string" then error(string.format('Invalid table index (attribute name/string expected, got %s)', type(k)), 2) elseif k == "" then error('Invalid table index (attribute name expected)', 2) elseif not k:match('^([%w%_%:%.%-]+)$') then error(string.format('Invalid HTML attribute name "%s"', k), 2) end k = k:lower if k == "style" and type(v) == "table" then cssTables = {} for key, val in pairs(v) do				cssTables[#cssTables+1] = (type(key) == "string" and key:lower..':' or '')..tostring(val) end v = table.concat(cssTables, ';') elseif k == "style" and not v:match('$') then v = v..';' elseif k == "id" then v = v:gsub(' ', '-') elseif k == "class" and type(v) == "table" then classTables = {} for i, val in pairs(v) do				classTables[#classTables+1] = val:gsub(' ', '-') end v = table.concat(classTables, ' ') elseif k == "href" then v = mw.uri.encode(mw.uri.encode(v)) elseif k == "title" and type(v) == "table" then v = p.parseDualArg(v) end attrsTable[#attrsTable+1] = table.concat{k, '="', p.parseDualArg(v), '"'} end return table.concat(attrsTable, ' ') end

local selfClosingTags = { ['area']=true; ['base']=true; ['br']=true; ['col']=true; ['command']=true; ['embed']=true; ['hr']=true; ['img']=true; ['input']=true; ['keygen']=true; ['link']=true; ['meta']=true; ['param']=true; ['source']=true; ['track']=true; ['wbr']=true; ['path']=true; ['svg']=true; ['defs']=true; }

function p.wrapHtml(val, wrap, attrs) if type(val) == 'table' and not wrap then attrs = val[3] or val.attrs wrap = val[2] or val.tag sep = val.sep or val.s		val = val[1] or val.text end

checkType('wrapHtml', 1, val, { 'string', 'table', 'number' }) checkType('wrapHtml', 2, wrap, 'string') checkType('wrapHtml', 3, attrs, 'table', true) if wrap == '' or not wrap then error('bad argument #2 to \'wrapHtml\' (tag name expected)', 2) end sep = type(val) == 'table' and (val.seperator or val.sep or val.s)		or '' wrap = wrap:lower:gsub('%<[%\\%/]?([^%/%\\]+)[%\\%/]?>', '%1') local selfClosing = selfClosingTags[wrap] if attrs ~= nil then attrs = ' '..parseHtmlAttrs(attrs) else attrs = '' end if wrap:match('([^%a0-9]+)') then error(string.format('Invalid HTML tag name \"<%s>\"', wrap), 2) end return table.concat{ '<',		wrap, attrs, selfClosing and ' />' or '>', type(val) == 'table' and table.concat(val, sep) or val, selfClosing and '' or '', } end

local function _makeTag(text, tag) customArgError(#tag==0, 'wrapTag', 2, 'tag name expected') customArgError(type(tag) ~= "table" and tag:match('([^%a0-9]+)'), 'wrapTag', 2, 'Invalid HTML tag name \"<%s>\"', tag) tag = tag:lower:gsub('%<[%\\%/]?([^%/%\\]+)[%\\%/]?>', '%1') text = p.parseDualArg(text) or '' local selfClosing = selfClosingTags[tag] return table.concat{ '<',		tag, selfClosing and ' />' or '>', text, selfClosing and '' or '', } end

function p.wrapTag(text, tag, attrs) checkType(1, text, { 'string', 'table', 'number' }, true) checkType(2, tag, { 'string', 'table' }) customArgError(#tag==0, 'wrapTag', 2, 'tag name expected') customArgError(type(tag) ~= "table" and tag:match('([^%a0-9]+)'), 'wrapTag', 2, 'Invalid HTML tag name \"<%s>\"', tag) customArgError(attrs, 'wrapTag', 3, 'Tag name contains attributes, please string.wrapHtml', tag) tag = type(tag) ~= "table" and tag:lower:gsub('%<[%\\%/]?([^%/%\\]+)[%\\%/]?>', '%1') or tag text = p.parseDualArg(text) or '' local selfClosing = selfClosingTags[tag] if type(tag) == "table" then for i, tg in ipairs(tag) do			text = _makeTag(text, tg) end end

return type(tag) == "table" and text or table.concat{ '<',		tag, selfClosing and ' />' or '>', text, selfClosing and '' or '', } end

- -- function: _formatShortNumber(number: string|number) -- -- This function takes a number value and returns a short version of it -- Example: 10000 = 10k - function p._formatShortNum(number) local steps = { {1,""},		 --		{1e3,"k"},	 --thousand {1e6,"M"},	 --million {1e9,"B"},	 --billion {1e12,"T"},	 --trillion {1e15,"Qu"},	--quadrillion {1e18,"Qi"},	--quintillion {1e21,"Se"},	--sextillion {1e24,"Sp"},	--septillion {1e27,"O"},	 --octillion {1e30,"N"},	 --nonillion {1e33,"De"},	--decillion {1e36,"UDe"},  --undecillion {1e39,"DDe"},  --duodecillion {1e42,"TDe"},  --tredecillion {1e45,"QaDe"}, --quattuordecillion {1e48,"QiDe"}, --quindecillion {1e51,"SeDe"}, --sexdecillion {1e54,"SpDe"}, --septemdecillion {1e57,"ODe"},  --octodecillion {1e60,"NDe"},  --novemdecillion {1e63,"v"},	 --vigintillion {1e66,"Uv"},	--unvigintillion {1e69,"Dv"},	--duovigintillion {1e72,"Tv"},	--trevigintillion {1e75,"Qav"},  --quattuorvigintillion {1e78,"Qiv"},  --quinvigintillion {1e81,"Sev"},  --sexvigintillion {1e84,"Spv"},  --septemvigintillion {1e87,"Ov"},	--octovigintillion {1e90,"Nv"},	--novemvigintillion }	for _,b in ipairs(steps) do		if b[1] <= number+1 then steps.use = _ end end local result = string.format("%.1f", number / steps[steps.use][1]) if tonumber(result) >= 1e3 and steps.use < #steps then steps.use = steps.use + 1 result = string.format("%.1f", tonumber(result) / 1e3) end result = string.sub(result,0,string.sub(result,-1) == "0" and -3 or -1) return result .. steps[steps.use][2] end - -- function: _toNumber(str: string) - function p._toNumber(str) -- check normal string -> number local num = tonumber(lang:parseFormattedNumber(str)) if num then return num end -- check if number short form local steps = { {1e3,"k"}, {1e6,"m"}, {1e9,"b"}, {1e12,"t"}, {1e15,"qa"}, {1e18,"qi"}, {1e21,"se"}, {1e24,"sp"}, {1e27,"o"}, {1e30,"n"}, {1e33,"de"} }	for i,step in pairs(steps) do		num = string.match( str.lower(str), "([%d%.,]+)"..step[2] ) if num then num = num * step[1] break end end if num then return num end -- else invalid return nil end

- -- Template:FormatNum - function p.formatNum( frame ) local args = getArgs(frame) return p._formatNum( args[1] ) end

- -- Template:FormatNum Module Access point - function p._formatNum(num) num = tonumber(lang:parseFormattedNumber(num)) return lang:formatNum(num) end

- -- function: error(msg: string, ...) -- -- Returns a formatted error message - function p.error(msg, ...) local level local vArgs = { ... }	for i, v in pairs(vArgs) do		if type(v) == "number" then level = v			table.remove(vArgs, i)			break; end end for i, v in pairs(vArgs) do		vArgs[i] = p.parseDualArg(v) end local tmp = msg and (#vArgs ~= 0 and string.format(msg, unpack(vArgs)) or msg) or nil local errorMsg = debug.traceback(tmp or 'Unknown error', level or 0) local msg = tmp or 'Unknown error' if msg:match('bad argument.*') then msg = msg:gsub('^.-%((.-)%).-$', '%1') end local errorMsg = (errorMsg:match('^(M?o?d?u?l?e?:?[%w%.%(%)%:]+):(%d+)') and '' or 'Lua Error: ')..errorMsg :gsub('^(M?o?d?u?l?e?:?[%w%.%(%)%:%/%_ %(%)]+):(%d+)', function(name, line)			return table.concat{ 				'Lua Error in ', 				name:match('Module:') and p.fullUrl( name, { action="edit" }, { name, '&#x0200b;:', line }, { 'mw-ce-l', line } ) or name, 				' at line ',				line 			}		end, 1) :gsub('\n\t([%w%.%(%)%:%/%_ %(%)%[%]]+):', '\n\t%1:') :gsub('\n', ' ') :gsub(			'Module:([%w%.%(%)%:%/%_ %(%)]+):(%d+)', 			function(module, line)				return p.fullUrl( { 'Module:', module }, { action="edit" }, { 'Module:', module, ':', line }, { 'mw-ce-l', line } )			end		) local ret = table.concat{ p.wrapHtml{ {				'Template Error: ', msg, },			' ',			{				class={ "error", "mw-customtoggle-callstack", },			},		},		p.wrapHtml{ errorMsg, ' ',			{				id="mw-customcollapsible-callstack", class={ "mw-collapsible", "mw-collapsed", },				style={ ["background-color"]="#251214"; ["line-height"]="14px"; ["overflow"]="auto"; ["padding"]="8px"; ["word-wrap"]="normal"; ["display"]="block"; ["white-space"]="pre"; ["margin"]="1em 0px"; ["font-family"]="monospace"; ["tab-size"]=4; }			},		},		'',	}	mw.addWarning(ret) return ret end p.templateError = p.error p._error = p.error

- -- function: preprocess(val: string) -- -- parse wikitext into html - function p._preprocess(val) local frame = mw.getCurrentFrame return frame:preprocess(val) end p.preprocess = p._preprocess

- -- function: centerText(s: string) -- -- aligns text to the center of an element - function p.centerText(s) return p.wrapHtml(s, 'div', {style="text-align: center;"}) end

- -- function: trimWhitespace(s: string) -- -- Trims the whitespace from the string - function p.trimWhitespace(s) return s:gsub('^%s*(.-)%s*$', '%1'):gsub('(%s)%s*', '%1') end

-- function: toRoman(s: string) -- original source: https://gist.github.com/efrederickson/4080372 -- Symbol  Value I		1 V		5 X		10 L		50 C		100 D		500 M		1000 If a lesser number comes before a greater number (e.g. IX), then the lesser number is subtracted from the greater number (IX -> 9, 900 -> CM) So, Symbol   Value IV	   4 IX	   9 XL	   40 XC	   90 CD	   400 CM	   900 LM	   950 VX is actually valid as 5, along with other irregularities, such as IIIIIV for 8 Copyright (C) 2012 LoDC - local map = { I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000, } local numbers = { 1, 5, 10, 50, 100, 500, 1000 } local chars = { "I", "V", "X", "L", "C", "D", "M" } function p._toRoman(s) --s = tostring(s) s = tonumber(s) if not s or s ~= s then error"Unable to convert to number" end if s == math.huge then error"Unable to convert infinity" end s = math.floor(s) if s <= 0 then return s end local ret = "" for i = #numbers, 1, -1 do		local num = numbers[i] while s - num >= 0 and s > 0 do ret = ret .. chars[i] s = s - num end --for j = i - 1, 1, -1 do		for j = 1, i - 1 do			local n2 = numbers[j] if s - (num - n2) >= 0 and s 0 and num - n2 ~= n2 then ret = ret .. chars[j] .. chars[i] s = s - (num - n2) break end end end return ret end - -- Template:ToRomanNum -- -- Convert Numbers to roman numerals - function p.roman(frame) local args = getArgs(frame) local num = args[1]; local min = tonumber(args["min"]) or 1; local max = tonumber(args["max"]) or 9999999; -- if already a roman numeral, convert to number if string.match(num:upper, "^[IVXLCDM]+$") then num = p._toArabic(num) end num = tonumber(num) if tonumber(num) then if num >= min and num <= max then return p.wrapHtml(p._toRoman(num), 'span', {style="font: bold 100% times new roman;"}) else -- if we set a min/max and the number doesn't fall between it then it's invalid return "[out of range]" end end return "INVALID INPUT" end - -- function: toArabic(s: string) -- -- converts roman numerals to regular numbers - function p._toArabic(s) s = tostring(s):upper local ret = 0 local i = 1 if s:match('^%d+$') then return tonumber(s) elseif s == '' or s == 'NIL' then return 0 end while i <= s:len do   --for i = 1, s:len do        local c = s:sub(i, i)        if c ~= " " then -- allow spaces local m = map[c] or error("Unknown Roman Numeral '" .. c .. "'") local next = s:sub(i + 1, i + 1) local nextm = map[next] if next and nextm then if nextm > m then -- if string[i] < string[i + 1] then result += string[i + 1] - string[i] -- This is used instead of programming in IV = 4, IX = 9, etc, because it is               -- more flexible and possibly more efficient ret = ret + (nextm - m)                   i = i + 1 else ret = ret + m               end else ret = ret + m           end end i = i + 1 end return tonumber(ret) end

function p._splitNameAndTier(str) local out = {} if str:find("%s[%dIVXLCDMivxlcdm]+$") then out[1], out[2] = str:match("^(.+)%s([%dIVXLCDMivxlcdm]+)$") else out[1] = str out[2] = nil end return out end

- -- Template: Skydate -- -- Converts an ingame date to a date compatible with skydate.js - function p.skydate(str) local conversions = { ['early spring'] = 'ESP', ['^spring'] = 'SP', ['late spring'] = 'LSP', ['early summer'] = 'ESU', ['^summer'] = 'SU', ['late summer'] = 'LSU', ['early autumn'] = 'EAU', ['^autumn'] = 'AU', ['late autumn'] = 'LAU', ['early fall'] = 'EAU', ['^fall'] = 'AU', ['late fall'] = 'LAU', ['early winter'] = 'EWI', ['^winter'] = 'WI', ['late winter'] = 'LWI', ['(%d)st'] = '%1', ['(%d)nd'] = '%1', ['(%d)rd'] = '%1', ['(%d)th'] = '%1', }	local str = getArgs(frame)[1] or '' for k, v in pairs(conversions) do		str = str:gsub(k, v); end return str end

- -- Template: Lorem -- -- Classic lorem ispum - function p.lorem(frame) local args = getArgs(frame) local num = args[1] if not num then num = "1p" end return p._lorem(num) end - -- Template: Lorem module access point - function p._lorem(num) lorem = require('Module:String/Lorem') local suffix suffix = num:match("^%d*(%a)$") or nil num = num:match("^(%d*)%a?$") or error("No number provided") num = tonumber(num) if not (suffix == nil or suffix == "w" or suffix == "p") then p._error('invalid suffix', suffix) end local str = {} if suffix == nil then suffix = "w" end if suffix == "p" then if num > 10 or num == 0 then return p._error("Invalid number. Maximum number accepted: 10") end str[#str+1] = lorem[i] for i=2,num,1 do			str[#str+1] = "\n"..lorem[i] end elseif suffix == "w" then if num > 1008 or num == 0 then return p._error("Invalid number. Maximum number accepted: 10") end full = lorem["full"] full = mw.text.split(full, "[%s\n]") str[#str+1] = full[i] for i=1,num,1 do			str[#str+1] = " "..full[i] end end return table.concat(str) end - -- function: roundNumber(num: number, posistion: number) -- -- rounds numbers nicely - function p.roundNumber(num, position) if not position then position = 0 end position = 10^position num = num * position num = math.floor(num + 0.5) num = num / position return num end

- -- function _delDoubleSpace(text: string) -- -- Remove duplicate spaces form strings - function p._delDoubleSpace(text) text = text:gsub("(%s)%s*", "%1") return text end

- -- Template:Lower - function p._lower(frame) local args = getArgs(frame) local s = args[1] if not s then s = "" end return s:lower end - -- Template:Upper - function p._upper(frame) local args = getArgs(frame) local s = args[1] or "" return s:upper end

function p.warning(frame) return mw.addWarning(getArgs(frame)[1]) end

-- Finish module return p