Documentation for this module may be created at Module:Utils/doc
--[[
Здесь собраны ряд функций, которые применяются в разных Модулях
для облегчения применения и корректировки сразу во всех местах
Для использования в Модулях необходимо:
1. Когда создаете новый модуль, подключите данный Модуль в начале:
local p = require( "Модуль:Функции")
(наименование оставлено как в англовики специально, для облегчения применения прочих модулей =) )
2. И там где нужно применить ту или иную функцию из "Функции"
Просто припишите префикс "p."
Например для вызова функции tableCount необходимо написать:
p.tableCount(data)
Исходно собрано пользователем User:Falterfire с EN wiki, но...
не у всех функций он являлся автором, поэтому где это удалось отследить
автор той или иной функции указан непосредственно перед ней.
Локализовано на русский User:Max.Archy (Rus localization by User:Max.Archy)
исходник на англовики находится в Module:p
]]--
local p = {}
p.lang = mw.language.new('en')
p.notUpdatedMessage = '[not updated for v0.2.0]'
--Получает две даты (можно в составе таблицы) предпочтительно в формате ГГГГ-мм-дд
--возвращает интервал в формате:
-- 10 - 12 января 2022 или / января - 12 февраля 2022 / 10 января 2022 - 12 февраля 2035
function p.daysInterval(date1,date2)
if type(date1)=='table' then
date2=date1[2]
date1=date1[1]
end
if p.lang:formatDate('Y', date1)~=p.lang:formatDate('Y', date2) then
return p.lang:formatDate('<b>d xg Y</b>', date1)..' - '..p.lang:formatDate('<b>d xg Y</b>', date2 )
elseif p.lang:formatDate('m', date1)~=p.lang:formatDate('m', date2) then
return p.lang:formatDate('<b>d xg</b>', date1)..' - '..p.lang:formatDate('<b>d xg Y</b>', date2 )
end
return p.lang:formatDate('<b>d</b>', date1)..' - '..p.lang:formatDate('<b>d xg Y</b>', date2 )
end
--[[ -- отображает оставшееся время в днях от указанной даты до текущей
function daysBefore(dayday)
return p.lang:formatDuration(os.difftime(p.lang:formatDate('U'),p.lang:formatDate('U', dayday )),{'days'})
end
]]--
--parses template arguments and returns seqrch parameters
function p.importStackFromTemplate(frame)
local localIn = {}
local impKey = {['YES']=true,['TRUE']=true,
['NO']=false,['FALSE']=false,
['NIL']='look@em',['NILL']='look@em'}
local function toFalse(thing)
if impKey[mw.ustring.upper(thing)]==false then return false else return impKey[mw.ustring.upper(thing)] or thing end
end
for index, value in pairs(frame.args) do
local bool = true
-- if mw.ustring.byte(index)==33 then index = mw.ustring.gsub(index,"!",'') bool=false end
index = mw.ustring.gsub(index,"%d",'') --removes numbers
if toNil(index) then
if not localIn[index] then localIn[index]={} end
localIn[index][toFalse(value)]=bool
end
end
--mw.logObject(localIn)
return localIn
end
--Фильтрует базу данных по запрошенным параметрам и возвращает две таблицы
--Первая разделена на подтаблицы в соответствии с curator-параметром
--Вторая просто существует. Содержит все фильтрованные, но не прошедшие курацию моды.
--Когда не указано куратора, все фильтрованные моды помещаются во вторую таблицу.
function p.getFilteredTableStack(curator, varImported, Database,customOrder)
local returnNamed, returnUnnamed ={}, {}
varImported = varImported or {}
if type(curator)=='table' then
varImported = p.importStackFromTemplate(curator)
curator = toNil(curator.args[1])
end
local typeTags = {Tags=true, Sets = true,}
local typeStats = {Stats=true}
local hitsRequred = p.tableCount(varImported)
local hitsScored
local function NL(thing)
if thing==false then return false end
return thing or 'look@em'
end
function complexCondition(valStack, index, key, var)
if typeTags[index] then
return valStack[NL(key)] and var==valStack[NL(key)]
elseif typeStats[index] then
return (var[1] or nil) and valStack[NL(var[1])]
else
return valStack[NL(var)]==true
end
return false
end
for Name, Item in p.ordpairs(Database,customOrder) do
hitsScored = 0
local customCurator={}
for index, valStack in pairs(varImported) do
if type(Item[index])~='table' then
if valStack[NL(Item[index])]==true then hitsScored=hitsScored+1
else break
end
else
for key, var in pairs(Item[index]) do
if complexCondition(valStack, index, key, var) then
customCurator[index]=var
hitsScored = hitsScored + 1
break
end
end
end
end
if toNil(Item.Exclude) and not (type(varImported.Exclude)=='table' and varImported.Exclude[true]==true) then hitsScored = hitsScored - 1 end
if hitsScored == hitsRequred then
local cur=customCurator[curator] or Item[curator]
if cur~=nil then
if returnNamed[cur] == nil then returnNamed[cur]={} end
table.insert(returnNamed[cur], Name)
else
table.insert(returnUnnamed, Name)
end
end
end
return returnNamed, returnUnnamed
end
--Creates a stack of <tr> of variably width and length based on passed table
--Is used for fast creatin of tables with tab = mw.hml.create('table'):node(p.drawColTable(...))
--sorts columns besed on the order table or alphabetical
function p.drawColTable(iTable,order,drawHeader,drawHeaderIcons,iconsSize)
local Icons
if drawHeaderIcons~=true then drawHeaderIcons=false else Icons = require( "Module:Icons" ) end
if drawHeader~=false then drawHeader=true end
local box = mw.html.create()
local brkOnce, maxRows = {}, {}
local brk = 0
local x=0
local colCount = 0
local celWidth = 100
for col, content in pairs(iTable) do
colCount = colCount + 1
maxRows[col] = table.maxn(content)
end
celWidth = math.ceil(100 / colCount)
local header = mw.html.create('tr') -- draws table header
for col in p.ordpairs(iTable, order) do
if drawHeaderIcons == true then
local Item=Icons.getIcon(col)
header:tag('th'):attr('style','font-size: 90%; width:'..celWidth..'%;')
:tag('div'):attr('style','height: 1em; margin-bottom:5px; padding: 0 5px;'):wikitext(col):done()
:tag('div'):attr('style','margin-bottom:5px;'):attr('style','height: 35px;')
:node(Icons.Main(Item,nil,nil,(iconsSize or 25))):done()
else
header:tag('th'):wikitext(col):attr('style','font-size: 90%; width:'..celWidth..'%;')
end
end
if drawHeader then box:node(header) end
while true do -- draws the rest of an owl
x=x+1
local row = mw.html.create('tr')
for col, itm in p.ordpairs(iTable, order) do
if x<= maxRows[col] then
row:tag('td')
:attr('style','width:'..celWidth..'%;')
:node(itm[x])
else
row:tag('td'):attr('style','width:'..celWidth..'%;')
if not brkOnce[col] then
brk=brk+1 brkOnce[col]=true end
end
end
if brk >= colCount then break end
box:node(row)
end
return box
end
--draws a table with all requested Vestiges in it.
function p.drawNavTable(itemNames, args)
args = args or {}
local itsNested, itsNotNested, cart
if args.Header == false then
cart = mw.html.create()
elseif args.Header then
cart = args.Header
else
cart = mw.html.create('table'):addClass(args.HeaderClass or 'article-table')
end
for i, Name in pairs(itemNames) do
if itsNotNested==nil and type(Name)=='table' then
itsNested = true
break
end
cart:node(args.RowFunction(Name,args)
:addClass(args.rowClassF and args.rowClassF(Name,itemNames,i,args.rowClassS) or nil))
itsNotNested = itsNotNested or true
end
if itsNested then
for rarity, rarStack in p.ordpairs(itemNames, args.NestedOrder) do
for i, Name in pairs(rarStack) do
cart:node(args.RowFunction(Name,args)
:addClass(args.rowClassF and args.rowClassF(Name,rarStack,i,args.rowClassS) or nil))
end
end
end
return cart
end
--draws a generic tabber from a table with one layer of subtables containing element names.
--Subtable names become tabber headers, subtable content is rendered via function privided
--The function must take element's name for the first argument
--and return either mw.html.create(something) or string
--If no function provided it backups to listing names as white text
function p.drawTabber(tabl,functionProvided,order,skipEmAll)
local Tabber = mw.html.create('div'):attr('class','tabberex')
local function drawEmAll(tab)
local tabContents = mw.html.create()
for _, Name in pairs(tab) do
tabContents:node(functionProvided and functionProvided(Name) or Name)
:newline()
end
return tabContents
end
function doesItHas(tab)
local bool = false
for _, thing in pairs(tab) do
if thing~=nil then bool=true break end
end
return bool
end
for tabName, nameStack in p.ordpairs(tabl,order) do
if skipEmAll==true then
if doesItHas(nameStack) then
Tabber:tag('div'):attr('class','tabberex-tab')
:attr('data-tab-header',tabName)
:node(functionProvided(nameStack))
end
else
if doesItHas(nameStack) then
Tabber:tag('div'):attr('class','tabberex-tab')
:attr('data-tab-header',tabName)
:node(drawEmAll(nameStack))
end
end
end
return Tabber:allDone()
end
-- Итератор с сортировкой по ключам
-- Например, если у вас есть таблица следующего вида
-- data = {["Cat"] = 5,
-- ["Bat"] = 4,
-- ["Hat"] = 7}
-- Вы можете
-- for k, v in skpairs(data) do...
-- И цикл начнётся с k="Bat", v=4 потом перейдёт к k="Cat", v=5,
-- and finally to k="Hat", v=7
--Изначально написано для Module:VoidByReward пользователем 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
--Упорядочивает таблицу согласно данной таблице-ключу order.
--Отсутствующие в order поля сортированы в алфавитном порядке после упорядоченных по order.
--Например, есть таблицы
--data = {['Редкий']=1,['Обычный']=2,['Необычный']=3,['Уникальый']=4,['Адаптивный']=5}
--order = {'Обычный','Необычный','Редкий'}
--цикл for k,v in ordpairs(data,order) пройдёт по таблице в следующем порядке:
--k='Обычный',v=2 | k='Необычный',v=3 | k='Редкий',v=1 | k='Адаптивный',v=5 | k='Уникальый',v=4
function p.ordpairs(t,order)
order = order or {}
local keys, sorted, unsorted, register = {}, {}, {}, {}
for k in pairs(t) do register[k]=1 end
for _, tag in pairs(order) do
if t[tag] ~=nil then table.insert(sorted,tag) register[tag]=nil end
end
for k in pairs(register) do table.insert(unsorted,k) end
table.sort(unsorted)
for _, tag in pairs(unsorted) do
table.insert(sorted,tag)
end
local i = 0
local iterator = function()
i = i + 1
local key = sorted[i]
if key then
return key, t[key]
else
return nil
end
end
return iterator
end
--example of an iterator function
function p.relicLoop(doWeReallyCareForRequiemTier)
local tier = ""
local iterator = function()
if(tier == "") then
tier = "Лит"
elseif (tier == "Лит") then
tier = "Мезо"
elseif(tier == "Мезо") then
tier = "Нео"
elseif(tier =="Нео") then
tier = "Акси"
elseif(tier =="Акси") and doWeReallyCareForRequiemTier then
tier = "Реквием"
else
tier = nil
end
return tier
end
return iterator
end
-- Преаброзовывает ШТУКИ в Штуки
-- Полезно, когда данные заполнены только КАПСОМ или только строчными
--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 = mw.ustring.gsub(head, "(%a)([%w_']*)", p.titleCase)
return result
else
return mw.ustring.upper(head) .. mw.ustring.lower(tail)
end
end
-- Возвращает количество строк в таблице
-- Originally snagged this from Module:VoidByReward written by User:NoBrainz
-- Оператор длины (#) работает некорректно для таблиц, которые были
-- загружены в модуль mw.loadData().
-- Используйте эту функцию чтобы подсчитать все строки с таблице
-- независимо от того, являются ли они ключами, значениями или таблицами
-- до : table - табллица без explicit nil values
-- после: возвращает количество строк, игнорируя ключи со значением nil
-- и сами значения nil
-- если тип table не является 'table' то возвращает 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 mw.ustring.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 mw.ustring.sub(string1, 1, mw.ustring.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 = mw.ustring.find(result, "%.")
if(decimals ~= nil ) then decimals = mw.ustring.len(result) - decimals else decimals = 0 end
if(maxDigits ~= nil and decimals > maxDigits) then
result = tonumber(mw.ustring.format("%."..maxDigits.."f", result))
elseif(minDigits ~= nil and decimals < minDigits) then
result = mw.ustring.format("%."..minDigits.."f", result)
end
return result
end
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
elseif (IgnoreCase and mw.ustring.upper(value) == mw.ustring.upper(Item)) then
return true
end
end
else
local start = mw.ustring.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.
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; for copying tables recursively
-- source: http://lua-users.org/wiki/CopyTable
-- pre : orig is the original value
-- post: returns a copy of the original table if orig is of type
-- table, including all children tables but not metatables;
-- otherwise returns whatever is contained in orig
function p.deepCopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[p.deepCopy(orig_key)] = p.deepCopy(orig_value)
end
-- cannot copy metatables of tables loaded in by mw.loadData();
-- stack overflow error if you uncomment the statement below
-- setmetatable(copy, p.deepCopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
return p