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: .split(text: string, pattern: string, plain?: boolean) --	*function: .gsplit(text: string, pattern: string, plain?: boolean) --	*function: .matchNum(s: string, pattern: string, pos?: number|boolean, escape?: boolean) --	*function: .charAt(s: string, pos?: number) --	*function: .styleString(s, css, classes, attrs) --	*function: .escape(s: string) --	*function: .unescape(s: string) --	*function: .includes(s: string, pattern: string, pos?: number|boolean, escape?: boolean) --	*function: .codePointAt(s: string, pos?: number) --	*function: .concat(s: string, ...items: string) --	*function: .indexOf(s: string, searcher: string, pos?: string|boolean, escape?: boolean) --	*function: .lastIndexOf(s: string, searcher: string, pos?: string|boolean, escape?: boolean) --	*function: .endsWith(s: string, searcher: string, pos?: string) --	*function: .startsWith(s: string, searcher: string, pos?: string) --	*function: .matchAll(s: string, pattern: string, escape?: boolean) --	*function: .orderedFormat(formatString: string, ...subsitutions) --	*function: .ucfirst(s: string) --	*function: .lcfirst(s: string) --	*function: .toCamelCase(s: string) --	*function: .bold(s: stringable) --	*function: .italic(s: stringable) --	*function: .underline(s: stringable) --	*function: .reverseSymbol(s: string, reverse?: boolean) --	*function: .pcall(f: function [function to call], ...params?: any [function parameters]) --	*function: .makeImage(name: string | table, table, options: table) --	*function: .makeTitle(s: string, title: string [the title to make the element with], options: table) --	*function: .parseDualArg(arg: stringable [argument to parse]) --	*function: .wrapLink(val: string, dest: string) --	*function: .parseUrlQuery(s: string | table [query to parse]) --	*function: .isStringable(v: any [value to check]) --	*function: .urlQueryToString(s: table [query table to parse], url?: string | table) --	*function: .getUrlParam(url: string | table, param: string, default: any) --	*function: .externalUrl(url: stringable, query: string | table, alt?: stringable) --	*function: .stringUtil(s: stringable) --	*function: .fullUrl(page: string, query: string | table, alt: string | table) --	*function: ._formatShortNumber(number: string|number) --	*function: ._toNumber(str: string) --	*function: .error(msg: string, ...) --	*function: .preprocess(val: string) --	*function: .centerText(s: string) --	*function: .trimWhitespace(s: string) --	*function: .toRoman(s: string) --	*function: .toArabic(s: string) --	*function: .roundNumber(num: number, posistion: number) [ 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

- -- function: .split(text: string, pattern: string, plain?: boolean) -- -- Breaks up the text according to the pattern specified. `plain` may be specified -- to make the pattern be treated as raw text. - function p.split(...) local text, pattern, plain = checkTypeArgs({ 'string', 'string', { 'boolean', nilOk=true } }, ...) local ret = {} for m in p.gsplit(text, pattern, plain) do		ret[#ret+1] = m	end return ret end

- -- function: .unpackedSplit(text: string, pattern: string, plain?: boolean) -- -- Same as `.split` execpt it returns an unpacked table instead of a regular one. - function p.unpackedSplit(...) return unpack(p.split(checkArgs({ 'string', 'string', { 'boolean', nilOk=true } }, ...))) end

- -- function: .gsplit(text: string, pattern: string, plain?: boolean) -- -- Returns an iterator function which iterates over the indices of the split string. - function p.gsplit(...) local text, pattern, plain = checkTypeArgs({ 'string', 'string', { 'boolean', nilOk=true } }, ...) local s, l = 1, text:len return function if s then local e, n = text:find(pattern, s, plain) local ret if not e then ret = text:sub(s) s = nil elseif n < e then -- Empty separator! ret = text:sub(s, e)				if e < l then s = e + 1 else s = nil end else ret = e > s and text:sub(s, e-1) or '' s = n + 1 end return ret end end end - -- 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: .dedent(s: string, isSpaces?: boolean) -- -- Removes uneeded indentation from `s`. Supports both spaces and Tabs. - function p.dedent(s, isSpaces) checkType(1, s, 'string') checkType(2, isSpaces, 'boolean', true) s = s:gsub('^[ \n\r\v]*(.-)[ \n\r\v]*$', '%1'):gsub('\\([%a\"\'%d]+)', {		['t'] = '\t',		['n'] = '\n',		['r'] = '\r',		['v'] = '\v',		['f'] = '\f',		['a'] = '\a',		['b'] = '\b',		['"'] = '\"',		['\] = '\,	})	local lines = mw.text.split(s, '\n')	local overallLen = {}	local isSpaces = true	-- Check spaces	for i, v in ipairs(lines) do		local _, num = v:find('^\t+')		if num then			isSpaces = false		end	end	for i, v in ipairs(lines) do		local _, num		if isSpaces then			_, num = v:find('^ +')		else			_, num = v:find('^\t+')		end		num = isSpaces and num/4 or num		table.insert(overallLen, #overallLen+1, num)	end	local overallLen = math.min(unpack(overallLen) or 0)	for i, v in ipairs(lines) do		lines[i] = v:gsub('^'..(not isSpaces and ('\t'):rep(overallLen) or (' '):rep(overallLen*4)), '')	end	return table.concat(lines, '\n') 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 and - -pos or 1 if pos < 0 then pos = #s+pos elseif pos == 0 then pos = 1 end return (mw.ustring.isutf8(s) and mw.ustring or string).sub(s, pos, pos) 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.charCodeAt(...) local s, pos, useHex = checkTypeArgs({ 'string', { 'number', nilOk=true }, { 'boolean', nilOk=true } }, ...) s = p.charAt(s, pos) local ret = (mw.ustring.isutf8(s) and mw.ustring.codepoint or string.byte)(s) or -1 return useHex and p.toHex(ret) or ret end

- -- function: .toHex(num: number) -- -- Converts a number to a hex number repersentation. - function p.toHex(...) local num = checkTypeArgs({ { 'number', 'string', base=16 } }, ...) return string.format('%x', type(num) == 'string' and tonumber(num, 16) or num) end

- -- function: .unicodeConvert(s: string) -- -- Turns unicode encodings like `\u0F303`, `\u+0F303`, and `\u{FFFFF}` to actual characters. - function p.unicodeConvert(...) local s = checkArgs('string', ...) function replUnicode(code) local success, res = pcall(mw.ustring.char, tonumber(code, 16)) if not success then formattedError('The unicode codepoint "\\u{%s}" is out of range', 4, code) end return res end return (s:gsub('\\u%+?(%x%x?%x?%x?)', replUnicode):gsub('\\u%{%+?(%x+)%}', replUnicode)) end

- -- function: .styleString(s, css, classes, attrs) -- -- Styles text according to the css properties provided. - function p.styleString(...) local s, css, classes, attrs = checkTypeArgs({ { 'string', numberOk=true }, 'table', { 'table', nilOk=true }, { 'table', nilOk=true } }, ...) return p.wrapHtml(s, ' ', table.merge({ style=css, class=classes }, attrs)) end

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

- -- function: .unescape(s: string) -- -- Unescapes any special characters in `s` that are used in patterns. - function p.unescape(...) local s, skipPow = checkTypeArgs({ 'string', { 'boolean', nilOk=true } }, ...) local pat = '(%%([%*%+%-%(%)%[%]%%%$%^%?%.]))' if skipPow then pat = pat:gsub('%%%^', '') end

return s:gsub(pat, '%2') 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: .codePointAt(s: string, pos?: number) -- -- Gets the codepoint for the character at `pos`. - function p.codePointAt(...) local s, pos = checkTypeArgs({ 'string', { 'number', nilOk=true } }, ...) local success, res = pcall(mw.ustring.isutf8(s) and mw.ustring.codepoint or string.byte, p.charAt(s, pos)) if not success then error(res, 2) end return res end

- -- function: .concat(s: string, ...items: string) -- -- Appends each string to `s`. - function p.concat(...) checkTypeArgs({ 'string' }, ...) local ret = {} for i, v in forEachArgs({ 'any', startIndex=1 }, ...) do		v = tostring(v) table.push(ret, v)	end return table.concat(ret) end

- -- function: .indexOf(s: string, searcher: string, pos?: string|boolean, escape?: boolean) -- -- Gets the index of the found string in `s` found by `searcher`. - function p.indexOf(...) local s, searcher, 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 local index, endsAt = s:find(searcher, pos, escape) return index or -1 end

- -- function: .lastIndexOf(s: string, searcher: string, pos?: string|boolean, escape?: boolean) -- -- Gets the last index of the found string in `s` found by `searcher`. - function p.lastIndexOf(...) local s, searcher, pos, escape = checkTypeArgs({ 		'string', 'string', { 'number', 'boolean', nilOk=true }, { 'boolean', nilOk=true } 	}, ...) local index = p.indexOf(s:reverse, searcher:reverse, pos and -pos-1, escape) if index ~= -1 then return -(index-#s) else return index end end

- -- function: .endsWith(s: string, searcher: string, pos?: string) -- -- Checks if `s` ends with `searcher`. Begins searching at `pos` in `s`. - function p.endsWith(...) local s, searcher, pos = checkTypeArgs({ 'string', 'string', { 'number', nilOk=true } }, ...) return not not s:match(p.escape(searcher)..'$', pos) end

- -- function: .startsWith(s: string, searcher: string, pos?: string) -- -- Checks if `s` starts with `searcher`. Begins searching at `pos` in `s`. - function p.startsWith(...) local s, searcher, pos = checkTypeArgs({ 'string', 'string', { 'number', nilOk=true } }, ...) return not not s:match('^'..p.escape(searcher), 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 ret local n = p.matchNum(s, pattern, escape) local mt = {} local function log return mw.logObject(ret) or ret end ret = table.tableUtil({ n=n, origPattern=pattern, input=s }, {		__index = {			print=log,			log=log,			dump=function				return mw.dumpObject(ret)			end,		}	})

local function callMatcher(f, i)		return (function(...)			local t = { ... }			t.index = i			t.n = table.pack(...).n			return t		end)(f) end local matcher = s:gmatch(escape and p.escape(pattern) or pattern) for i = 1, n do		local res = callMatcher(matcher, i)		table.insert(ret, res) end return ret end

- -- function: .orderedFormat(formatString: string, ...subsitutions) -- -- Works like `string.format`, execpt the formatters work with ordered arguments. -- Directives are exactly the same as `string.format`. - function p.orderedFormat(...) local s = checkTypeArgs({ 'string' }, ...) local subsitutions = table.tableUtil local nArgs = select('#', ...) local match = p.matchAll('\127'..s, '[^%%]?%%([%d%.%#+%-]*)(%w?)(%d)') local args = { ... }	if ('\127'..s..'\127'):gsub('%%%%', '%%%%'..'\127'):match('[^%%]%%[^%d%w%%]') then formattedError('options must be escaped or be followed by an option or argument number (at string posistion #%s)', 2, ('\127'..s..'\127'):find('[^%%]?%%[^%d%w]')) elseif (s..'\127'):gsub('%%%%', '%%%%'..'\127'):match('[^%%]%%%a[^%d]') then formattedError('options must have a argument to subsitute from (at string posistion #%s)', 2, (s..'\127'):find('[^%%]?%%%a[^%d]')) end local options = { ['c']='number', ['d']='number', ['i']='number', ['o']='number', ['u']='number', ['x']='number', ['X']='number', ['e']='number', ['E']='number', ['f']='number', ['g']='number', ['G']='number', ['q']='string', ['s']='string', }	local optionsTemp = {} -- Find the greatest index first and check for bad indexes local greatestIndex = 0 for i, v in ipairs(match) do		local new = tonumber(v[3]) if greatestIndex+1 < new then formattedError('options must be sequential (at option #%s)', i, new) end if greatestIndex < new then greatestIndex = new end end -- Check the types of arguments, error if one is missing or is an invalid type for i = 1, greatestIndex do		local index = tonumber(match[i][3]) local option = match[i][2] if match[i] ~= nil and index == i then local arg = args[i+1] if not options[option] and option ~= "" then formattedError('invalid option formatting "%%%s"', 2, option) end if optionsTemp[index] ~= option then formattedError('options may not have conflicting types (at option #%s)', 2, index) end optionsTemp[index] = option if i > nArgs-1 then checkType('no value', i+1, { options[option] or 'string' }) end end end for i = 1, match.n do		if match[i] ~= nil then local index = match[i][3] local value = args[index+1]

checkType(tonumber(index)+1, value, options[match[i][2]] or 'string') subsitutions:push(value) end end local s, _ = (string.gsub('\127'..s, '([^%%]?)%%([%d%.%#+%-]*)(%w?)(%d)', function(before, modifier, option, number) return before..'%'..modifier..(option ~= "" and option or 's')..'\127' end)):gsub('\127', '')

return s:format(unpack(subsitutions)) 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 :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) options = options or {}

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] if name:match('^(.*)%.(%a+)$') then name, extension = name:match('^(.*)%.(%a+)$') end local tpSize = type(size) local size = (tpSize == "number" or (tpSize == 'string' and size:match('^%d-(px)$'))) and table.concat{ tonumber((tostring(size):gsub('px', ''))), "px" } or (tpSize == "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 #p.split(s, '&') > 1 then s = p.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: .methods(s: stringable) -- -- Takes all methods from this module and makes them avaible as methods on the object. - function p.methods(s) local Methods = { __tostring = function(self) return self.value end; __concat = function(self, a, b)			if a == self then self.value = self.value..b			else self.value = a..self.value end return self end; }	function Methods:getValue return self.value end Methods.tostring = Methods.__tostring function Methods:constructor(s) self.value = s	end for k, v in pairs(p) do		if k ~= 'methods' then Methods[k] = function(self, ...) return v(self.value, ...) end Methods['_'..k] = function(self, ...) self.value = v(self.value, ...) return self end Methods['__'..k] = function(self, ...) return self:constructor(v(self.value, ...)) end end end

return table.makeClass(Methods)(s) 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, trim=false }) local s = args[1] or "" local num = p.trim(args[2]) or 1 local sep = p.trim(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, yesno(p.trim(args[4]), false)) end --- -- Template:Repeat Module Access Point --- function p._repeat(s, num, sep, isRoman) checkType(1, s, { "string", "number", "table" }) assertTrue(tonumber(num), 'argument #2 is not convertable to a number', 2) checkType(3, sep, { "string", "number", "table" }, true) local num = tonumber(num) or 1 local start = tonumber(sep) or 1 assertTrue(num < 0 or num < 1e+308, 'number out of range', 2)

local t = {} s, sep = s and s:gsub('\\n', '\n'), sep and sep:gsub('\\n', '\n') for i = start, num, 1 do		t[i] = (type(s) ~= 'string' and p.parseDualArg(s) or s):gsub('([^\\])%$n', '%1'..(isRoman and p._toRoman(i) or i)):gsub('\\%$', '$') 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" and type(k) ~= 'number' 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 tostring(k):match('^([%w%_%:%.%-]+)$') then error(string.format('Invalid HTML attribute name "%s"', k), 2) end k = tostring(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(tostring(str):gsub('[,]', ''))) 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) local num = tostring(num or 0):gsub('[%, ]', '') if not tonumber(num) then return tonumber(lang:parseFormattedNumber(num)) end if #num < 3 then return num end local decimal = num:match('%.([0-9]+)$') num = num:gsub('%.([0-9]+)$', '') num, ret = tostring(num), {} local _, len = num:gsub('%d%d%d', '') for i = len, 1, -1 do		table.insert(ret, 1, num:match('%d%d%d$')) num = num:gsub('%d%d%d$', '') end if num ~= "" then table.insert(ret, 1, num) end return table.concat(ret, ",")..(decimal and '.'..decimal or '') end

- -- function: .error(msg: string, ...) -- -- Returns a error message with the lua call stack. "Template: " -- may be added at the front of the message to link a template to the error message. -- Arguments may be passed as they are in `string.format`. - 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 if msg:match('^[Tt]emplate:(.-):%s*') then template = msg:match('^[Tt]emplate:(.-):%s*') msg = msg:gsub('^[Tt]emplate:(.-):%s*', '') 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 and 'Template Error in '.. p.wrapHtml('Template:'..template, 'strong', {class='error'}) .. ': ' or '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, removeDoubles) checkType(1, s, { 'string' }, true) checkType(2, removeDoubles, { 'boolean' }, true) if not s then return s end s = s:gsub('^%s*(.-)%s*$', '%1') if removeDoubles then s = s:gsub('(%s)%s*', '%1') end return s end p.trim = p.trimWhitespace

-- 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 = p._toArabic(s) if not s or s ~= s then error("Unable to convert to number", 2) end if s == math.huge then error("Unable to convert infinity", 2) 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 elseif not s:match('^[IVXLCDM%d%-]+$') then return nil 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 .. "'", 2) 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 = p.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