Module:Shared
From Melvor Idle
Documentation for this module may be created at Module:Shared/doc
local Num = require('Module:Number')
--So there are a handful of functions that I'm using in a lot of places
--So rather than continue to copy across the same handful of functions to every single new module
--I'm just going to unify them here
--Here's how you use this:
-- 1. When you're making a new module, add this near the top:
-- local Shared = require( "Module:Shared" )
-- 2. When you need to make a call to one of these functions,
-- just preface it with "Shared."
-- So for example you could call tableCount like so:
-- Shared.tableCount(data)
--This whole module copied wholesale from the Warframe wiki (https://warframe.fandom.com/wiki/Module:Shared)
--I removed a couple irrelevant functions, but otherwise did not change anything
local p = {}
-- iterator sorted by keys
-- For example, if you had a table that looked something like
-- data = {["Cat"] = 5,
-- ["Bat"] = 4,
-- ["Hat"] = 7}
-- You could do
-- for k, v in skpairs(data) do...
-- And your loop would start with k="Bat", v=4 then go to k="Cat", v=5,
-- and finally to k="Hat", v=7
--Originally snagged this from Module:VoidByReward written by User:NoBrainz
function p.skpairs(t, revert)
local keys = {}
for k in pairs(t) do keys[#keys + 1] = k end
if revert ~= nil then
table.sort(keys, function(a, b) return a > b end)
else
table.sort(keys)
end
local i = 0
local iterator = function()
i = i + 1
local key = keys[i]
if key then
return key, t[key]
else
return nil
end
end
return iterator
end
-- Function to sort a dictionary-like structure where items are added like tbl['key'] = value
-- We need to turn this structure into a table first, in order to sort it.
function p.sortDictionary(dict, comparer, factory)
local sortedTable = {}
for k, v in pairs(dict) do
local newValue = nil
if factory then
newValue = factory(k, v)
end
newValue = newValue or {key = k, value = v}
table.insert(sortedTable, newValue)
end
table.sort(sortedTable, comparer)
return sortedTable
end
--General purpose function for going through a table after sorting based on a custom sort order
--Taken from https://stackoverflow.com/questions/15706270/sort-a-table-in-lua
function p.spairs(t, order)
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
-- Makes a deep copy of a table or otherwise object.
-- Yoinked from http://lua-users.org/wiki/CopyTable
function p.deepcopy(orig)
local copy
if type(orig) == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
-- Takes an input string and returns the same string with title case-like
-- formatting (that is, the first letter of every word becomes uppercase,
-- while the remainder becomes lowercase)
-- Examples:
-- titleCase('ALL CAPS') = 'All Caps'
-- titleCase('all lowercase') = 'All Lowercase'
-- titleCase('A MiXTUre') = 'A Mixture'
-- Note that non-alphanumeric characters are treated as a word boundary, so:
-- titleCase('a!b(c)d') = 'A!B(C)D' (not 'A!b(c)d')
--Originally snagged this from Module:VoidByReward written by User:NoBrainz
function p.titleCase(head, tail)
if tail == nil then
--Split into two lines because don't want the other return from gsub
local result = string.gsub(head, "(%a)([%w_']*)", p.titleCase)
return result
else
return string.upper(head) .. string.lower(tail)
end
end
-- Converts an input string into TitleCase.
-- Every first letter of every word becomes uppercase
-- With the exception of 'of', 'the', 'and', but only if these
-- appear anywhere in the sentence besides the first occurence.
-- Examples:
-- specialTitleCase('ALL CAPS') = 'All Caps'
-- specialTitleCase('all lowercase') = 'All Lowercase'
-- specialTitleCase('A MiXTUre') = 'A Mixture'
-- specialTitleCase('the bones') = 'The Bones
-- specialTitleCase('of the world') = 'Of the World'
-- specialTitleCase('amulet Of Fishing') = 'Amulet of Fishing'
function p.specialTitleCase(sentence)
if sentence == nil or sentence:match("^%s*$") ~= nil then return nil end
-- List of words that are excluded from TitleCase
local excludedWords = {
["of"] = true,
["and"] = true,
["the"] = true
}
-- Split all words and add them as lower case to the table.
local words = {}
for word in sentence:gmatch("%S+") do
table.insert(words, word:lower())
end
-- Capitalize the first word
words[1] = words[1]:gsub("^%l", string.upper)
-- Title-case the remaining words, excluding certain words based on position
for i = 2, #words do
local curWord = words[i]
if excludedWords[curWord] == true then
else
words[i] = curWord:gsub("^%l", string.upper)
end
end
return table.concat(words, " ")
end
-- Returns the number of rows in a table
-- Originally snagged this from Module:VoidByReward written by User:NoBrainz
-- Note from User:Cephalon Scientia:
-- Length operator (#) doesn't work as expected for tables that have been
-- loaded into a module by mw.loadData().
-- Use this function to get all the rows in a table regardless of them
-- being keys, values, or tables
-- pre : table is a table with no explicit nil values
-- post: returns the size of table, ignoring keys with nil values and
-- nil values themselves
-- if table is not of type 'table' then return nil
function p.tableCount(table)
if (type(table) == 'table') then
local count = 0
for _ in pairs(table) do count = count + 1 end
return count
else
return nil
end
end
-- Returns true if the table is empty, false otherwise
function p.tableIsEmpty(table)
if type(table) == 'table' then
for k, v in pairs(table) do
return false
end
return true
else
return nil
end
end
-- Returns the number of indexed elements in a table
-- pre : table is a table with no explicit nil values
-- post: returns the number of indexed elements in a table
-- if table is not of type 'table' then return nil
function p.indexCount(table)
if (type(table) == 'table') then
local count = 0
for _ in ipairs(table) do count = count + 1 end
return count
else
return nil
end
end
--Sorts theTable based on the listed column
function p.tableSort(theTable, sortCol, ascend)
local sorter = function(r1, r2)
if ascend then
return r1[sortCol] < r2[sortCol]
else
return r1[sortCol] > r2[sortCol]
end
end
table.sort(theTable, sorter)
end
--- Splits a string based on a sent in separating character
--- For example calling splitString ("Lith V1 Relic", " ") would return {"Lith", "V1", "Relic"}
-- @param inputstr (string) The input to separate.
-- @param sep (string/char) The separation character.
-- @param trim (boolean) TRUE to trim the leading/trailing whitespaces
function p.splitString(inputstr, sep, trim)
if sep == nil then
sep = "%s"
end
local t = {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
if trim == true then
str = str:gsub("^%s*(.-)%s*$", "%1")
end
table.insert(t, str)
end
return t
end
--Returns 'true' if a string starts with something
--For example calling startsWith ("Lith V1 Relic", "Lith") would return true
function p.startsWith(string1, start)
return string.sub(string1, 1, string.len(start)) == start
end
-- pre : List is a table or a string
-- Item is the element that is being searched
-- IgnoreCase is a boolean; if false, search is case-sensitive
-- post: returns a boolean; true if element exists in List, false otherwise
function p.contains(List, Item, IgnoreCase)
if List == nil or Item == nil then
return false
end
if IgnoreCase == nil then
IgnoreCase = false
end
if type(List) == "table" then
for key, value in pairs(List) do
if value == Item then
return true, key
elseif IgnoreCase and string.upper(value) == string.upper(Item) then
return true, key
end
end
else
local start = string.find(List, Item)
return start ~= nil
end
return false
end
--Stolen from http://lua-users.org/wiki/StringTrim
--Trims whitespace. Not quite sure how it works.
--I know how it works
--replaces "^%s*(.-)%s*$" with "%1" in str
--^%s*(.-)%s*$ matches:
--^:beginning of string
--%s*:any number of spaces
--(.-):any number of any character, minimum possible, saved to %1
--%s* again
--$: end of string
--%1 inserts the content of the parentheses
--pretty simple if you know the meanings
--User:Giga Martin
function p.trim(str)
return (str:gsub("^%s*(.-)%s*$", "%1"))
end
-- generic function that checks to see if a key exists in a given nested table
-- added by User:Cephalon Scientia
-- pre : table is a nested table
-- key is a string that represents a key name
-- length is a integer that represents the size of outer table;
-- if omitted, length is set to size of outer table
-- post: returns a boolean; true if key exists in table, false otherwise or
-- if key contains a nil value
function p.hasKey(table, key, length)
if (length == nil) then
length = p.tableCount(table)
end
-- iterating through outer table
for i = 1, length, 1 do
local elem = table[i] -- storing one of inner tables into a variable
if (elem[key] ~= nil) then
return true
end
end
return false
end
-- copies the contents of a variable; handy for when you might want to modify an object taken from a data file
-- or any other read-only variable
-- Stolen from https://gist.github.com/tylerneylon/81333721109155b2d244
function p.clone(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[p.clone(k)] = p.clone(v) end
return res
end
-- Shallow clone, desirable when operations such as sorting are to be performed
-- on a table where it is not necessary to perform a deep clone of all data within
-- the table's elements
function p.shallowClone(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do
res[k] = v
end
return res
end
function p.timeString(timeInSeconds, shorten)
local remain = timeInSeconds
local days, hours, minutes = 0, 0, 0
local isShort = shorten
local pieces = {}
if remain >= 86400 then
days = math.floor(remain / 86400)
remain = remain - days * 86400
if isShort then
table.insert(pieces, days..'d')
elseif days > 1 then
table.insert(pieces, days..' days')
else
table.insert(pieces, days..' day')
end
end
if remain >= 3600 then
hours = math.floor(remain / 3600)
remain = remain - hours * 3600
if isShort then
table.insert(pieces, hours..'h')
elseif hours > 1 then
table.insert(pieces, hours..' hours')
else
table.insert(pieces, hours..' hour')
end
end
if remain >= 60 then
minutes = math.floor(remain / 60)
remain = remain - minutes * 60
if isShort then
table.insert(pieces, minutes..'m')
elseif minutes > 1 then
table.insert(pieces, minutes..' minutes')
else
table.insert(pieces, minutes..' minutes')
end
end
if remain > 0 then
if isShort then
table.insert(pieces, remain..'s')
elseif remain > 1 then
table.insert(pieces, remain..' seconds')
else
table.insert(pieces, remain..' second')
end
end
return table.concat(pieces, ', ')
end
function p.fixPagename(pageName)
local result = pageName
result = string.gsub(result, "%%27", "'")
result = string.gsub(result, "'", "'")
result = string.gsub(result, "&", "&")
return result
end
--Checks if two tables contain the same value with the same indices
function p.tablesEqual(t1, t2)
if p.tableCount(t1) ~= p.tableCount(t2) then return false end
for i, val in p.skpairs(t1) do
if type(val) ~= type(t2[i]) then
return false
elseif type(val) == 'table' then
if not p.tablesEqual(val, t2[i]) then return false end
elseif t2[i] ~= val then
return false
end
end
return true
end
-- Applies formatting to an error message for display on wiki pages.
-- Also appends a category such that errors can be easily located
function p.printError(message)
-- Prevent message being interpreted as wikitext and handle non-string messages
local messageString = mw.text.nowiki(type(message) == 'string' and message or mw.dumpObject(message))
return '[[Category:Pages with script errors]]<div class="text-negative">ERROR: ' .. messageString .. '</div>'
end
-- Takes a description template & template data, returning a description with variables populated
function p.applyTemplateData(descTemplate, templateData)
local resultDesc = descTemplate
for k, v in pairs(templateData) do
local val = v
if type(v) == 'number' then
val = Num.formatnum(val)
end
resultDesc = string.gsub(resultDesc, '${' .. k .. '}', val)
end
return resultDesc
end
-- Given a namespace & local ID, returns a namespaced ID
function p.getNamespacedID(namespace, localID)
if string.find(localID, ':') == nil then
return namespace .. ':' .. localID
else
-- ID already appears to be namespaced
return localID
end
end
-- Given a namespaced ID, returns both the namespace & local ID
function p.getLocalID(ID)
local namespace, localID = nil, nil
local sepIdx = string.find(ID, ':')
if sepIdx == nil then
-- Provided ID doesn't appear to be namespaced
localID = ID
else
namespace = string.sub(ID, 1, sepIdx - 1)
localID = string.sub(ID, sepIdx + 1, string.len(ID))
end
return namespace, localID
end
-- Compares two strings, optionally ignoring case
function p.compareString(left, right, ignoreCase)
-- Both are nil (equal)
if left == nil and right == nil then return true end
-- Only one is nil (not equal)
if left == nil or right == nil then return false end
-- Convert inputs to strings, just in case
left = tostring(left)
right = tostring(right)
if ignoreCase == true then
return left:upper() == right:upper()
else
return left == right
end
end
function p._replace(str, searchTerm, replacementTerm)
if str == nil then
return str
end
local escapedSearch = searchTerm:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1")
local result = str:gsub(escapedSearch, replacementTerm)
return result
end
function p.addOrUpdate(tbl, key, func)
local val = tbl[key]
if val ~= nil then
tbl[key] = func(val)
else
tbl[key] = func(nil)
end
return tbl[key]
end
return p