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: Difference between revisions

From Eco - English Wiki
[checked revision][checked revision]
No edit summary
m (Undo revision 8382 by Demian (talk) Broke the function.)
Tag: Undo
 
(20 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


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


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


    return args
return args
end
end


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


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


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


function p.build_icon(name, size, bg, border, too_expensive)
--- Build HTML code for an icon image.
    -- Size options are iconNormal or iconRecipe - 64px or 44px - defaults to iconNormal
-- @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


    local L = require('Module:Localization') -- local import
if not size then
   
size = 'iconNormal'
    if not size then
end
        size = 'iconNormal'
local icon_bg
    end
if bg then
    local icon_bg = ''
icon_bg = bg
    local icon_border = ''
end
local icon_border
if border then
icon_border = border
end


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


    if border then
if not icon_bg then
        icon_border = border
icon_bg = 'iconBlue'
    end
end


    local file = '[[File:' .. image .. '|frameless|class=' .. size .. ' ' .. icon_bg .. ']]'
if border then
    return '<div class="iconContainer"><div class="iconStack">' .. file .. '</div><div class="iconBorder ' .. icon_border .. '" style="position:absolute;"></div></div>'
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
end


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


-- mw.LoadData prevents #table from working correctly
--- 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)
function p.tableLen(tbl)
    local count = 0
local count = 0
    for _, v in ipairs(tbl) do
for _, v in ipairs(tbl) do
        if v == nil then
if v == nil then
            return count
return count
        end
end
        count = count + 1
count = count + 1
    end
end
    return count
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
 
--- Add thousands separator to given number and use custom decimal point.
--
-- Extension:NumberFormat is more extensive, but more cumbersome to use and also not installed at the moment.
-- @param #string number Number to format. Is processed as a string regardless of type.
-- @param #string thousandsSeparator String to place between each set of 3 digits. Default: " "
-- @param #string decimalPoint String to place between the whole and fractional part of the number. Default: "."
-- @return #string <code>number</code> with the specified thousands separator and decimal point.
-- @return #string <code>number</code> unchanged if it contained 1 or more characters that are <strong>not</strong> a: digit, one of ".,-", a space.
-- @author User:Demian
-- @usage formatNumber(-1234567.89) = "-1 234 567.89"
-- @usage formatNumber("1234567,89", ".", "_") = "1,234,567_89"
function p.formatNumber(number, thousandsSeparator, decimalPoint)
-- Default separator to space.
if nil == thousandsSeparator then
thousandsSeparator = " "
end
 
-- Default point to period.
if nil == decimalPoint then
decimalPoint = "."
end
 
-- We're dealing with formatting a string here.
local numberString = tostring(number)
 
-- Check if the input number is reasonable.
-- Does NOT check for multiple instance of each character.
-- E.g. Inputting something like 123-456.789 will lead to incorrect results.
-- I can't handle every edge case: garbage in, garbage out.
-- The user has to have some responsibility in inputting reasonable numbers.
if mw.ustring.find(numberString, "[^%d%.%-, ]") then
return number
end
 
-- Split input into parts.
-- 1st group: MAY start with a "-".
-- 2nd group: MUST contain 1 or more digits-
-- 3rd group: MAY start with with one of ".,"
-- 3rd group: MAY have 0 or more digits.
local _, _, minus, digits, fraction = mw.ustring.find(tostring(numberString), "(-?)(%d+)([%.,]?%d*)")
 
-- Reverse the string of digits.
-- Append the thousands separator after (before when reversed again) each set of 3 digits.
digits = mw.ustring.gsub(string.reverse(digits), "(%d%d%d)", mw.ustring.format("%%1%s", thousandsSeparator))
 
-- Replace the existing decimal separator with the specified one.
if "" ~= fraction then
fraction = mw.ustring.format("%s%s",decimalPoint, mw.ustring.sub(fraction, 2))
end
 
-- Reverse the string of digits back to the original direction.
-- If the string digits starts with the thousands separator, remove the separator.
-- Add the optional minus in front and the optional fractional part at the back.
-- Need to remember to escape the thousandsSeparator, it could be "." which would translate to "any character"!
return mw.ustring.format("%s%s%s", minus, mw.ustring.gsub(string.reverse(digits), mw.ustring.format("^%%s", thousandsSeparator), ""), fraction)
end
 
--- Check if the string is empty or <code>nil</code>.
-- @param #string str String to check
-- @return #bool <code>true</code> if <code>str</code> is <code>nil</code> or an empty string ("").
-- @author User:Demian
function p.isEmpty(str)
return nil == str or "" == str
end
end


return p
return p

Latest revision as of 22:31, 2 March 2022

This module provides utility functions used from other modules.

Usage[edit source]

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

--- Add thousands separator to given number and use custom decimal point.
--
-- Extension:NumberFormat is more extensive, but more cumbersome to use and also not installed at the moment.
-- @param #string number Number to format. Is processed as a string regardless of type.
-- @param #string thousandsSeparator String to place between each set of 3 digits. Default: " "
-- @param #string decimalPoint String to place between the whole and fractional part of the number. Default: "."
-- @return #string <code>number</code> with the specified thousands separator and decimal point.
-- @return #string <code>number</code> unchanged if it contained 1 or more characters that are <strong>not</strong> a: digit, one of ".,-", a space.
-- @author User:Demian
-- @usage formatNumber(-1234567.89) = "-1 234 567.89"
-- @usage formatNumber("1234567,89", ".", "_") = "1,234,567_89"
function p.formatNumber(number, thousandsSeparator, decimalPoint)
	-- Default separator to space.
	if nil == thousandsSeparator then
		thousandsSeparator = " "
	end

	-- Default point to period.
	if nil == decimalPoint then
		decimalPoint = "."
	end

	-- We're dealing with formatting a string here.
	local numberString = tostring(number)

	-- Check if the input number is reasonable.
	-- Does NOT check for multiple instance of each character.
	-- E.g. Inputting something like 123-456.789 will lead to incorrect results.
	-- I can't handle every edge case: garbage in, garbage out.
	-- The user has to have some responsibility in inputting reasonable numbers.
	if mw.ustring.find(numberString, "[^%d%.%-, ]") then
		return number
	end

	-- Split input into parts.
	-- 1st group: MAY start with a "-".
	-- 2nd group: MUST contain 1 or more digits-
	-- 3rd group: MAY start with with one of ".,"
	-- 3rd group: MAY have 0 or more digits.
	local _, _, minus, digits, fraction = mw.ustring.find(tostring(numberString), "(-?)(%d+)([%.,]?%d*)")

	-- Reverse the string of digits.
	-- Append the thousands separator after (before when reversed again) each set of 3 digits.
	digits = mw.ustring.gsub(string.reverse(digits), "(%d%d%d)", mw.ustring.format("%%1%s", thousandsSeparator))

	-- Replace the existing decimal separator with the specified one.
	if "" ~= fraction then
		fraction = mw.ustring.format("%s%s",decimalPoint, mw.ustring.sub(fraction, 2))
	end

	-- Reverse the string of digits back to the original direction.
	-- If the string digits starts with the thousands separator, remove the separator.
	-- Add the optional minus in front and the optional fractional part at the back.
	-- Need to remember to escape the thousandsSeparator, it could be "." which would translate to "any character"!
	return mw.ustring.format("%s%s%s", minus, mw.ustring.gsub(string.reverse(digits), mw.ustring.format("^%%s", thousandsSeparator), ""), fraction)
end

--- Check if the string is empty or <code>nil</code>.
-- @param #string str String to check
-- @return #bool <code>true</code> if <code>str</code> is <code>nil</code> or an empty string ("").
-- @author User:Demian
function p.isEmpty(str)
	return nil == str or "" == str
end

return p