Module:Shared

From Melvor Idle
Revision as of 01:34, 28 January 2022 by Falterfire (talk | contribs) (added p.fractionpair)
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Documentation for this module may be created at Module:Shared/doc

--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


--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
 
-- conveniently shifts BLAH to Blah
-- Handy when formatting data in ALL CAPS or all lower case
--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
 
-- 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 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 new   function sorter(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"}
function p.splitString(inputstr, sep)
	if sep == nil then
		sep = "%s"
	end
	local t = {}
	for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
		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
 
--Stolen from Stack Overflow
--Adds commas
function p.formatnum(number)
	local i, j, minus, int, fraction = tostring(number):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

function p.formatNumber(frame)
	number = frame.args ~= nil and frame.args[1] or frame
	return p.formatnum(number)
end

function p.round(val, maxDigits, minDigits)
	if val == nil then
		return nil
	else
		if type(maxDigits) == "table" then
			minDigits = maxDigits[2]
			maxDigits = maxDigits[1]
		end
 
		local result = val..""
		local decimals = string.find(result, "%.")
		if decimals ~= nil then
			decimals = string.len(result) - decimals
		else
			decimals = 0
		end
 
		if maxDigits ~= nil and decimals > maxDigits then
			result = string.format("%."..maxDigits.."f", result)
		elseif minDigits ~= nil and decimals < minDigits then
			result = string.format("%."..minDigits.."f", result)
		end
 
		return result
	end
end
 
--From http://lua-users.org/wiki/SimpleRound
function p.round2(num, numDecimalPlaces)
	local mult = 10^(numDecimalPlaces or 0)
	return math.floor(num * mult + 0.5) / mult
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

-- Euclidean Greatest Common Divisor algorithm
function p.gcd(a, b)
	if b ~= 0 then
		return p.gcd(b, a % b)
	else
		return math.abs(a)
	end
end

--Formats a pair of numbers as a reduced fraction
function p.fraction(n, d)
	local gcd = p.gcd(n, d)
	return p.formatnum(n/gcd)..'/'..p.formatnum(d/gcd)
end

--Similar to p.fraction but returns the simplified numerator and denomerator separately without formatting
function p.fractionpair(n, d)
	local gcd = p.gcd(n, d)
	return n / gcd, d / gcd
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, "&#39;", "'")
	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

--Returns a number including the sign, even if positive
function p.numStrWithSign(number)
	if number >= 0 then
		return '+'..p.formatnum(number)
	else
		return p.formatnum(number)
	end
end

return p