Module:Convert: Difference between revisions

Content deleted Content added
approximate handling of convert/2, convert/3, convert/4, as well as flip2, flip3, flip4
can define extra units with lower overhead; bn has more than 2 characters in an SI prefix; fix excessive sigfigs bugs; simplify language handler; use_require option not needed
Line 20:
-- Configuration options to keep magic values in one ___location.
-- The conversion data and message text are defined in separate modules.
-- To allow easy comparison between "require" and "loadData", a config option
-- can be set to control which is used.
local numdot, numsep -- each must be a single byte for simple regex search/replace
local maxsigfig, warnings
local default_exceptions, link_exceptions, unitsall_units
local SIprefixes, all_categories, all_messages, customary_units, disp_joins
local en_option_value, eng_scales, local_option_name, range_aliases, range_types
Line 31 ⟶ 29:
local from_en_table -- to translate an output string of en digits to local language
local to_en_table -- to translate an input string of digits in local language to en
 
-- All units should be defined in the data module. However, to cater for quick changes
-- and experiments, any unknown unit is looked up in an extra data module, if it exists.
-- That module would be transcluded in only a small number of pages, so there should be
-- little server overhead from making changes, and changes should propagate quickly.
local extra_module -- name of module with extra units
local extra_units -- nil or table of extra units from extra_module
 
local function boolean(text)
Line 87 ⟶ 92:
local data_module, text_module, data_code, text_code
if is_test_run then
local langlangcode = mw.language.getContentLanguage().code
data_module = "convertdata"
text_moduledata_module = "converttextconvertdata-" .. langcode
text_module = "converttext-" .. langcode
extra_module = "convertextra"
spell_module = "ConvertNumeric"
else
data_module = "Module:Convert/data"
text_module = "Module:Convert/text"
extra_module = "Module:Convert/extra"
spell_module = "Module:ConvertNumeric"
end
data_code = mw.loadData(data_module)
if args.use_require then
data_codetext_code = requiremw.loadData(data_moduletext_module)
text_code = require(text_module)
else
data_code = mw.loadData(data_module)
text_code = mw.loadData(text_module)
end
default_exceptions = data_code.default_exceptions
link_exceptions = data_code.link_exceptions
unitsall_units = data_code.unitsall_units
SIprefixes = text_code.SIprefixes
all_categories = text_code.all_categories
Line 115 ⟶ 118:
range_aliases = text_code.range_aliases
range_types = text_code.range_types
local translation = text_code.translation_table[lang.code]
-- LATER: Simplify following by using a translation table if it is defined,
-- without needing to check the current wiki's language. Am using this more
-- generic treatment while testing.
local lang = mw.language.getContentLanguage()
local translation = text_code.translation_table[lang.code]
if translation then
if translation.group then
Line 408 ⟶ 407:
}
 
local function lookup(unitcode, opt_sp_us, what, utable, depth)
-- Return true, t where t is a copy of the unit's converter table,
-- or return false, t where t is an error message table.
Line 425 ⟶ 424:
-- For compatibility with the old template, underscores in unitcode are replaced
-- with spaces so {{convert|350|board_feet}} --> 350 board feet (0.83 m³).
utable = utable or all_units
depth = depth and depth + 1 or 1
if args.use_requiredepth > 9 then
-- There are ways to mistakenly define units which result in infinite
-- recursion when lookup() is called. That gives a long delay and very
-- confusing error messages, so the depth parameter is used as a guard.
return false, { 'cvt_lookup', unitcode }
elseend
if unitcode == nil or unitcode == '' then
return false, { 'cvt_no_unit' }
end
unitcode = unitcode:gsub('_', ' ')
local t = unitsutable[unitcode]
if t then
if t.shouldbe then
Line 441 ⟶ 448:
local target = t.target -- nil, or unitcode is an alias for this target
if target then
local success, result = lookup(target, opt_sp_us, what, utable, depth)
if not success then return false, result end
override_from(result, t, { 'customary', 'default', 'link', 'symbol', 'symlink' })
Line 462 ⟶ 469:
prefix = v
else
local success, t = lookup(v, opt_sp_us, 'no_combination', utable, depth)
if not success then return false, t end
cvt[i] = t
Line 489 ⟶ 496:
local cvt = result.combination
for i, v in ipairs(combo) do
local success, t = lookup(v, opt_sp_us, multiple and 'no_combination' or 'only_multiple', utable, depth)
if not success then return false, t end
cvt[i] = t
Line 504 ⟶ 511:
return true, setmetatable(result, unit_mt)
end
for plen = SIprefixes[1] or 2, 1, -1 do
-- Look for an SI prefix; should never occur with an alias.
-- Check for longer prefix first ('dam' is decametre).
-- SIprefixes[1] = prefix maximum #characters (as seen by mw.ustring.sub).
local prefix = usub(unitcode, 1, plen)
local si = SIprefixes[prefix]
if si then
local t = unitsutable[usub(unitcode, plen+1)]
if t and t.prefixes then
local result = shallow_copy(t)
Line 536 ⟶ 544:
local engscale = eng_scales[exponent]
if engscale then
local success, result = lookup(baseunit, opt_sp_us, 'no_combination', utable, depth)
if not success then return false, result end
if not (result.offset or result.builtin or result.engscale) then
Line 544 ⟶ 552:
return true, result
end
end
end
if not extra_units then
local success, extra = pcall(function () return require(extra_module).extra_units end)
if success and type(extra) == 'table' then
fmtextra_units = '%.0f'extra
end
end
if extra_units then
-- TODO Is there a clean way to abort this early in obvious cases, like
-- looking up "xyz" which does not exist in either table?
-- A unit in one data table might refer to a unit in the other table, so
-- switch from one to the other, relying on depth to terminate loops.
local other = (utable == all_units) and extra_units or all_units
local success, result = lookup(unitcode, opt_sp_us, what, other, depth)
if success then
return true, outinforesult
end
end
Line 1,668 ⟶ 1,693:
if out_current.builtin == 'hand' then
-- Convert to hands, then convert the fractional part to inches.
-- LATER: Code is not correct when output is spelled, and (butit that'signores veryany rare).requested
-- precision if the output uses scientific notation (very large, or very
-- small). Not worth more complexity as these cases should be very rare.
local dummy_unit_table = { scale = out_current.scale }
local success, outinfo = cvtround(parms, info, in_current, dummy_unit_table)
if not success then return false, outinfo end
local fmt
if not outinfo.use_default_precisionis_scientific then
local fraction = (outinfo.show):match('[' .. numdot .. '](.*)') or '' -- outinfo.show is in local language
if fraction = fmt = '%.1f' then
if not outinfo.use_default_precision then
return true, outinfo
end
fmt = '%.0f'
else
fmtlocal fraction = '%(outinfo.show):match('[' .. formatnumdot .. '](.*)') or '%d', ulen(fraction) -- 1) .outinfo.show 'f'is in local language
if fraction == '' then
if not outinfo.use_default_precision then
return true, outinfo
end
fmt = '%.0f'
else
fmt = '%.' .. format('%d', ulen(fraction) - 1) .. 'f'
end
end
local hands, inches = math.modf(outinfo.raw_absvalue)
Line 2,401 ⟶ 2,432:
end
local function make_result(info)
local outvaluefmt, signoutvalue, fmtsign
local results = {}
for i = 1, #combos do
local thisvalue, strforce
local out_current = combos[i]
out_current.inout = 'out'
local scale = multiple[i]
if i == 1 then -- least significant unit ('in' from 'ftin')
local fraction
local success, outinfo = cvtround(parms, info, in_unit_table, out_current)
if not success then return false, outinfo end
sign = outinfo.sign
localif fraction = (outinfo.show):match('[' .. numdot .. '](.*)') or '' -- outinfo.show is in localis_scientific languagethen
strforce = outinfo.show
fraction = ''
else
local fraction = (outinfo.show):match('[' .. numdot .. '](.*)') or '' -- outinfo.show is in local language
end
fmt = '%.' .. ulen(fraction) .. 'f' -- to reproduce precision
if fraction == '' then
Line 2,437 ⟶ 2,474:
end
end
local strval = (thisvalue == 0) and from_en('0') or with_separator(parms, format(fmt, thisvalue))
if strforce and outvalue == 0 then
sign = '' -- any sign is in strforce
strval = strforce -- show small values in scientific notation; will only use least significant unit
else
strval = (thisvalue == 0) and from_en('0') or with_separator(parms, format(fmt, thisvalue))
end
table.insert(results, strval .. sep1 .. id)
if outvalue == 0 then