Module:Shared

From Melvor Idle
Revision as of 18:02, 11 November 2020 by Falterfire (talk | contribs) (Fixed specific edge case of rounding sometimes leading to too few digits being shown.)

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.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("%."..minDigits.."f", tonumber(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

-- Euclid's Greatest Common Divisor algorithm
function p.gcd(a, b)
  if(a == b) then
    return a
  else
    if(a > b) then
      if b == 0 then
        return a
      else
        return p.gcd(a - b, b)
      end
    else
      if a == 0 then
        return b
      else
        return p.gcd(a, b - a)
      end
    end
  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

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