Anonymous
×
Create a new article
Write your page title here:
We currently have 15 articles on TwistedFates Database. Type your article name above or click on one of the titles below and start writing!



TwistedFates Database
15Articles

Documentation for this module may be created at Module:Val/doc

-- For Template:Val, output a number and optional unit.
-- Format options include scientific and uncertainty notations.

local numdot = '.'  -- decimal mark (use ',' for Italian)
local numsep = ','  -- group separator (use ' ' for Italian)
local mtext = {
	-- Message and other text that should be localized.
	['mt-bad-exponent'] =       'exponent parameter (<b>e</b>)',
	['mt-parameter'] =          'parameter ',
	['mt-not-number'] =         'is not a valid number',
	['mt-cannot-range'] =       'cannot use a range if the first parameter includes "e"',
	['mt-need-range'] =         'needs a range in parameter 2',
	['mt-should-range'] =       'should be a range',
	['mt-cannot-with-e'] =      'cannot be used if the first parameter includes "e"',
	['mt-not-range'] =          'does not accept a range',
	['mt-cannot-e'] =           'cannot use e notation',
	['mt-too-many-parameter'] = 'too many parameters',
	['mt-need-number'] =        'need a number after the last parameter because it is a range.',
	['mt-ignore-parameter4'] =  'Val parameter 4 ignored',
	['mt-val-not-supported'] =  'Val parameter "%s=%s" is not supported',
	['mt-invalid-scale'] =      'Unit "%s" has invalid scale "%s"',
	['mt-both-u-ul'] =          'unit (<b>u</b>) and unit with link (<b>ul</b>) are both specified, only one is allowed.',
	['mt-both-up-upl'] =        'unit per (<b>up</b>) and unit per with link (<b>upl</b>) are both specified, only one is allowed.',
}

local data_module = 'Module:Val/units'
local convert_module = 'Module:Convert'

local function valerror(msg, nocat, iswarning)
	-- Return formatted message text for an error or warning.
	-- Can append "#FormattingError" to URL of a page with a problem to find it.
	local anchor = '<span id="FormattingError"></span>'
	local body, category
	if nocat or mw.title.getCurrentTitle():inNamespaces(1, 2, 3, 5) then
		-- No category in Talk, User, User_talk, or Wikipedia_talk.
		category = ''
	else
		category = '[[Category:Pages with incorrect formatting templates use]]'
	end
	iswarning = false  -- problems are infrequent so try showing large error so editor will notice
	if iswarning then
		body = '<sup class="noprint Inline-Template" style="white-space:nowrap;">' ..
			'[[Template:Val|<span title="' ..
			msg:gsub('"', '&quot;') ..
			'">warning</span>]]</sup>'
	else
		body = '<strong class="error">' ..
			'Error in &#123;&#123;[[Template:val|val]]&#125;&#125;: ' ..
			msg ..
			'</strong>'
	end
	return anchor .. body .. category
end

local range_types = {
	-- No need for '&nbsp;' because nowrap applies to all output.
	[","]   = ", ",
	["by"]  = " by ",
	["-"]   = "–",
	["–"]   = "–",
	["and"] = " and ",
	["or"]  = " or " ,
	["to"]  = " to " ,
	["x"]   = " × ",
	["×"]   = " × ",
	["/"]   = "/",
}
local range_repeat_unit = {
	-- WP:UNIT wants unit repeated when a "multiply" range is used.
	["x"]   = true,
	["×"]   = true,
}

local function extract_item(index, numbers, arg)
	-- Extract an item from arg and store the result in numbers[index].
	-- If no argument or if argument is valid, return nil (no error);
	-- otherwise, return an error message.
	-- The stored result is:
	-- * a table for a number (empty if there was no specified number); or
	-- * a string for range text
	-- Input like 1e3 is regarded as invalid for all except argument 1
	-- which accepts e notation as an alternative to the 'e' argument.
	-- Input group separators are removed.
	local which = index
	local function fail(msg)
		local description
		if which == 'e' then
			description = mtext['mt-bad-exponent']
		else
			description = mtext['mt-parameter'] .. which
		end
		return description .. ' ' .. (msg or mtext['mt-not-number']) .. '.'
	end
	local result = {}
	local range = range_types[arg]
	if range then
		if type(index) == 'number' and (index % 2 == 0) then
			if index == 2 then
				if numbers[1] and numbers[1].exp then
					return fail(mtext['mt-cannot-range'])
				end
				numbers.has_ranges = true
			else
				if not numbers.has_ranges then
					return fail(mtext['mt-need-range'])
				end
			end
			numbers[index] = range
			if range_repeat_unit[arg] then
				-- Any "repeat" range forces unit (if any) to be repeated for all items.
				numbers.isrepeat = true
			end
			return nil
		end
		return fail(mtext['mt-not-range'])
	end
	if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then
		return fail(mtext['mt-should-range'])
	end
	if index == 'e' then
		local e = numbers[1] and numbers[1].exp
		if e then
			if arg then
				return fail(mtext['mt-cannot-with-e'])
			end
			arg = e
			which = 1
		end
	end
	if arg and arg ~= '' then
		arg = arg:gsub(numsep, '')
		if numdot ~= '.' then
			arg = arg:gsub(numdot, '.')
		end
		if arg:sub(1, 1) == '(' and arg:sub(-1) == ')' then
			result.parens = true
			arg = arg:sub(2, -2)
		end
		local a, b = arg:match('^(.+)[Ee](.+)$')
		if a then
			if index == 1 then
				arg = a
				result.exp = b
			else
				return fail(mtext['mt-cannot-e'])
			end
		end
		local isnegative, propersign, prefix
		local minus = '−'
		prefix, arg = arg:match('^(.-)([%d.]+)$')
		local value = tonumber(arg)
		if not value then
			return fail()
		end
		if arg:sub(1, 1) == '.' then
			arg = '0' .. arg
		end
		if prefix == '' then
			-- Ignore.
		elseif prefix == '±' then
			-- Display for first number, ignore for others.
			if index == 1 then
				propersign = '±'
			end
		elseif prefix == '+' then
			propersign = '+'
		elseif prefix == '-' or prefix == minus then
			propersign = minus
			isnegative = true
		else
			return fail()
		end
		result.clean = arg
		result.sign = propersign or ''
		result.value = isnegative and -value or value
	end
	numbers[index] = result
	return nil  -- no error
end

local function get_args(numbers, args)
	-- Extract arguments and store the results in numbers.
	-- Return nothing (no error) if ok; otherwise, return an error message.
	for index = 1, 99 do
		local which = index
		local arg = args[which]  -- has been trimmed
		if not arg then
			which = 'e'
			arg = args[which]
		end
		local msg = extract_item(which, numbers, arg)
		if msg then
			return msg
		end
		if which == 'e' then
			break
		end
		if index > 19 then
			return mtext['mt-too-many-parameter']
		end
	end
	if numbers.has_ranges and (#numbers % 2 == 0) then
		return mtext['mt-need-number']
	end
end

local function get_scale(text, ucode)
	-- Return the value of text as a number, or throw an error.
	-- This supports extremely basic expressions of the form:
	--   a / b
	--   a ^ b
	-- where a and b are numbers or 'pi'.
	local n = tonumber(text)
	if n then
		return n
	end
	n = text:gsub('pi', math.pi)
	for _, op in ipairs({ '/', '^' }) do
		local a, b = n:match('^(.-)' .. op .. '(.*)$')
		if a then
			a = tonumber(a)
			b = tonumber(b)
			if a and b then
				if op == '/' then
					return a / b
				elseif op == '^' then
					return a ^ b
				end
			end
			break
		end
	end
	error(string.format(mtext['mt-invalid-scale'], ucode, text))
end

local function get_builtin_unit(ucode, definitions)
	-- Return table of information for the specified built-in unit, or nil if not known.
	-- Each defined unit code must be followed by two spaces (not tab characters).
	local _, pos = definitions:find('\n' .. ucode .. '  ', 1, true)
	if pos then
		local endline = definitions:find('%s*\n', pos)
		if endline then
			local result = {}
			local n = 0
			local text = definitions:sub(pos + 1, endline - 1):gsub('%s%s+', '\t')
			for item in (text .. '\t'):gmatch('(%S.-)\t') do
				if item == 'ALIAS' then
					result.alias = true
				elseif item == 'ANGLE' then
					result.isangle = true
					result.nospace = true
				elseif item == 'NOSPACE' then
					result.nospace = true
				elseif item == 'SI' then
					result.si = true
				else
					n = n + 1
					if n == 1 then
						local link, symbol = item:match('^%[%[([^|]+)|(.+)%]%]$')
						if link then
							result.symbol = symbol
							result.link = link
							n = 2
						else
							result.symbol = item
						end
					elseif n == 2 then
						result.link = item
					elseif n == 3 then
						result.scale_text = item
						result.scale = get_scale(item, ucode)
					else
						result.more_ignored = item
						break
					end
				end
			end
			if result.si then
				local s = result.symbol
				if ucode == 'mc' .. s or ucode == 'mu' .. s then
					result.ucode = 'µ' .. s  -- unit code for convert should be this
				end
			end
			if n >= 2 or (n >= 1 and result.alias) then
				return result
			end
			-- Ignore invalid definition, treating it as a comment.
		end
	end
end

local function convert_lookup(ucode, value, scaled_top, want_link, si, options)
	local lookup = require(convert_module)._unit
	return lookup(ucode, {
			value = value,
			scaled_top = scaled_top,
			link = want_link,
			si = si,
			sort = options.sortable,
		})
end

local function get_unit(ucode, value, scaled_top, options)
	local want_link = options.want_link
	if scaled_top then
		want_link = options.want_per_link
	end
	local data = mw.loadData(data_module)
	local result = options.want_longscale and
		get_builtin_unit(ucode, data.builtin_units_long_scale) or
		get_builtin_unit(ucode, data.builtin_units)
	local si, use_convert
	if result then
		if result.alias then
			ucode = result.symbol
			use_convert = true
		end
		if result.scale then
			-- Setting si means convert will use the unit as given, and the sort key
			-- will be calculated from the value without any extra scaling that may
			-- occur if convert found the unit code. For example, if val defines the
			-- unit 'year' with a scale and if si were not set, convert would also apply
			-- its own scale because convert knows that a year is 31,557,600 seconds.
			si = { result.symbol, result.link }
			value = value * result.scale
		end
		if result.si then
			ucode = result.ucode or ucode
			si = { result.symbol, result.link }
			use_convert = true
		end
	else
		result = {}
		use_convert = true
	end
	local convert_unit = convert_lookup(ucode, value, scaled_top, want_link, si, options)
	result.sortkey = convert_unit.sortspan
	if use_convert then
		result.text = convert_unit.text
		result.scaled_top = convert_unit.scaled_value
	else
		if want_link then
			result.text = '[[' .. result.link .. '|' .. result.symbol .. ']]'
		else
			result.text = result.symbol
		end
		result.scaled_top = value
	end
	return result
end

local function makeunit(value, options)
	-- Return table of information for the requested unit and options, or
	-- return nil if no unit.
	options = options or {}
	local unit
	local ucode = options.u
	local percode = options.per
	if ucode then
		unit = get_unit(ucode, value, nil, options)
	elseif percode then
		unit = { nospace = true, scaled_top = value }
	else
		return nil
	end
	local text = unit.text or ''
	local sortkey = unit.sortkey
	if percode then
		local function bracketed(code, text)
			return code:find('[*./]') and '(' .. text .. ')' or text
		end
		local perunit = get_unit(percode, 1, unit.scaled_top, options)
		text = (ucode and bracketed(ucode, text) or '') ..
				'/' .. bracketed(percode, perunit.text)
		sortkey = perunit.sortkey
	end
	if not (unit.nospace or options.nospace) then
		text = '&nbsp;' .. text
	end
	return { text = text, isangle = unit.isangle, sortkey = sortkey }
end

local function list_units(mode)
	-- Return wikitext to list the built-in units.
	-- A unit code should not contain wikimarkup so don't bother escaping.
	local data = mw.loadData(data_module)
	local definitions = data.builtin_units .. data.builtin_units_long_scale
	local last_was_blank = true
	local n = 0
	local result = {}
	local function add(line)
		if line == '' then
			last_was_blank = true
		else
			if last_was_blank and n > 0 then
				n = n + 1
				result[n] = ''
			end
			last_was_blank = false
			n = n + 1
			result[n] = line
		end
	end
	local si_prefixes = {
		-- These are the prefixes recognized by convert; u is accepted for micro.
		y = 'y',
		z = 'z',
		a = 'a',
		f = 'f',
		p = 'p',
		n = 'n',
		u = 'µ',
		['µ'] = 'µ',
		m = 'm',
		c = 'c',
		d = 'd',
		da = 'da',
		h = 'h',
		k = 'k',
		M = 'M',
		G = 'G',
		T = 'T',
		P = 'P',
		E = 'E',
		Z = 'Z',
		Y = 'Y',
	}
	local function is_valid(ucode, unit)
		if unit and not unit.more_ignored then
			assert(type(unit.symbol) == 'string' and unit.symbol ~= '')
			if unit.alias then
				if unit.link or unit.scale_text or unit.si then
					return false
				end
			end
			if unit.si then
				if unit.scale_text then
					return false
				end
				ucode = unit.ucode or ucode
				local base = unit.symbol
				if ucode == base then
					unit.display = base
					return true
				end
				local plen = #ucode - #base
				if plen > 0 then
					local prefix = si_prefixes[ucode:sub(1, plen)]
					if prefix and ucode:sub(plen + 1) == base then
						unit.display = prefix .. base
						return true
					end
				end
			else
				unit.display = unit.symbol
				return true
			end
		end
		return false
	end
	local lookup = require(convert_module)._unit
	local function show_convert(ucode, unit)
		-- If a built-in unit defines a scale or sets the SI flag, any unit defined in
		-- convert is not used (the scale or SI prefix's scale is used for a sort key).
		-- If there is no scale or SI flag, and the unit is not defined in convert,
		-- the sort key may not be correct; this allows such units to be identified.
		if not (unit.si or unit.scale_text) then
			if mode == 'convert' then
				unit.show = not lookup(unit.alias and unit.symbol or ucode).unknown
				unit.show_text = 'CONVERT'
			elseif mode == 'unknown' then
				unit.show = lookup(unit.alias and unit.symbol or ucode).unknown
				unit.show_text = 'UNKNOWN'
			elseif not unit.alias then
				-- Show convert's scale in square brackets ('[1]' for an unknown unit).
				-- Don't show scale for an alias because it's misleading for temperature
				-- and an alias is probably not useful for anything else.
				local scale = lookup(ucode, {value=1, sort='on'}).scaled_value
				if type(scale) == 'number' then
					scale = string.format('%.5g', scale):gsub('e%+?(%-?)0*(%d+)', 'e%1%2')
				else
					scale = '?'
				end
				unit.show = true
				unit.show_text = '[' .. scale .. ']'
			end
		end
	end
	for line in definitions:gmatch('([^\n]*)\n') do
		local pos, _ = line:find('  ', 1, true)
		if pos then
			local ucode = line:sub(1, pos - 1)
			local unit = get_builtin_unit(ucode, '\n' .. line .. '\n')
			if is_valid(ucode, unit) then
				show_convert(ucode, unit)
				local flags, text
				if unit.alias then
					text = unit.symbol
				else
					text = '[[' .. unit.link .. '|' .. unit.display .. ']]'
				end
				if unit.isangle then
					unit.nospace = nil  -- don't show redundant flag
				end
				for _, f in ipairs({
						{ 'alias', 'ALIAS' },
						{ 'isangle', 'ANGLE' },
						{ 'nospace', 'NOSPACE' },
						{ 'si', 'SI' },
						{ 'scale_text', unit.scale_text },
						{ 'show', unit.show_text },
					}) do
					if unit[f[1]] then
						local t = f[2]
						if t:match('^%u+$') then
							t = '<small>' .. t .. '</small>'
						end
						if flags then
							flags = flags .. ' ' .. t
						else
							flags = t
						end
					end
				end
				if flags then
					text = text .. ' • ' .. flags
				end
				add(ucode .. ' = ' .. text .. '<br />')
			else
				add(line .. ' ◆ <b>invalid definition</b><br />')
			end
		else
			add(line)
		end
	end
	return table.concat(result, '\n')
end

local delimit_groups = require('Module:Gapnum').groups
local function delimit(sign, numstr, fmt)
	-- Return sign and numstr (unsigned digits or numdot only) after formatting.
	-- Four-digit integers are not formatted with gaps.
	fmt = (fmt or ''):lower()
	if fmt == 'none' or (fmt == '' and #numstr == 4 and numstr:match('^%d+$')) then
		return sign .. numstr
	end
	-- Group number by integer and decimal parts.
	-- If there is no decimal part, delimit_groups returns only one table.
	local ipart, dpart = delimit_groups(numstr)
	local result
	if fmt == 'commas' then
		result = sign .. table.concat(ipart, numsep)
		if dpart then
			result = result .. numdot .. table.concat(dpart)
		end
	else
		-- Delimit with a small gap by default.
		local groups = {}
		groups[1] = table.remove(ipart, 1)
		for _, v in ipairs(ipart) do
			table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')
		end
		if dpart then
			table.insert(groups, numdot .. (table.remove(dpart, 1) or ''))
			for _, v in ipairs(dpart) do
				table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')
			end
		end
		result = sign .. table.concat(groups)
	end
	return result
end

local function sup_sub(sup, sub, align)
	-- Return the same result as Module:Su except val defaults to align=right.
	if align == 'l' or align == 'left' then
		align = 'left'
	elseif align == 'c' or align == 'center' then
		align = 'center'
	else
		align = 'right'
	end
	return '<span style="display:inline-block;margin-bottom:-0.3em;vertical-align:-0.4em;line-height:1.2em;font-size:85%;text-align:' ..
		align .. ';">' .. sup .. '<br />' .. sub .. '</span>'
end

local function range_text(items, unit_table, options)
	local fmt = options.fmt
	local nend = items.nend or ''
	if items.isrepeat or unit_table.isangle then
		nend = nend .. unit_table.text
	end
	local text = ''
	for i = 1, #items do
		if i % 2 == 0 then
			text = text .. items[i]
		else
			text = text .. delimit(items[i].sign, items[i].clean, fmt) .. nend
		end
	end
	return text
end

local function uncertainty_text(uncertainty, unit_table, options)
	local angle, text, need_parens
	if unit_table.isangle then
		angle = unit_table.text
	end
	local upper = uncertainty.upper or {}
	local lower = uncertainty.lower or {}
	local uncU = upper.clean
	if uncU then
		local fmt = options.fmt
		local uncL = lower.clean
		if uncL then
			uncU = delimit('+', uncU, fmt) .. (upper.errend or '')
			uncL = delimit('−', uncL, fmt) .. (lower.errend or '')
			if angle then
				uncU = uncU .. angle
				uncL = uncL .. angle
			end
			text = (angle or '') ..
				'<span style="margin-left:0.3em;">' ..
				sup_sub(uncU, uncL, options.align) ..
				'</span>'
		else
			if upper.parens then
				text = '(' .. uncU .. ')'  -- old template did not delimit
			else
				text = (angle or '') ..
					'<span style="margin-left:0.3em;margin-right:0.15em;">±</span>' ..
					delimit('', uncU, fmt)
				need_parens = true
			end
			if uncertainty.errend then
				text = text .. uncertainty.errend
			end
			if angle then
				text = text .. angle
			end
		end
	else
		if angle then
			text = angle
		end
	end
	return text, need_parens
end

local function _main(values, unit_spec, options)
	if options.sandbox then
		data_module = data_module .. '/sandbox'
		convert_module = convert_module .. '/sandbox'
	end
	local action = options.action
	if action then
		if action == 'list' then
			-- Kludge: am using the align parameter (a=xxx) for type of list.
			return list_units(options.align)
		end
		return valerror('invalid action "' .. action .. '".', options.nocat)
	end
	local number = values.number or (values.numbers and values.numbers[1]) or {}
	local e_10 = options.e or {}
	local novalue = (number.value == nil and e_10.clean == nil)
	local fmt = options.fmt
	local want_sort = true
	local sortable = options.sortable
	if sortable == 'off' or (sortable == nil and novalue) then
		want_sort = false
	elseif sortable == 'debug' then
		-- Same as sortable = 'on' but the sort key is displayed.
	else
		sortable = 'on'
	end
	local sort_value = 1
	if want_sort then
		sort_value = number.value or 1
		if e_10.value and sort_value ~= 0 then
			-- The 'if' avoids {{val|0|e=1234}} giving an invalid sort_value due to overflow.
			sort_value = sort_value * 10^e_10.value
		end
	end
	local unit_table = makeunit(sort_value, {
						u = unit_spec.u,
						want_link = unit_spec.want_link,
						per = unit_spec.per,
						want_per_link = unit_spec.want_per_link,
						nospace = novalue,
						want_longscale = unit_spec.want_longscale,
						sortable = sortable,
					})
	local sortkey
	if unit_table then
		if want_sort then
			sortkey = unit_table.sortkey
		end
	else
		unit_table = { text = '' }
		if want_sort then
			sortkey = convert_lookup('dummy', sort_value, nil, nil, nil, { sortable = sortable }).sortspan
		end
	end
	local final_unit = unit_table.isangle and '' or unit_table.text
	local e_text, n_text, need_parens
	local uncertainty = values.uncertainty
	if uncertainty then
		if number.clean then
			n_text = delimit(number.sign, number.clean, fmt) .. (number.nend or '')
			local text
			text, need_parens = uncertainty_text(uncertainty, unit_table, options)
			if text then
				n_text = n_text .. text
			end
		else
			n_text = ''
		end
	else
		if values.numbers.isrepeat then
			final_unit = ''
		end
		n_text = range_text(values.numbers, unit_table, options)
		need_parens = true
	end
	if e_10.clean then
		if need_parens then
			n_text = '(' .. n_text .. ')'
		end
		e_text = '10<sup>' .. delimit(e_10.sign, e_10.clean, fmt) .. '</sup>'
		if number.clean then
			e_text = '<span style="margin-left:0.25em;margin-right:0.15em;">×</span>' .. e_text
		end
	else
		e_text = ''
	end
	local result =
		(sortkey or '') ..
		(options.prefix or '') ..
		n_text ..
		e_text ..
		final_unit ..
		(options.suffix or '')
	if result ~= '' then
		result = '<span class="nowrap">' .. result .. '</span>'
	end
	return result .. (options.warning or '')
end

local function check_parameters(args, has_ranges, nocat)
	-- Return warning text for the first problem parameter found, or nothing if ok.
	local whitelist = {
		a = true,
		action = true,
		debug = true,
		e = true,
		['end'] = true,
		errend = true,
		['+errend'] = true,
		['-errend'] = true,
		fmt = true,
		['long scale'] = true,
		long_scale = true,
		longscale = true,
		nocategory = true,
		p = true,
		s = true,
		sortable = true,
		u = true,
		ul = true,
		up = true,
		upl = true,
	}
	for k, v in pairs(args) do
		if type(k) == 'string' and not whitelist[k] then
			local warning = string.format(mtext['mt-val-not-supported'], k, v)
			return valerror(warning, nocat, true)
		end
	end
	if not has_ranges and args[4] then
		return valerror(mtext['mt-ignore-parameter4'], nocat, true)
	end
end

local function main(frame)
	local getArgs = require('Module:Arguments').getArgs
	local args = getArgs(frame, {wrappers = { 'Template:Val' }})
	local nocat = args.nocategory
	local numbers = {}  -- table of number tables, perhaps with range text
	local msg = get_args(numbers, args)
	if msg then
		return valerror(msg, nocat)
	end
	if args.u and args.ul then
		return valerror(mtext['mt-both-u-ul'], nocat)
	end
	if args.up and args.upl then
		return valerror(mtext['mt-both-up-upl'], nocat)
	end
	local values
	if numbers.has_ranges then
		-- Multiple values with range separators but no uncertainty.
		numbers.nend = args['end']
		values = {
			numbers = numbers,
		}
	else
		-- A single value with optional uncertainty.
		local function setfield(i, dst, src)
			local v = args[src]
			if v then
				if numbers[i] then
					numbers[i][dst] = v
				else
					numbers[i] = { [dst] = v }
				end
			end
		end
		setfield(1, 'nend', 'end')
		setfield(2, 'errend', '+errend')
		setfield(3, 'errend', '-errend')
		values = {
			number = numbers[1],
			uncertainty = {
				upper = numbers[2],
				lower = numbers[3],
				errend = args.errend,
			}
		}
	end
	local unit_spec = {
			u = args.ul or args.u,
			want_link = args.ul ~= nil,
			per = args.upl or args.up,
			want_per_link = args.upl ~= nil,
			want_longscale = (args.longscale or args.long_scale or args['long scale']) == 'on',
		}
	local options = {
			action = args.action,
			align = args.a,
			e = numbers.e,
			fmt = args.fmt,
			nocat = nocat,
			prefix = args.p,
			sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil,
			sortable = args.sortable or (args.debug == 'yes' and 'debug' or nil),
			suffix = args.s,
			warning = check_parameters(args, numbers.has_ranges, nocat),
		}
	return _main(values, unit_spec, options)
end

return { main = main, _main = _main }