Module:Shared: Difference between revisions

m
no edit summary
(added p.fractionpair)
mNo edit summary
 
(30 intermediate revisions by 2 users not shown)
Line 1: Line 1:
-- Module that contains all functions regarding numeric formatting and calculations.
-- TODO: Make modules that now call Shared call Number (for relevant functions)
local numModule = require('Module:Number')
--So there are a handful of functions that I'm using in a lot of places
--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
--So rather than continue to copy across the same handful of functions to every single new module
Line 13: Line 17:


local p = {}
local p = {}
 
-- iterator sorted by keys
-- iterator sorted by keys
-- For example, if you had a table that looked something like
-- For example, if you had a table that looked something like
Line 32: Line 36:
table.sort(keys)
table.sort(keys)
end
end
 
local i = 0
local i = 0
local iterator = function()
local iterator = function()
Line 46: Line 50:
end
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
--General purpose function for going through a table after sorting based on a custom sort order
Line 71: Line 93:
end
end
end
end
 
-- conveniently shifts BLAH to Blah
-- Makes a deep copy of a table or otherwise object.
-- Handy when formatting data in ALL CAPS or all lower case
-- 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
--Originally snagged this from Module:VoidByReward written by User:NoBrainz
function p.titleCase(head, tail)
function p.titleCase(head, tail)
Line 84: Line 129:
end
end
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
-- Returns the number of rows in a table
-- Originally snagged this from Module:VoidByReward written by User:NoBrainz
-- Originally snagged this from Module:VoidByReward written by User:NoBrainz
Line 105: Line 192:
end
end
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
-- Returns the number of indexed elements in a table
-- pre : table is a table with no explicit nil values
-- pre : table is a table with no explicit nil values
Line 119: Line 218:
end
end
end
end
 
--Sorts theTable based on the listed column
--Sorts theTable based on the listed column
function p.tableSort(theTable, sortCol, ascend)
function p.tableSort(theTable, sortCol, ascend)
local new  function sorter(r1, r2)
local sorter = function(r1, r2)
if ascend then
if ascend then
return r1[sortCol] < r2[sortCol]
return r1[sortCol] < r2[sortCol]
Line 131: Line 230:
table.sort(theTable, sorter)
table.sort(theTable, sorter)
end
end
 
--Splits a string based on a sent in separating character
--- Splits a string based on a sent in separating character
--For example calling splitString ("Lith V1 Relic", " ") would return {"Lith", "V1", "Relic"}
--- For example calling splitString ("Lith V1 Relic", " ") would return {"Lith", "V1", "Relic"}
function p.splitString(inputstr, sep)
-- @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
if sep == nil then
sep = "%s"
sep = "%s"
Line 140: Line 242:
local t = {}
local t = {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
if trim == true then
str = str:gsub("^%s*(.-)%s*$", "%1")
end
table.insert(t, str)
table.insert(t, str)
end
end
return t
return t
end
end
 
--Returns 'true' if a string starts with something
--Returns 'true' if a string starts with something
--For example calling startsWith ("Lith V1 Relic", "Lith") would return true
--For example calling startsWith ("Lith V1 Relic", "Lith") would return true
Line 150: Line 255:
return string.sub(string1, 1, string.len(start)) == start
return string.sub(string1, 1, string.len(start)) == start
end
end
 
--Stolen from Stack Overflow
--Adds commas
--Adds commas
function p.formatnum(number)
function p.formatnum(number)
local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')
return numModule.formatnum(number)
-- 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
end


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


function p.round(val, maxDigits, minDigits)
function p.round(val, maxDigits, minDigits)
if val == nil then
return numModule.round(val, maxDigits, minDigits)
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
end
 
--From http://lua-users.org/wiki/SimpleRound
--From http://lua-users.org/wiki/SimpleRound
function p.round2(num, numDecimalPlaces)
function p.round2(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return numModule.round2(num, numDecimalPlaces)
return math.floor(num * mult + 0.5) / mult
end
end
 
-- pre : List is a table or a string
-- pre : List is a table or a string
--      Item is the element that is being searched
--      Item is the element that is being searched
Line 213: Line 286:
IgnoreCase = false  
IgnoreCase = false  
end
end
 
if type(List) == "table" then
if type(List) == "table" then
for key, value in pairs(List) do
for key, value in pairs(List) do
Line 258: Line 331:
length = p.tableCount(table)
length = p.tableCount(table)
end
end
 
-- iterating through outer table
-- iterating through outer table
for i = 1, length, 1 do
for i = 1, length, 1 do
Line 268: Line 341:
return false
return false
end
end
 
-- copies the contents of a variable; handy for when you might want to modify an object taken from a data file
-- 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
-- or any other read-only variable
Line 276: Line 349:
local res = {}
local res = {}
for k, v in pairs(obj) do res[p.clone(k)] = p.clone(v) end
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
return res
end
end
Line 281: Line 366:
-- Euclidean Greatest Common Divisor algorithm
-- Euclidean Greatest Common Divisor algorithm
function p.gcd(a, b)
function p.gcd(a, b)
if b ~= 0 then
return numModule.gcd(a, b)
return p.gcd(b, a % b)
else
return math.abs(a)
end
end
end


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


--Similar to p.fraction but returns the simplified numerator and denomerator separately without formatting
--Similar to p.fraction but returns the simplified numerator and denomerator separately without formatting
function p.fractionpair(n, d)
function p.fractionpair(n, d)
local gcd = p.gcd(n, d)
return numModule.fractionpair(n, d)
return n / gcd, d / gcd
end
end


Line 356: Line 435:
result = string.gsub(result, "%%27", "'")
result = string.gsub(result, "%%27", "'")
result = string.gsub(result, "&#39;", "'")
result = string.gsub(result, "&#39;", "'")
result = string.gsub(result, "&#38;", "&")
return result
return result
end
end
Line 376: Line 456:
--Returns a number including the sign, even if positive
--Returns a number including the sign, even if positive
function p.numStrWithSign(number)
function p.numStrWithSign(number)
if number >= 0 then
return numModule.numStrWithSign(number)
return '+'..p.formatnum(number)
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 = p.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
else
return p.formatnum(number)
return left == right
end
end
 
function p._replace(str, searchTerm, replacementTerm)
if str == nil then
return str
end
end
    local escapedSearch = searchTerm:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%1")
    local result = str:gsub(escapedSearch, replacementTerm)
   
    return result
end
function p.replace(frame)
local args = frame:getParent().args
return p._replace(
frame.args[1],
frame.args[2] or '',
frame.args[3] or '')
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
end


return p
return p
915

edits