ATTENTION! The process of updating WiKi to version Eco 10.x has begun. Those wishing to participate can find out more Information on our ECO Contribution Wiki Discord.

Module:Utils

From Eco - English Wiki
Revision as of 19:25, 24 February 2022 by Demian (talk | contribs) (Replace string library with mw.ustring in Demian's functions.)

This module provides utility functions used from other modules.

Usage

Add the following line of code at the top of your file.

local Utils = require("Module:Utils")

-- You may then call functions from this module in your script. For example:
local tableLength = Utils.tableLen(myTable)

local p = {}

--- Trims and parses the args into a table, then returns the table
-- @author User:Avaren
function p.normaliseArgs(frame)
	local origArgs = frame:getParent().args
	local args = {}

	for k, v in pairs(origArgs) do
		v = mw.text.trim(tostring(v))
		if v ~= '' then
			args[k] = v
		end
	end

	return args
end

--- Get path to icon file.
-- @author User:Avaren
function p.checkImage(name, too_expensive)
	local icon = name:gsub('%s+', '') .. '_Icon.png'
	if too_expensive then
		return icon
	end

	if mw.title.makeTitle('File', icon).file.exists then
		return icon
	else
		return 'NoImage.png'
	end
end

--- Check if <code>item</code> is in given <code>array</code>.
-- @param item Item to look for
-- @param #table array Table to check
-- @return #bool <code>true</code> if <code>item</code> is in <code>array</code>
-- @author User:Avaren
local function in_array(item, array)
	-- Should only use on short arrays
	local set = {}
	for _, l in ipairs(array) do
		set[l] = true
	end
	return set[item] ~= nil
end

--- Build HTML code for an icon image.
-- @param name string
-- @param size string|nil One of: <code>"iconNormal"</code> (64px) or <code>"iconRecipe"</code> (44px). Default: <code>"iconNormal"</code>
-- @param bg string|nil
-- @param border string|nil
-- @param too_expensive boolean|nil
-- @author User:Avaren
function p.build_icon(name, link, size, bg, border, too_expensive)
	local L = require('Module:Localization') -- local import

	if not size then
		size = 'iconNormal'
	end
	local icon_bg
	if bg then
		icon_bg = bg
	end
	local icon_border
	if border then
		icon_border = border
	end

	local item_data = mw.loadData('Module:ItemData')
	local item = item_data.items[name]
	local image
	if item then
		if item['group'] == L.t('Skill Books') then
			image = 'SkillBook.png'
			icon_bg = 'iconGold'
		elseif item['group'] == L.t('Skill Scrolls') then
			image = 'Skill Scroll'
			icon_bg = 'iconGold'
			-- Attempt to generate skill page
		elseif in_array(L.t('Basic Research'), item['tagGroups']) then
			image = string.sub(item['untranslated'], 1, -7):gsub('%s+', '') .. '_Icon.png'
			icon_bg = 'paperBasic'
		elseif in_array(L.t('Advanced Research'), item['tagGroups']) then
			image = string.sub(item['untranslated'], 1, -10):gsub('%s+', '') .. '_Icon.png'
			icon_bg = 'paperAdvanced'
		elseif in_array(L.t('Modern Research'), item['tagGroups']) then
			image = string.sub(item['untranslated'], 1, -8):gsub('%s+', '') .. '_Icon.png'
			icon_bg = 'paperModern'
		else
			image = p.checkImage(item['untranslated'], too_expensive)
		end
		if not icon_bg then
			if item['group'] == L.t('Food') then
				icon_bg = 'iconGreen'
			elseif item['carried'] == L.t('Hands') then
				icon_bg = 'iconBrown'
			end
		end
	else
		image = p.checkImage(name, too_expensive)
	end

	if not icon_bg then
		icon_bg = 'iconBlue'
	end

	if border then
		icon_border = border
	else
		icon_border = 'borderBlue'
	end

	if size == 'iconNormal' then
		icon_container = 'iconContainer'
	else
		icon_container = 'iconContainerSmall'
	end

	if not link then
		link = ''
	else
		link = '|link='..link
	end

	local file = '[[File:' .. image .. '|frameless|class=' .. size .. ' ' .. icon_bg .. link ..']]'
	return '<div class="' .. icon_container .. '"><div class="iconStack">' .. file .. '</div><div class="iconBorder ' .. icon_border .. '" style="position:absolute;"></div></div>'
end

--- Get HTML code for an icon image.
-- @author User:Avaren
function p.Icon(frame)
	args = p.normaliseArgs(frame)
	return p.build_icon(args.name, args.link, args.size, args.bg, args.border, args.too_expensive)
end

--- Calculate the length of a table by iterating over every item in it.
--
-- <code>mw.LoadData</code> prevents <code>#tbl</code> from working correctly.
-- @param #table tbl Table to calculate the length of
-- @return #number Length of the table.
-- @author User:Avaren
function p.tableLen(tbl)
	local count = 0
	for _, v in ipairs(tbl) do
		if v == nil then
			return count
		end
		count = count + 1
	end
	return count
end

--- Check if <code>value</code> is not <code>nil</code> and return it or if it is <code>nil</code> fall back to <code>default</code>.
-- @param value Value to check
-- @param default Value to fall back to
-- @return <code>value</code> if it is not <code>nil</code>
-- @return <code>default</code> if <code>value</code> is <code>nil</code>
-- @author User:Demian
-- @see valueOrDash
-- @see formatNilToYesNo
-- @see formatBoolToYesNo
function p.valueOrDefault(value, default)
	return nil == value and default or value
end

--- Check if <code>value</code> is not <code>nil</code> and return it or if it is <code>nil</code> fall back to the em-dash (—).
--
-- The em-dash (—) is commonly used represent a missing, not applicable (N/A), or a negative ("no") value with just a single character.
-- @param value Value to check
-- @return <code>value</code> if it is not <code>nil</code>
-- @return #string "—" if <code>value</code> is <code>nil</code>
-- @author User:Demian
-- @see valueOrDefault
function p.valueOrDash(value)
	return nil == value and "—" or value
end

--- Check if <code>value</code> is not <code>nil</code> and return "Yes" or "No".
-- @param value Value to check
-- @return #string "Yes" if <code>value</code> is not <code>nil</code>
-- @return #string "No" if <code>value</code> is <code>nil</code>
-- @author User:Demian
-- @see valueOrDefault
-- @usage formatNilToYesNo("Hello") == "Yes"
-- @usage formatNilToYesNo(nil) == "No"
function p.formatNilToYesNo(value)
	-- TODO: Support i18n.
	return nil == value and "No" or "Yes"
end

--- Check if <code>value</code> <em>evaluates</em> as <code>true</code> and return "Yes" or "No".
-- @param value Value to evaluate. Does not have to be a bool.
-- @return #string "Yes" if <code>value</code> evaluates as <code>true</code>
-- @return #string "No" if <code>value</code> evaluates as <code>false</code>
-- @author User:Demian
-- @see valueOrDefault
-- @usage formatBoolToYesNo("") == true
-- @usage formatBoolToYesNo(123) == true
-- @usage formatBoolToYesNo(nil) == false
function p.formatBoolToYesNo(value)
	-- TODO: Support i18n.
	return value and "Yes" or "No"
end

--- Format the input values into a string representing the range between the values.
--
-- Returning an an empty string intended to ease concatenation with other strings.
-- The en-dash (–) (instead of the hyphen-minus "-") is the appropriate character to signify a range of values.
-- @param #number min Minimum value (left side)
-- @param #number max Maximum value (right side)
-- @param #number default Default value in case of an error (only value).
-- @param #string valueFormat Format string used with <code>mw.ustring.format</code>.
-- @return #string "<code>min</code>–<code>max</code>" if <code>min < max</code>
-- @return #string "<code>default</code>" formatted with <code>valueFormat</code> if <code>min == max</code> or <code>min > max</code> and <code>default ~= nil</code>
-- @return #string "" (empty string) if either <code>min</code> <strong>or</strong> <code>max</code> do not convert to a numerical value
-- @return #nil <code>nil</code> if <code>min == max</code> or <code>min > max</code> and <code>default == nil</code>
-- @author User:Demian
function p.toRangeString(min, max, default, valueFormat)
	min = tonumber(p.valueOrDefault(min, nil))
	max = tonumber(p.valueOrDefault(max, nil))
	default = tonumber(p.valueOrDefault(default, nil))

	if nil ~= min and nil ~= max then
		if min < max then
			return mw.ustring.format(mw.ustring.format("%s–%s", valueFormat, valueFormat), min, max)
		elseif nil == default then
			return nil
		else
			return mw.ustring.format(valueFormat, default)
		end
	end

	return ""
end

--- Get all keys from <code>tbl</code> and sort them in alphabetical order.
-- @param #table tbl Table to get keys from
-- @return #table Input table keys in alphabetical order.
-- @author User:Demian
-- @see getSortedValues
function p.getSortedKeys(tbl)
	local sorted = {}

	for key in pairs(tbl) do
		table.insert(sorted, key)
	end

	table.sort(sorted)

	return sorted
end

--- Get all values from <code>tbl</code> and sort them in alphabetical order.
-- @param #table tbl Table to get values from
-- @return #table Input table values in alphabetical order.
-- @author User:Demian
-- @see getSortedKeys
function p.getSortedValues(tbl)
	local sorted = {}

	for _, value in ipairs(tbl) do
		table.insert(sorted, value)
	end

	table.sort(sorted)

	return sorted
end

--- Split <code>str</code> by the given character.
-- @param #string str String to split
-- @param #string separator String that separates values in <code>str</code>. May optionally be surrounded by 1 <em>whitespace</em> character by default.
-- @return #table Table of strings that were split from <code>str</code>.
-- @author User:Demian
-- @usage splitString("hello, world", ",") == {"hello", "world"}
function p.splitString(str, separator)
	local tbl = {}

	for token in mw.ustring.gmatch(str, mw.ustring.format("%%s?([^%s]+)%%s?", separator)) do
		table.insert(tbl, token)
	end

	return tbl
end

--- Sort items in the given list of values <code>str</code> separated with <code>separator</code> and return them as a single string.
-- @param #string str String with values separated by <code>separator</code>
-- @param #string separator String that separates values in <code>str</code>
-- @param #string joiner String used to join sorted values from <code>str</code>.
-- @return #string <code>str</code> with items sorted in alphabetical order.
-- @author User:Demian
-- @usage sortListString("Dog,Ape, Cat", ",", ";") == "Ape;Cat;Dog"
function p.sortListString(str, separator, joiner)
	-- Split string by commas.
	-- Sort items.
	-- Rejoin into string.
	return table.concat(p.getSortedValues(p.splitString(str, separator)), joiner)
end

--- Check if a page with the title "<code>name</code> (<code>disambiguationTitle</code>)" exists in the database and return that page title, otherwise return "<code>name</code>".
--
-- Use sparingly as this uses a comparatively slow MediaWiki function p.to check if a page exists.
--
-- Using this function p.will create a new entry in the <code>Special:WantedPages</code> list.
-- Be careful when calling this function p.and do not pass garbage into its parameters so you do not clog up that list.
-- This is a long-standing issue with MediaWiki that has not yet been solved, and may not be possible to solve without an architectural change to the software.
-- @param #string name Name of a page.
-- @param #string disambiguationTitle Disambiguation clarifier in a page title.
-- @return #string "<code>name</code> (<code>disambiguationTitle</code>)"
-- @return #string "<code>name</code>"
-- @author User:Demian
function p.getDirectPageName(name, disambiguationTitle)
	-- Try to get the actual end page instead of the disambiguation page if it exists.
	-- E.g. Salmon has "Salmon (animal)" and "Salmon (item)" as well as the "Salmon" disambiguation page between these two.
	local directPage = mw.ustring.format("%s (%s)", name, disambiguationTitle)
	return mw.title.new(directPage).exists and directPage or name
end

--- Create a wikilink with [[square brackets]] from parameters.
-- @param #string pageName The actual name of a page to create a link to
-- @param #string displayText Text to display as a clickable link instead of the page name. If <code>nil</code>, <code>pageName</code> is displayed instead.
-- @param #bool twoLineDisplayText Force the <em>last word</em> of <code>displayText</code> on the next line
-- @return #string "[[<code>name</code>|<code>displayText</code>]]" if <code>displayText</code> is not <code>nil</code>
-- @return #string "[[<code>name</code>]]" if <code>displayText</code> is <code>nil</code> or the same string as <code>name</code>
-- @author User:Demian
function p.formatWikilink(pageName, displayText, twoLineDisplayText)
	local finalDisplayText = p.valueOrDefault(displayText, pageName)

	if twoLineDisplayText then
		local lastSpaceIdx = mw.ustring.find(finalDisplayText, " [^ ]*$")

		if nil ~= lastSpaceIdx then
			finalDisplayText = mw.ustring.format("%s<br>%s", mw.ustring.sub(finalDisplayText, 0, lastSpaceIdx-1), mw.ustring.sub(finalDisplayText, lastSpaceIdx+1))
		end
	end

	if pageName == finalDisplayText then
		return mw.ustring.format("[[%s]]", pageName)
	else
		return mw.ustring.format("[[%s|%s]]", pageName, finalDisplayText)
	end
end

return p