Module:Currency

From Brighter Shores Wiki
Revision as of 21:51, 30 November 2024 by Im Wired In (talk | contribs) (Round copper to 2 decimal places)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
Module documentation
This documentation is transcluded from Module:Currency/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:Currency/doc. [edit]
Module:Currency's function cell is invoked by Template:Currency cell.
Module:Currency's function main is invoked by Template:Currency.
Module:Currency requires Module:Yesno.
Module:Currency requires strict.

require('strict')
local yesno = require('Module:Yesno')

local p = {}

local function parse_num(val)
	if val == nil then
		return nil
	end
	
	local x = tonumber(val)
	if x ~= nil then
		return x
	end
	
	x = tonumber((val:gsub(',', '')))
	if x ~= nil then
		return x
	end

	-- Try parsing as an expression if it wasn't a number
	local status, parsed = pcall(mw.ext.ParserFunctions.expr, val)
	if not status then
		if parsed == '' then
			-- Give a better error message than a blank string
			parsed = ('Could not interpret "%s" as a number'):format(val)
		end
		error(parsed)
	end
	
	return tonumber(parsed) or 0
end

function p.main(frame)
	local args = frame:getParent().args
	return p.parse(args[1] or 0, args)
end

function p.parse(val, options)
	options = options or {}
	val = parse_num(val)
	if val == nil then
		return nil
	end

	local prefix = val < 0 and '-' or (yesno(options.force_sign) and (val == 0 and '±' or '+') or '')
	-- Whether to show the sign on all components. For example,
	-- {{Currency|-143|all_signs=yes}} would result in "-1 [[File:Silver]] -43 [[File:Copper]]"
	local sign_all_components = yesno(options.all_signs) or false
	local absVal = math.abs(val)

	-- Control how zero value denominations are shown
	-- With show_zeros=all, it will show all zero value denominations after
	-- the first non-zero denomination
	-- With show_zeros=none, it will not show any denomination that has zero value
	-- With show_zeros=some (the default), it will show the zeros between
	-- the largest and smallest denominations.
	-- E.g., 4,000,002,000:
	--   show_zeros=all: "4 [[File:Platinum]] 0 [[File:Gold]] 2 [[File:Silver]] 0 [[File:Copper]]"
	--   show_zeros=none: "4 [[File:Platinum]] 2 [[File:Silver]]"
	--   show_zeros=some: "4 [[File:Platinum]] 0 [[File:Gold]] 2 [[File:Silver]]"
	options.show_zeros = (options.show_zeros or options.show_zeroes or ''):lower()
	local show_all_zeros = options.show_zeros == 'all'
	local show_no_zeros = (options.show_zeros == 'no' or options.show_zeros == 'none')
	local show_some_zeros = not (show_all_zeros or show_no_zeros)

	local output = mw.html.create('span')
		:attr('data-sort-value', val)
		:addClass('currency')
		:css('text-wrap', 'nowrap')

	local first_component = true
	local divisor = 1000000000  -- Pieces of copper in a platinum
	for i, denomination in ipairs{ 'Platinum', 'Gold', 'Silver', 'Copper' } do
		local denomValue
		if denomination == 'Copper' then
			-- Round to 2 decimal places
			denomValue = math.floor(absVal * 100 + 0.5) / 100
			absVal = 0
		else
			denomValue = math.floor(absVal / divisor)
			absVal = absVal % divisor
			divisor = divisor / 1000
		end

		-- Show if non-zero, we are showing this zero, or if we got to the last component
		-- and haven't output anything (for val == 0)
		if denomValue ~= 0 or (show_all_zeros and not first_component) or (show_some_zeros and not first_component and absVal ~= 0) or (first_component and i == 4) then
			if first_component then
				first_component = false
			else
				-- Make sure there is a space between components
				output:wikitext('&nbsp;')
				if not sign_all_components then
					prefix = ''
				end
			end
			output:wikitext(('%s%s&nbsp;[[File:%s coin.png|link=|20px|%s]]'):format(prefix, denomValue, denomination, denomination))
		end
	end

	return tostring(output)
end

function p.cell(frame)
	local args = frame:getParent().args
	return p._cell(args[1] or 0, args)
end

function p._cell(val, options)
	options = options or {}
	val = parse_num(val)

	local prefix = val < 0 and '-' or (yesno(options.force_sign) and (val == 0 and '±' or '+') or '')
	local absVal = math.abs(val)
	local class = val == 0 and 'currency-zero' or (val > 0 and 'currency-pos' or 'currency-neg')
	class = class .. ' currency-cell'
	if options.extra_class then
		class = class .. ' ' .. options.extra_class
	end

	-- Col/rowspan
	local left_colspan = (tonumber(options.left_colspan or '0') or 0) + 1
	local right_colspan = (tonumber(options.right_colspan or '0') or 0) + 1
	local rowspan = tonumber(options.rowspan or '0') or 0
	if left_colspan <= 1 then left_colspan = nil end
	if right_colspan <= 1 then right_colspan = nil end
	if rowspan <= 1 then rowspan = nil end

	-- Sign style
	-- {{sign=leading}} -> A new column in the front with the sign
	-- {{sign=all}} -> All currency cells get the sign prepended as text
	-- {{sign=first}} (default) -> Only the first cell with a value gets the sign
	options.sign = (options.sign or ''):lower()
	local sign_cell = options.sign == 'leading'
	local sign_all_components = options.sign == 'all'
	local sign_first = not (sign_cell or sign_all_components)

	options.show_zeros = (options.show_zeros or options.show_zeroes or ''):lower()
	local show_all_zeros = options.show_zeros == 'all'
	local show_no_zeros = (options.show_zeros == 'no' or options.show_zeros == 'none')
	local show_some_zeros = not (show_all_zeros or show_no_zeros)

	local output_cells = {}

	local first_component = true
	local divisor = 1000000000  -- Pieces of copper in a platinum

	-- Padding cell on the left
	table.insert(output_cells, {
		style = {
			['border-right'] = 'none',
			['width'] = '0',
			['padding-right'] = '0'
		},
		attributes = {
			['data-sort-value'] = tostring(val),
			colspan = left_colspan,
			rowspan = rowspan
		},
		content = ''
	})

	-- Convert the padding cell into the sign cell
	if sign_cell then
		output_cells[1].style['text-align'] = 'center'
		output_cells[1].style['padding-right'] = nil
		output_cells[1].content = prefix
		prefix = ''
	end

	for i, denomination in ipairs{ 'Platinum', 'Gold', 'Silver', 'Copper' } do
		local denomValue
		if denomination == 'Copper' then
			-- Round to 2 decimal places
			denomValue = math.floor(absVal * 100 + 0.5) / 100
			absVal = 0
		else
			denomValue = math.floor(absVal / divisor)
			absVal = absVal % divisor
			divisor = divisor / 1000
		end

		local value = {
			style = {
				['border-left'] = 'none',
				['border-right'] = 'none',
				['text-align'] = 'right',
				['width'] = '0'
			},
			content = '',
			attributes = {
				rowspan = rowspan
			}
		}
		local coin = {
			style = {
				['border-left'] = 'none',
				['border-right'] = 'none',
				['text-align'] = 'center',
				['width'] = '0'
			},
			content = '',
			attributes = {
				rowspan = rowspan
			}
		}

		-- Show if non-zero, we are showing this zero, or if we got to the last component
		-- and haven't output anything (for val == 0)
		if denomValue ~= 0 or (show_all_zeros and not first_component) or (show_some_zeros and not first_component) or (first_component and i == 4) then
			-- This is a "hidden" trailing zero, and will only show if any other row contains this component
			local hidden_trailing_zero = denomValue == 0 and show_some_zeros and not first_component and absVal == 0
			if first_component then
				first_component = false

				-- For the first component, merge it into the left cell if it is
				-- an empty image (i.e, not the first), so any leading sign
				-- won't artifically widen the column
				if i ~= 1 then
					table.remove(output_cells)
					value.attributes.colspan = '2'
					value.style['padding-left'] = '0'
				end
			elseif not sign_all_components then
				prefix = ''
			end
			value.content = prefix..tostring(denomValue)
			coin.content = ('[[File:%s coin.png|link=|20px|%s]]'):format(denomination, denomination)
			coin.style['padding-left'] = '0'
			coin.style['padding-right'] = '0'

			--[[
			Extremely disgusting hack to make this work.
			Each "hidden trailing zero" td will have width = 0, meaning
			the column overall will have zero width. There will be
			a 0 in this cell which is entirely in the overflow, which is
			hidden.
			*But* if there is at least one row with this column (e.g.,
			another cell has a copper=1, when this cell is silver=1 with
			no copper), then the zero will no longer overflow because the
			entire column will have width.
			The span has to be positioned on the very right of the td,
			but the zero has to be to the left of the span (the
			transform:translateX(-1em))
			]]--
			if hidden_trailing_zero then
				value.style.overflow = 'hidden'
				value.style.padding = '0'
				coin.style.overflow = 'hidden'

				local value_width = '1'
				if prefix ~= '' then
					value_width = '1.25'
				end
				value.content = '<span style="width:0;display:block;text-align:right;position:absolute;right:0;transform:translate(-'..value_width..'em, -0.75em)">'..value.content..'</span>'
				value.style.position = 'relative'
				coin.content = '<span style="width:0;display:block">'..coin.content..'</span>'
			end
		else
			value.style.padding = '0'
			coin.style.padding = '0'
		end
		
		table.insert(output_cells, value)
		table.insert(output_cells, coin)
	end

	-- One more cell for right padding
	table.insert(output_cells, {
		style = {
			['border-left'] = 'none',
			['padding-left'] = '0',
			['width'] = '0'
		},
		attributes = {
			rowspan = rowspan,
			colspan = right_colspan
		},
		content = ''
	})

	if yesno(options.html) then
		local output = mw.html.create()

		for _, cell in ipairs(output_cells) do
			output:tag('td')
				:css(cell.style)
				:addClass(class)
				:wikitext(cell.content)
				:attr(cell.attributes or {})
			:done()
		end

		return output
	end
	
	local output = {}
	for i, cell in ipairs(output_cells) do
		if i ~= 1 then
			table.insert(output, ' ||')
		end
		table.insert(output, (' class="%s" style="'):format(class))
		for name, value in pairs(cell.style) do
			table.insert(output, ('%s:%s;'):format(name, value))
		end
		table.insert(output, '" ')
		for attr, value in pairs(cell.attributes or {}) do
			table.insert(output, ('%s="%s" '):format(attr, value))
		end
		table.insert(output, ('|%s'):format(cell.content))
	end
	return table.concat(output, '')
end

return p