Module:Feature

--- Miscellaneous useful functions. local lib = {}

local util = require('libraryUtil') local checkType = util.checkType local checkTypeMulti = util.checkTypeMulti local NIL_OK = true

--- Choose one of two values to return. -- @param {boolean} cond Determines which value to return. -- @param T The value to return if `cond` is true (or truthy). -- @param F The value to return if `cond` is false (or falsey). function lib.ternary(cond, T, F)   if cond then return T   end

return F end

--- Some functions from `mw.text` are slow or may not always work as intended. -- This group of functions provides better alternatives for them. -- @section `mw.text` replacements

--- -- Removes ASCII whitespace from the start and end of `text`. -- Slightly more efficient than the `mw.text` implementation. -- @see https://help.fandom.com/wiki/Extension:Scribunto#mw.text.trim_is_slow -- @param {string} text The text to trim. -- @return {string} The trimmed text. function lib.trim(text) return (text:gsub( '^[\t\r\n\f ]+',  ):gsub( '[\t\r\n\f ]+$',  )) -- the "extra" parentheses are important for removing the second value returned from `gsub` end

--- Returns an iterator over substrings that would be returned by @{lib.split}. -- @see @{lib.split} for argument documentation. -- @return {function} Iterator over substrings of `text` separated by `delim`. function lib.gsplit(text, delim, opt) checkType('Feature.gsplit', 1, text, 'string') checkType('Feature.gsplit', 2, delim, 'string', NIL_OK) checkType('Feature.gsplit', 3, opt, 'table', NIL_OK) if delim == nil then delim = " " end if opt == nil then opt = {} end -- the mediawiki implementation uses ustring, which is slower than string -- and also not necessary if delim isn't a pattern. -- https://help.fandom.com/wiki/Extension:Scribunto#mw.text.split_is_very_slow -- local g = mw.text.gsplit(text, delim, true) -- local function f -- 	local value = g -- 	if value and not opt.noTrim then -- value is nil when the generator ends -- 		value = mw.text.trim(value) -- 	end -- 	if value == "" and opt.removeEmpty then -- 		return f -- 	end -- 	return value -- end -- return f, nil, nil -- based on https://github.com/wikimedia/mediawiki-extensions-Scribunto/blob/1eecdac6def6418fb36829cc2f20b464c30e4b37/includes/Engines/LuaCommon/lualib/mw.text.lua#L222 local s, l = 1, #text local function f if s then local e, n = string.find( text, delim, s, true ) local ret if not e then ret = string.sub( text, s ) s = nil elseif n < e then -- Empty separator! ret = string.sub( text, s, e ) if e < l then s = e + 1 else s = nil end else ret = e > s and string.sub( text, s, e - 1 ) or '' s = n + 1 end if not opt.noTrim then ret = lib.trim(ret) end if ret == '' and opt.removeEmpty then return f end return ret end end return f, nil, nil end

--- Returns a table containing the substrings of `text` that are separated by `delim`. -- (Compared to @{mw.text.split}, this function always treats `delim` as a literal --  string rather than a pattern, and it trims output substrings using @{lib.trim} by default.) -- @param       {string} text The string to split. -- @param[opt]  {string} delim The delimiter string to use when splitting `text`. --                (Using an empty string will split `text` into individual characters.) -- @param[opt]  {table} opt Extra options: -- @param[opt]  {boolean} opt.noTrim Set true to disable trimming of generated substrings --                 using @{lib.trim}. -- @param[opt]  {boolean} opt.removeEmpty Set true to omit empty substrings --                 (i.e., when multiple `delim` appear consecutively, or when --                  `delim` appears at the start or end of `text`). -- @return {table} Substrings of `text separated by `delim`. function lib.split(text, delim, opt) checkType('Feature.split', 1, text, 'string') checkType('Feature.split', 2, delim, 'string', NIL_OK) checkType('Feature.split', 3, opt, 'table', NIL_OK) local output = {} for item in lib.gsplit(text, delim, opt) do		table.insert(output, item) end return output end

--- A wrapper around @{mw.text.unstripNoWiki} that un-escapes -- characters/sequences that escapes. -- @see https://github.com/wikimedia/mediawiki/blob/c22d01f23b7fe754ef106e97bae32c3966f8db3e/includes/parser/CoreTagHooks.php#L146 --            for MediaWiki source code for function lib.unstripNoWiki(str) return (mw.text.unstripNoWiki(str)		:gsub('&lt;', '<'):gsub('&gt;', '>')		:gsub('-&#123;', '-{'):gsub('&#125;-', '}-')) -- the "extra" parentheses are important for removing the second value returned from `gsub` end

--- @section end

--- Returns an iterator over `tbl` that outputs items in the order defined by `order`. -- @param      {table} tbl The table to iterate over. -- @param[opt] {table|function} order The iteration order. -- --               Can be specified either as an ordered list (table) of keys from `tbl` --               or as an ordering function that accepts `tbl`, `keyA`, and `keyB` --               and returns true when the entry in `tbl` associated wity `keyA` --               should come before the one for `keyB`. -- --               If not specified, the keys' natural ordering is used --               (i.e., `function(tbl, a, b) return a < b end`). -- @return {function} The iterator. function lib.spairs(tbl, order) checkType('Feature.spairs', 1, tbl, 'table') checkTypeMulti('Feature.spairs', 2, order, {'table', 'function', 'nil'}) local keys if type(order) == "table" then keys = order else -- collect the keys keys = {} for k in pairs(tbl) do table.insert(keys, k) end -- sort the keys (using order function if given) if order then table.sort(keys, function(a, b) return order(tbl, a, b) end) else table.sort(keys) end end

-- return the iterator function local i = 0 return function i = i + 1 local key = keys[i] return key, tbl[key] end end

--	Parses Phantom Template Format strings into a list of maps.	@param      {string} input A string formed by concatenating the output of Phantom Templates.		Usually, this string is generated by DPL.	@param[opt]  {string} key_separator Separator between the entries (key-value pairs) of items in `input`. Defaults to ';'.	@param[opt]  {string} end_separator Separator between items in `input`. Defaults to '.	@param[opt]  {string} equal_separator Separator between the key and value of each entry in `input`. Defaults to '='.	@return      {table} A list of items from `input`; each value is a map of the item's entries. -- function lib.parseTemplateFormat (inputStr, key_separator, end_separator, equal_separator) if key_separator == nil then key_separator = ";" end if end_separator == nil then end_separator = "$" end if equal_separator == nil then equal_separator = "=" end local arg_format = "^%s*(.-)%s*" .. equal_separator .. "%s*(.-)%s*$" local resultTable = {} for str in lib.gsplit(inputStr, end_separator, {noTrim=true, removeEmpty=true}) do		local result = {} for param in lib.gsplit(str, key_separator) do			local arg, val = param:match(arg_format) if arg then result[arg] = val else -- skip, i guess -- mw.log("Warning: Lua module found extra " .. key_separator .. " or " .. end_separator .. " separators in DPL output.") end end table.insert(resultTable, result) end return resultTable end

--[=[	Parses Phantom Template Format strings into a list of ordered maps. @param      {string} input A string formed by concatenating the output of Phantom Templates. Usually, this string is generated by DPL. @param[opt] {string} key_separator Separator between the entries (key-value pairs) of items in `input`. Defaults to ';'. @param[opt] {string} end_separator Separator between items in `input`. Defaults to '$'. @param[opt] {string} equal_separator Separator between the key and value of each entry in `input`. Defaults to '='. @return[name=output] {table} A list of items from `input`; each value is a list of the item's entries. @return[name=output[i]] {table} The i-th item of `input`. @return[name=output[i].page] {string} The value of the `page` key for this item. @return[name=output[i][j]] {table} The j-th key-value pair of this item. @return[name=output[i][j].key] {string} The j-th key of this item. @return[name=output[i][j].value] The j-th value of this item. --]=] function lib.parseTemplateFormatOrdered (inputStr, key_separator, end_separator, equal_separator) if key_separator == nil then key_separator = ";" end if end_separator == nil then end_separator = "$" end if equal_separator == nil then equal_separator = "=" end local arg_format = "^%s*(.-)%s*" .. equal_separator .. "%s*(.-)%s*$" local resultTable = {} for str in lib.gsplit(inputStr, end_separator, {noTrim=true, removeEmpty=true}) do		local result = {} for param in lib.gsplit(str, key_separator) do			local arg, val = param:match(arg_format) if arg == 'page' then result['page'] = val else table.insert(result,{					key = arg,					value = val				}) end end table.insert(resultTable, result) end return resultTable end

-- searches ordered table and returns value function lib.orderedTableSearch(tbl, search) for i, obj in ipairs(tbl) do       if obj.key == search then return obj.value end end return false end

--- Add thousands separator to number `n`. -- @param {number|frame} n If a frame is given, then its first argument (`frame.args[1]`) will be used as input instead. -- @return {string} The number formatted with commas for thousands separators. -- @see https://stackoverflow.com/questions/10989788/format-integer-in-lua/10992898#10992898 function lib.thousandsSeparator(n) if (n == mw.getCurrentFrame) then n = n.args[1] elseif (type(n) == "table") then n = n[1] end local i, j, minus, int, fraction = tostring(n):find('([-]?)(%d+)([.]?%d*)')

-- reverse the int-string and append a comma to all blocks of 3 digits int = int:reverse:gsub("(%d%d%d)", "%1,") -- reverse the int-string back remove an optional comma and put the optional minus and fractional part back return minus .. int:reverse:gsub("^,", "") .. fraction end

--- @return {boolean} true iff string or table is empty -- @note May not be correct for tables with metatables. function lib.isEmpty(item) if item == nil or item == "" then return true end if type(item) == "table" then return next(item) == nil end return false end

--- @return {boolean} true iff string or table is not empty -- @note May not be correct for tables with metatables. function lib.isNotEmpty(item) return not lib.isEmpty(item) end

--- @return nil if string or table is empty, otherwise return the value. function lib.nilIfEmpty(item) if lib.isEmpty(item) then return nil else return item end end

--- -- @param {table} t A table of items -- @param elm The item to search for -- @returns true if `elm` is a value in `t`; false otherwise. (Does not check keys of `t`.) -- @see http://stackoverflow.com/q/2282444 -- @see another implementation: Dev:TableTools.includes function lib.inArray(t, elm) for _, v in pairs(t) do       if v == elm then return true end end return false end

return lib