Inkbound Wiki
Advertisement

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
Advertisement