Module:Val: Difference between revisions

Content deleted Content added
accept ranges for input values; a range allows multiple values but no uncertainties
change 0.3em spacing to 0.225em (same total space as old code) per Template talk:Val#Proposal to reduce spacing around ±
 
(17 intermediate revisions by 5 users not shown)
Line 2:
-- Format options include scientific and uncertainty notations.
 
local data_modulenumdot = '.' -- namedecimal mark of(use module',' definingfor unitsItalian)
local numsep = ',' -- group separator (use ' ' for Italian)
local delimit_groups = require('Module:Gapnum').groups
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 function valerror(msg, nocat)
local convert_module = 'Module:Convert'
-- Return formatted message for {{val}} errors.
 
if is_test_run then -- LATER remove
local function valerror(msg, nocat, iswarning)
return 'Error: ' .. msg
-- 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
local ret = mw.html.create('strong')
if iswarning then
:addClass('error')
body = '<sup class="noprint Inline-Template" style="white-space:nowrap;">' ..
:wikitext('Error in &#123;&#123;val&#125;&#125;: ' .. msg)
'[[Template:Val|<span title="' ..
-- Not in talk, user, user_talk, or wikipedia_talk
msg:gsub('"', '&quot;') ..
if not nocat and not mw.title.getCurrentTitle():inNamespaces(1,2,3,5) then
'">warning</span>]]</sup>'
ret:wikitext('[[Category:Pages with incorrect formatting templates use]]')
else
body = '<strong class="error">' ..
'Error in &#123;&#123;[[Template:val|val]]&#125;&#125;: ' ..
msg ..
'</strong>'
end
return tostring(ret)anchor .. body .. category
end
 
Line 32 ⟶ 65:
["×"] = " × ",
["/"] = "/",
}
local range_repeat_unit = {
-- WP:UNIT wants unit repeated when a "multiply" range is used.
["x"] = true,
["×"] = true,
}
 
Line 43 ⟶ 81:
-- Input like 1e3 is regarded as invalid for all except argument 1
-- which accepts e notation as an alternative to the 'e' argument.
-- Input commasgroup separators are removed so 1,234 is the same as 1234.
local which = index
local function fail(msg)
local description
if which == 'e' then
description = mtext['mt-bad-exponent parameter (<b>e</b>)']
else
description = mtext['mt-parameter '] .. which
end
return description .. ' ' .. (msg or mtext['is mt-not a valid -number']) .. '.'
end
local result = {}
Line 60 ⟶ 98:
if index == 2 then
if numbers[1] and numbers[1].exp then
return fail(mtext['mt-cannot use a -range if the first parameter includes "e"'])
end
numbers.has_ranges = true
else
if not numbers.has_ranges then
return fail(mtext['needs a mt-need-range in parameter 2'])
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['does mt-not accept a -range'])
end
if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then
return fail(mtext['mt-should be a -range'])
end
if index == 'e' then
Line 80 ⟶ 122:
if e then
if arg then
return fail(mtext['mt-cannot be used if the first parameter includes "-with-e"'])
end
arg = e
Line 87 ⟶ 129:
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
Line 98 ⟶ 143:
result.exp = b
else
return fail(mtext['mt-cannot use -e notation'])
end
end
Line 136 ⟶ 181:
local function get_args(numbers, args)
-- Extract arguments and store the results in numbers.
-- Return nilnothing (no error) if ok; otherwise, return an error message.
for index = 1, 99 do
local which = index
Line 152 ⟶ 197:
end
if index > 19 then
return mtext['mt-too -many parameters-parameter']
end
end
if numbers.has_ranges and (#numbers % 2 == 0) then
return mtext['mt-need a -number after the last parameter because it is a range.']
end
end
Line 186 ⟶ 231:
end
end
error('Unit "' string.. ucode .. format(mtext['" has mt-invalid -scale "'], ..ucode, text .. '"'))
end
 
Line 194 ⟶ 239:
local _, pos = definitions:find('\n' .. ucode .. ' ', 1, true)
if pos then
local endline = definitions:find('%s*\n', pos, true)
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
Line 212 ⟶ 257:
n = n + 1
if n == 1 then
result.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
Line 239 ⟶ 291:
 
local function convert_lookup(ucode, value, scaled_top, want_link, si, options)
local lookup = require('Module:Convert/sandbox'convert_module)._unit -- TODO remove "/sandbox" when convert updated
return lookup(ucode, {
value = value,
Line 258 ⟶ 310:
get_builtin_unit(ucode, data.builtin_units_long_scale) or
get_builtin_unit(ucode, data.builtin_units)
local si, use_resultuse_convert
if result then
use_result = true
if result.alias then
ucode = result.symbol
use_resultuse_convert = falsetrue
end
if result.scale then
Line 277 ⟶ 328:
ucode = result.ucode or ucode
si = { result.symbol, result.link }
use_resultuse_convert = falsetrue
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_result then
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 .. ']]'
Line 287 ⟶ 345:
result.text = result.symbol
end
result.sortkey = convert_unit.sortspan
result.scaled_top = value
else
result = {
text = convert_unit.text,
sortkey = convert_unit.sortspan,
scaled_top = convert_unit.scaled_value,
}
end
return result
Line 324 ⟶ 375:
sortkey = perunit.sortkey
end
if not (unit.nospace or options.nospace) then
text = '&nbsp;' .. text
end
Line 330 ⟶ 381:
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.
Line 353 ⟶ 404:
local si_prefixes = {
-- These are the prefixes recognized by convert; u is accepted for micro.
y = true'y',
z = true'z',
a = true'a',
f = true'f',
p = true'p',
n = true'n',
u = true'µ',
['µ'] = true'µ',
m = true'm',
c = true'c',
d = true'd',
da = true'da',
h = true'h',
k = true'k',
M = true'M',
G = true'G',
T = true'T',
P = true'P',
E = true'E',
Z = true'Z',
Y = true'Y',
}
local function is_valid(ucode, unit)
-- LATER Check for valid combinations of links/flags.
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
localif plenucode == #ucode - #base then
unit.display = base
if ucode == base or (plen > 0 and si_prefixes[ucode:sub(1, plen)] and ucode:sub(plen + 1) == base) then
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
Line 397 ⟶ 493:
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.symboldisplay .. ']]'
end
if unit.isangle then
Line 412 ⟶ 509:
{ 'si', 'SI' },
{ 'scale_text', unit.scale_text },
{ 'show', unit.show_text },
}) do
if unit[f[1]] then
Line 439 ⟶ 537:
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()
Line 451 ⟶ 550:
local result
if fmt == 'commas' then
result = sign .. table.concat(ipart, ','numsep)
if dpart then
result = result .. '.'numdot .. table.concat(dpart)
end
else
Line 460 ⟶ 559:
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)
-- LATER Is the following needed?
-- It is for compatibility with {{val}} which uses {{val/delimitnum}}.
result = '<span style="white-space:nowrap">' .. sign .. result .. '</span>'
end
return result
Line 486 ⟶ 582:
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
 
Line 492 ⟶ 588:
local fmt = options.fmt
local nend = items.nend or ''
if items.isrepeat or unit_table.isangle then
nend = nend .. unit_table.text
end
Line 533 ⟶ 629:
else
text = (angle or '') ..
'<span style="margin-left:0.3em225em;margin-right:0.15em225em;">±</span>' ..
delimit('', uncU, fmt)
need_parens = true
Line 553 ⟶ 649:
 
local function _main(values, unit_spec, options)
data_moduleif = 'Module:Val/units' .. (options.sandbox and '/sandbox' or '')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()
return list_units(options.align)
end
return valerror('invalid action "' .. action .. '".', options.nocat)
Line 563 ⟶ 663:
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
Line 586 ⟶ 687:
per = unit_spec.per,
want_per_link = unit_spec.want_per_link,
nospace = novalue,
want_longscale = unit_spec.want_longscale,
sortable = sortable,
Line 600 ⟶ 702:
end
end
local final_unit = unit_table.isangle and '' or unit_table.text
local e_text, n_text, need_parens
local uncertainty = values.uncertainty
Line 612 ⟶ 715:
else
n_text = ''
if not e_10.clean then
e_10.clean = '0'
e_10.sign = ''
end
end
else
if values.numbers.isrepeat then
final_unit = ''
end
n_text = range_text(values.numbers, unit_table, options)
need_parens = true
Line 627 ⟶ 729:
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 =
return
'<span class="nowrap">' ..
(sortkey or '') ..
(options.prefix or '') ..
n_text ..
e_text ..
final_unit ..
(unit_table.isangle and '' or unit_table.text) ..
(options.suffix or '') ..
if result ~= '' then
'</span>'
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', 'Template:Val/sandboxlua' }})
local nocat = args.nocategory
local numbers = {} -- table of number tables, perhaps with range text
Line 653 ⟶ 792:
end
if args.u and args.ul then
return valerror(mtext['unit (<b>mt-both-u</b>) and unit with link (<b>-ul</b>) are both specified, only one is allowed.'], nocat)
end
if args.up and args.upl then
return valerror(mtext['unit per (<b>mt-both-up</b>) and unit per with link (<b>-upl</b>) are both specified, only one is allowed.'], nocat)
end
local values
Line 704 ⟶ 843:
prefix = args.p,
sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil,
sortable = args.sortable or (args.debug == 'yes' and 'debug' or 'on'nil),
suffix = args.s,
warning = check_parameters(args, numbers.has_ranges, nocat),
}
return _main(values, unit_spec, options)