Page Tools: Create Sandbox View subpages View links
Module Links: Create talk Latest Diff Page Logs
This module Houses general purpose string functions for ease of use in other modules.
Loading the module
To load this module, Add this line of code to the start of your module:
local string = require('Module:String')
All methods described below will be available under the loaded table.
Methods
All methods are available below as fields under the required object name.
- NOTE: All functions described here are NOT part of the original lua string library. they are stored under this object for simplicity.
string.ucfirst
string.ucfirst(<string>(string))
This method returns <string> with the first letter of every word to upper case.
string.lcfirst
string.lcfirst(<string>(string))
This method works exactly like string.ucfirst(), but with every letter of the first word being uppercase.
string.blankCell
string.blankCell()
This method returns a HTML blank cell like {{blank cell}}.
string.infoNeeded
string.infoNeeded()
This method returns a info needed text like {{InfoNeeded}}.
string.reverseSymbol
string.reverseSymbol(<string>(string), <reverse>(boolean))
This method returns a copy of <string> with certain characters like [ or { replaced with their flipped counterparts. It also reverses the returned string with string.reverse() if the parameter <reverse> is set to true.
string.pcall
string.pcall(<function>(function), ...(any, NR))
This method calls <function> with the arguments as set in <...>. If no error is raised in calling the function, all values returned by <function> are returned. Else, it returns the error message as a template error.
string.makeTitle
string.makeTitle(<s>(string), <title>(string), <options>(table, NR))
This method returns a HTML <abbr> element (<span> if <options.disableAbbr> is set to true) with the title attribute set to the text of <title> and the inner text as <s>. If <options.ignoreTitleNil> is set to true, <title> is not required (otherwise required).
string.parseDualArg
string.parseDualArg(<val>(string, table, number))
This method returns the value if it is a string, if it is a table, it concatenates it with table.concat() with an empty string as a default parameter or the sep field in the provided table, if it is a number, it converts it to a string.
string.wrapLink
string.wrapLink(<page>(string), <alt>(string, NR))
This method returns a wikilink with <page> as the link target, and <alt> as the display text.
string.externalUrl
string.externalUrl(<url>(string), <query>(string, NR), <alt>(string, NR))
This method returns an external link to a external website provided by <url> with <query> as an optional URL query and optional alternate text provided by <alt>.
string.fullUrl
string.fullUrl(<page>(string), <query>(string, NR), <alt>(string, NR))
This method returns an internal URL to a page provided by <page> with <query> as an optional URL query and optional alternate text provided by <alt> in a similar fashion to string.externalUrl().
string._repeat
string._repeat(<s>(string), <num>(number), <sep>(string, NR))
This method takes the string in <s> and repeats it according to the delimiter <num> separated by the text specified in <sep> if it is omitted.
string.wrapHtml
string.wrapHtml(<s>(string), <tag>(string), <attrs>(table, NR))
Alternative Calls:
- wrapHtml({ <s>(string), <tag>(string), <attrs>(table, NR) })
- wrapHtml({ text = <s>(string), tag = <tag>(string), attrs = <attrs>(table, NR) })
This method is provided as a light-weight alternative to mw.html.create(). If you need to make more complex elements like tables, use mw.html.create().
The function returns <s> wrapped by the html element with the tag name specified in <tag> with the attributes in <attrs> if the parameter is omitted.
There are special cases for attributes:
- Class: - The class names for the element can be just a string, or it can be an table of classes. If the input is a table, the keys in the arrays are joined by spaces.
- Style: - The css rule can be just a string, or it can be a table. In the case of the attribute being a table, the attribute is a CSS rule with the table key as the CSS property name, and the value as the CSS value of the omitted property.
Examples
- Regular example
string.wrapHtml('Foo', 'span', {style="color:red"})
Produces:
Foo
- Table function call
string.wrapHtml{
text='Foo',
tag='span',
style='color: red; font-weight: bold;'
}
Produces:
Foo
- Special Attribute values
string.wrapHtml{
text='Foo',
tag='span',
style={
color="red";
font-weight="bold";
}
}
Produces:
Foo
string._formatShortNum
string._formatShortNum(<num>(string))
This method returns a short formatted version of <num> like 10k.
string._toNumber
string._toNumber(<num>(string))
This method converts a string (can be a formatted number returned by the method above) to a number using tonumber().
string._formatNum
string._formatNum(<num>(string))
This method converts <num> to a formatted number using mw.language.getContentLangauge():formatNum().
string.error
string.error(<msg>(string), ...(string, number))
This method returns a string as a template error with <msg> and <...> as arguments to string.format()
string.centerText
string.centerText(<s>(string))
This method returns a <div> element that aligns the text to the center of an element using string.wrapHtml().
string._toRoman
string._toRoman(<string>(string))
This method returns a copy of <string> with numbers converted to roman numerals.
string._toArabic
string._toArabic(<string>(string))
This method returns a copy of <string> with roman numerals converted to numbers. Basically it is a oposite of string._toRoman().
string._splitNameAndTier
string._splitNameAndTier(<str>(string))
This method extracts a name and tier from the target string <str>. It returns a table with 2 values, the first being the name, the second being the tier.
string._lorem
string.lorem(<num>(number))
Currently not working!
This method returns a lorem sample text based on the length of <num>.
string.roundNumber
string.roundNumber(<num>(number), <posistion>(number, NR))
This method returns <num> rounded to the number of decimal places specified by <num>.
<num> defaults to 1.
string._delDoubleSpace
string.delDoubleSpace(<s>(string))
This method returns a copy of <s> with all duplicate spaces removed using the following lua pattern: (%s)%s* with the replacement being %1.
Submodules
See Also
| Hypixel SkyBlock Wiki Standard Lua Libraries (hsw/stdll) v · d · e | |
| Type | Libraries |
|---|---|
| Module Loading Utilities |
|
| General Utilities |
|
| Meta Modules |
|
| Object-oriented |
|
| Caching | |
Module Code
--<pre>--------------------------------------------------------------------------
-- 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: allMatched(s: string, t: table)
-- * function: anyMatched(s: string, t: table)
-- * function: gsubAll(s: string, ...)
-- * function: gsubMulti(s: string, replacements: table)
-- * 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, ...<string.format() arguments>)
-- * 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)
-- * function: sublength(frame: table)
-- * function: matchTemplate(frame: table)
----------------[ TODO ]-----------------
-- *None yet.
---------------------------------------------------------------------------------
local p = {}
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local libU = require('Module:LibU')
local table = require('Module:Table')
local checkType, checkTypeLight, checkArgs, assertTrue, assertFalse, bind =
libU.checkType, libU.checkTypeLight, libU.checkArgs, libU.assertTrue, libU.assertFalse, libU.bind
-- Load string library if the module is loaded as "string" in another module
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
p.format = string.format
---------------------------------------------------------------------------------
-- 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 = checkArgs({ 'string', 'string', { 'boolean', nilOk = true } }, ...)
local ret = {}
for m in p.gsplit(tostring(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 = checkArgs({ 'string', 'string', { 'boolean', nilOk = true } }, ...)
text = tostring(text)
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 = checkArgs({
'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)
checkTypeLight('dedent', 1, s, 'string')
checkTypeLight('dedent', 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: remove(s: string, pattern: string|table, index?: number)
--
-- Removes the specified match from the string and returns it.
--------------------------------------------------------------------------------
function p.remove(...)
local s, pattern, index = checkArgs({ 'string', { 'string', 'table' }, { 'number', nilOk = true } }, ...)
local oldString = s
local i, match = 1
if type(pattern) == 'table' then
while not match do
if i > #pattern then
match = {}
break
end
match = oldString:match(pattern[i], index)
if match then
s = s:gsub(pattern[i], '', 1)
match = { oldString:match(pattern[i], index) }
end
i = i+1
end
else
match = { oldString:match(pattern, index) }
s = s:gsub(pattern, '', 1)
end
return unpack{ s, unpack(match) }
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 = checkArgs({ '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 = checkArgs({ '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 = checkArgs({ { '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{%x}" is out of range', 4, p.toHex(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 = checkArgs({ { 'string', numberOk = true }, 'table', { 'table', nilOk = true }, { 'table', nilOk = true } }, ...)
return p.wrapHtml(s, '<font>', 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 = checkArgs({ '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 = checkArgs({ '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 = checkArgs({
'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 = checkArgs({ '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(...)
checkArgs({ '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 = checkArgs({
{ 'string', numberOk = true }, '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 = checkArgs({
'string', 'string', { 'number', 'boolean', nilOk = true }, { 'boolean', nilOk = true }
}, ...)
local index = p.indexOf(s:reverse(), searcher, 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 = checkArgs({ '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 = checkArgs({ '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 = checkArgs({ '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 = setmetatable({ 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: allMatched(s: string, t: table)
--
-- Attempt to match each pattern in the table/subsequent args,
-- then return true if all matches exists
---------------------------------------------------------------------------------
function p.allMatched(s, ...)
checkType('allMatched', 1, s, 'string')
local t = { ... }
if type(t[1]) == 'table' then
t = t[1]
end
for _, v in ipairs(t) do
if not(s:match(v)) then
return false
end
end
return true
end
---------------------------------------------------------------------------------
-- function: anyMatched(s: string, t: table)
--
-- Attempt to match each pattern in the table/subsequent args,
-- returns true if any match exists
---------------------------------------------------------------------------------
function p.anyMatched(s, ...)
checkType('anyMatched', 1, s, 'string')
local t = { ... }
if t[1] == 'table' then
t = t[1]
end
for _, v in ipairs(t) do
if s:match(v) then
return true
end
end
return false
end
---------------------------------------------------------------------------------
-- function: gsubAll(s: string, ...)
--
-- For each pair of values in subsequent args, get pattern and replacement string
-- Apply all replacements on a string
---------------------------------------------------------------------------------
function p.gsubAll(s, ...)
checkType('gsubAll', 1, s, 'string')
local t, i = { ... }, 1
if t[1] == 'table' then
t = t[1]
end
t = table.flat(t)
if #t % 2 ~= 0 then
error('[gsubAll] Pattern/Replacement pairs mismatch (Odd number arguments)')
end
while i * 2 <= #t do
s = s:gsub(t[i*2-1], t[i*2])
i = i + 1
end
return s
end
---------------------------------------------------------------------------------
-- function: gsubMulti(s: string, replacements: table)
--
-- Takes multiple patterns and replacements in a table and calls `:gsub()` on `s`.
---------------------------------------------------------------------------------
function p.gsubMulti(...)
local s, replacements = checkArgs({ 'string', 'table', { 'number', nilOk = true } }, ...)
for search, repl in pairs(replacements) do
s = s:gsub(search, type(search) == 'number' and '' or repl)
end
return s
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 = checkArgs({ 'string' }, ...)
local subsitutions = table.tableUtil()
local nArgs = select('#', ...)
local match = p.matchAll('\127' .. s, '[^%%]?%%([%d%.%#+%-]*)(%w-)(%d)%{?([^}]+)%}?')
local args = { ... }
local values = 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 optionTypes = {
['c'] = 'number',
['d'] = 'number',
['i'] = 'number',
['o'] = 'number',
['x'] = 'number',
['X'] = 'number',
['e'] = 'number',
['E'] = 'number',
['f'] = 'number',
['g'] = 'number',
['G'] = 'number',
['u'] = 'number',
['un'] = 'number',
['q'] = 'string',
['s'] = 'string',
['l'] = 'string',
['uc'] = 'string',
['tm'] = 'string',
['tg'] = 'string',
['t'] = 'table',
}
local customOptions = {
['t'] = p.parseDualArg,
['l'] = string.lower,
['uc'] = string.upper,
['tm'] = p.trim,
['tg'] = function(value, ...)
return p.wrapTag(value, {...})
end,
['un'] = function(value)
return p.unicodeConvert('\\u{' .. -(-value) .. '}')
end,
}
local optionsTemp = {}
-- Find the greatest index first and check for bad indexes
local greatestIndex = 0
for i, v in ipairs(match) do
local new = - -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, #match do
local index = - -match[i][3]
local option = match[i][2]
if match[i] ~= nil then
local arg = args[i+1]
if not optionTypes[option] and option ~= '' then formattedError('invalid option formatting "%%%s"', 2, option) end
if optionsTemp[index] and 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, { optionTypes[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(- -index+1, value, { optionTypes[match[i][2]] or 'string', numberOk = true })
subsitutions:push(value)
end
end
local count = 0
local s, _ = (string.gsub('\127' .. s, '([^%%]?)%%([%d%.%#+%-]*)(%w-)(%d)%{?([^{}]+)%}?', function(before, modifier, option, number, options)
count = count+1
if customOptions[option] then
local t = p.split(options:gsub('\\,', '\255'), '%s*,%s*')
local success, res
for i = 1, #t do
t[i] = t[i]:gsub('\255', ',')
end
if t[1] == '{' and #t == 1 then
success, res = pcall(customOptions[option], values[count+1])
else
success, res = pcall(customOptions[option], values[count+1], unpack(t))
end
assertTrue(success, res, 4)
return before .. res
else
return before .. '%' .. modifier .. (option ~= '' and option or 's') .. '\127'
end
end)):gsub('\127', '')
return s:format(unpack(subsitutions))
end
---------------------------------------------------------------------------------
-- function: parseTextList(s, phrase): table
--
-- Parses a text list into a table.
---------------------------------------------------------------------------------
function p.parseTextList(s, phrase, removeTrailing)
checkType('parseTextList', 1, s, 'string')
checkType('parseTextList', 2, phrase, 'string')
checkType('parseTextList', 3, includeTrailing, 'boolean', true)
phrase = p.trim(phrase)
removeTrailing = removeTrailing ~= false
s = p.trim(s):gsub('^%s*' .. phrase .. '%s*', '')
if removeTrailing then
s = s:gsub('%s*' .. phrase .. '%s*$', '')
end
local t = p.split(s, '%s*' .. p.escape(phrase) .. '+%s*')
return t
end
---------------------------------------------------------------------------------
-- function: parserTag(tagName: string, t?: table)
--
--
---------------------------------------------------------------------------------
function p.parserTag(tagName, t)
checkType('parserTag', 1, tagName, 'string')
checkType('parserTag', 2, t, 'table', true)
local buffer = {'{{#', tagName, ':'}
if t then
table.push(buffer, '\n')
for key, val in pairs(t) do
local isNum = tonumber(key)
if type(val) == 'table' then
for _, value in ipairs(val) do
table.push(buffer, '| ', key, ' = ', value, '\n')
end
else
table.push(buffer, '| ', key, ' = ', val, '\n')
end
end
end
table.insert(buffer, '}}')
return table.concat(buffer)
end
---------------------------------------------------------------------------------
-- function: .ucfirst(s: string)
---------------------------------------------------------------------------------
function p._ucfirst(s)
checkTypeLight('ucfirst', 1, s, 'string')
if s == nil then s = '' end
return s:gsub('(%w)([%w\']*)', function(a, b) return string.upper(a) .. string.lower(b) end)
end
---------------------------------------------------------------------------------
-- function: .lcfirst(s: string)
---------------------------------------------------------------------------------
function p._lcfirst(s)
checkTypeLight('lcfirst', 1, s, 'string')
if s == nil then s = '' end
return s:gsub('(%w)([%w\']*)', function(a, b) return string.lower(a) .. string.lower(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)
checkTypeLight('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{ '<b>', p.parseDualArg(s), '</b>' }
end
---------------------------------------------------------------------------------
-- function: .italic(s: stringable)
--
-- Makes italic text
---------------------------------------------------------------------------------
function p.italic(s)
return table.concat{ '<i>', p.parseDualArg(s), '</i>' }
end
---------------------------------------------------------------------------------
-- function: .underline(s: stringable)
--
-- Makes underlined text
---------------------------------------------------------------------------------
function p.underline(s)
return table.concat{ '<u>', p.parseDualArg(s), '</u>' }
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: .makeImage(name: string | table, table, options: table)
--
-- Creates a wikitext image element. You can specify the file extention by adding
-- `.<extension>` 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)
checkTypeLight('makeImage', 1, name, {'string', 'table'})
checkTypeLight('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 `<abbr>` 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)
checkTypeLight('makeTitle', 1, s, {'string', 'table', 'number'})
checkTypeLight('makeTitle', 2, title, {'string', 'table', 'number', 'nil'})
checkTypeLight('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
parsedTip = p.parseDualArg(title)
return p.wrapHtml(s, disableAbbr and '<span>' or '<abbr>', { title = parsedTip, tabIndex=0 })
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
local page = p.parseDualArg(page)
local displayText = p.parseDualArg(displayText)
return table.concat{
'[[',
page,
displayText and '|' 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
assertTrue(tmp[k] ~= nil, 'bad argument #1 to getParam (attempted to get a non-existent query parameter %s)', 1, 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)
local vType = type(v)
if vType == 'string' or (vType == 'table' and #v ~= 0 and v[1]) or vType == '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 '',
},
'<span>',
{ class = 'plainlinks' },
}
end
function p._externalUrl(frame)
local args = getArgs(frame)
local url, alt, fragment = args[1], args[2], args[3]
local query = {}
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: .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])
assertFalse(num:match('[^%d%-%+]+'), 'number expected, got %q', 2, num)
assertFalse(0 >= tonumber(num), 'repeat delimiter must be greater than 0, got %q', 2, num)
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)
checkTypeLight('_repeat', 1, s, { 'string', 'number', 'table' })
assertTrue(tonumber(num), 'argument #2 is not convertable to a number', 2)
checkTypeLight('_repeat', 3, sep, { 'string', 'number', 'table' }, true)
local num = tonumber(num) or 1
local start = tonumber(sep) or 1
assertTrue(num < 0 or num < 1e308, '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
local parsedNum = isRoman and p._toRoman(i) or i
t[i] = p.parseDualArg(s):gsub('([^\\])%$n', '%1' .. parsedNum):gsub('^%$n', parsedNum):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
local kType, vType = type(k), type(v)
if kType ~= 'string' and kType ~= 'number' then
error(string.format('Invalid table index (attribute name/string expected, got %s)', kType), 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 vType == '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 vType == '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 vType == '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
checkTypeLight('wrapHtml', 1, val, { 'string', 'table', 'number' })
checkTypeLight('wrapHtml', 2, wrap, 'string')
checkTypeLight('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 '</',
selfClosing and '' or wrap,
selfClosing and '' or '>',
}
end
local function _makeTag(text, tag)
tag = tag:lower():gsub('%<[%\\%/]?([^%/%\\]+)[%\\%/]?>', '%1')
assertTrue(not tag:match('([^%a0-9]+)'), 'bad argument #2 to \'wrapTag\' (Invalid HTML tag name \"<%s>\")', 2, tag)
text = p.parseDualArg(text) or ''
local selfClosing = selfClosingTags[tag]
return table.concat{
'<',
tag,
selfClosing and ' />' or '>',
text,
selfClosing and '' or '</',
selfClosing and '' or tag,
selfClosing and '' or '>',
}
end
function p.wrapTag(text, tag, attrs)
checkTypeLight('wrapTag', 1, text, { 'string', 'table', 'number' }, true)
checkTypeLight('wrapTag', 2, tag, { 'string', 'table' })
assertTrue(#tag > 0, 'bad argument #2 to \'wrapTag\' (tag name expected)', 2)
assertTrue(not attrs, 'Tag name contains attributes, please string.wrapHtml()', 2)
text = p.parseDualArg(text) or ''
tag = type(tag) ~= 'table' and { tag } or tag
for i, tg in ipairs(tag) do
text = _makeTag(text, tg)
end
return text
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, ''}, -- units
{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: trimTrailingZeros(n)
--
-- Removes trailing zeros from a number.
---------------------------------------------------------------------------------
function p.trimTrailingZeros(s)
return tonumber((tostring(s):gsub('0*$', '')))
end
---------------------------------------------------------------------------------
-- function: ._toNumber(str: string)
---------------------------------------------------------------------------------
function p._toNumber(str)
local lang = mw.language.new('en')
-- check normal string -> number
local num = tonumber(lang:parseFormattedNumber(tostring(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(string.lower(tostring(str)), '([%d%.,]+)' .. step[2] )
if num then
local exp = step[1]
num = num * exp
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 lang = mw.language.new('en')
local num = tostring(num or 0):gsub('[%, ]', '')
local neg
if not tonumber(num) then
return tonumber(lang:parseFormattedNumber(num))
end
if #num < 3 then return num end
if num:match('^%-') then
neg = true
num = num:gsub('^%-', '')
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 (neg and '-' or '') .. table.concat(ret, ',') .. (decimal and '.' .. decimal or '')
end
---------------------------------------------------------------------------------
-- Internal implementation of p.error() for more flexibility
---------------------------------------------------------------------------------
local function errorWithStack(msg, level)
if msg and msg:match('^[Tt]emplate:(.-):%s*') then
template = msg:match('^[Tt]emplate:(.-):%s*')
msg = msg:gsub('^[Tt]emplate:(.-):%s*', '')
end
local tmp = msg
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, '​:', line },
{ 'mw-ce-l', line }
) or name,
' at line ',
line
}
end, 1)
:gsub('\n\t([%w%.%(%)%:%/%_ %(%)%[%]]+):', '\n\t<b>%1</b>:')
:gsub('\n', '<br>')
: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 [[Template:' .. template .. '|'.. p.wrapHtml('Template:' .. template, 'strong', {class = 'error'}) .. ']]: ' or 'Template Error: ',
msg,
},
'<span>',
{
class = {
'error',
'mw-customtoggle-callstack',
},
},
},
p.wrapHtml{
errorMsg,
'<div>',
{
id = 'mw-customcollapsible-callstack',
class = {
'mw-collapsible',
'mw-collapsed',
},
style = {
['background-color'] = 'rgba(0,0,0,0.1)',
['line-height'] = '14px',
['overflow'] = 'auto',
['padding'] = '8px',
['word-wrap'] = 'normal',
['display'] = 'block',
['white-space'] = 'pre',
['margin'] = '1em 0px',
['font-family'] = 'monospace',
['tab-size'] = 4,
},
},
},
'[[Category:Pages with template errors]]',
}
mw.addWarning(ret)
return ret
end
---------------------------------------------------------------------------------
-- function: .error(msg: string, ...<string.format() arguments>)
--
-- Returns a error message with the lua call stack. "Template:<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 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
return errorWithStack(string.format(msg, ...), 0)
end
p.templateError = p.error
p._error = p.error
---------------------------------------------------------------------------
-- 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 len = select('#', ...)
local t = { ... }
local success, response = xpcall(bind(function(...)
return f(...)
end, ...), function(e)
local t = p.split(errorWithStack(e, 4), '<br>')
return table.concat(t, '<br>')
end)
return response
end
---------------------------------------------------------------------------------
-- 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)
checkTypeLight('trimWhitespace', 1, s, 'string', true)
checkTypeLight('trimWhitespace', 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 return nil 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 < num 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][[Category:Pages with roman numeral errors]]'
end
end
return 'INVALID INPUT[[Category:Pages with roman numeral errors]]'
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
---------------------------------------------------------------------------------
-- function: sublength(frame: table)
--
-- returns a substring of a given string at a specific index and length
-- originally from [[WP:Module:String]]
---------------------------------------------------------------------------------
function p.sublength(frame)
local args = getArgs(frame)
local i = tonumber(args.i) or 0
local len = tonumber(args.len)
return mw.ustring.sub(args.s, i + 1, len and (i + len))
end
---------------------------------------------------------------------------------
-- function: matchTemplate(frame)
--
-- a version of string.match() that can be used with {{#invoke:String|matchTemplate}}
---------------------------------------------------------------------------------
function p.matchTemplate(frame)
local args = getArgs(frame)
local s = args['s'] or args[1] or ''
local pattern = args['pattern'] or args[2] or ''
local nomatch = args['nomatch']
return mw.ustring.match(s, pattern) or nomatch
end
-- Finish module
return p