Module:Convert: Difference between revisions

(syncing discussed and well-tested changes in sandbox to live (speed_of_sound calculation logic simplification, variable name change))
(update from sandbox per Template talk:Convert#Module version 14)
Line 136:
 
local spell_module -- name of module that can spell numbers
local speller -- function from that module to handle spelling (set if spelling is wantedneeded)
local wikidata_module, wikidata_data_module -- names of Wikidata modules
local wikidata_code, wikidata_data -- exported tables from those modules (set if needed)
 
local function set_config(args)
Line 147 ⟶ 149:
text_module = "Module:Convert/text" .. sandbox
extra_module = "Module:Convert/extra" .. sandbox
wikidata_module = "Module:Convert/wikidata" .. sandbox
wikidata_data_module = "Module:Convert/wikidata/data" .. sandbox
spell_module = "Module:ConvertNumeric"
data_code = mw.loadData(data_module)
Line 270 ⟶ 274:
end
 
local function message(parms, mcode)
-- Return wikitext for an error message, including category if specified
-- for the message type.
-- mcode = numbered table specifying the message:
-- mcode[1] = 'cvt_xxx' (string used as a key to get message info)
-- mcode[2] = 'parm1' (string to replace first %s'$1' if any in message)
-- mcode[3] = 'parm2' (string to replace second %s'$2' if any in message)
-- mcode[4] = 'parm3' (string to replace third %s'$3' if any in message)
local msg = text_code.all_messages[mcode[1]]
if type(mcode) == 'table' then
local nowiki = mw.text.nowiki
if mcode[1] == 'cvt_no_output' then
-- Some errors should cause convert to output an empty string,
-- for example, for an optional field in an infobox.
return ''
end
msg = text_code.all_messages[mcode[1]]
end
parms.have_problem = true
local function subparm(fmt, ...)
local rep = {}
for i, v in ipairs({...}) do
rep['$' .. i] = v
end
return (fmt:gsub('$%d+', rep))
end
if msg then
local parts = {}
Line 305 ⟶ 324:
append = '...'
end
s = mw.text.nowiki(s) .. (append or '')
else
s = '?'
end
parts['$' .. i] = s
end
local function ispreview()
local title = format(msg[1] or 'Missing message', parts[1], parts[2], parts[3])
-- Return true if a prominent message should be shown.
if parms.test == 'preview' or parms.test == 'nopreview' then
-- For testing, can preview a real message or simulate a preview
-- when running automated tests.
return parms.test == 'preview'
end
local success, revid = pcall(function ()
return (parms.frame):preprocess('{{REVISIONID}}') end)
return success and (revid == '')
end
local title = string.gsub(msg[1] or 'Missing message', '$%d+', parts)
local text = msg[2] or 'Missing message'
local cat = wanted_category(text_code.all_categories[msg[3]]) or ''
local anchor = msg[4] or ''
local fmtkey = ispreview() and 'cvt_format_preview' or (msg.format or 'cvt_format')
local fmt = text_code.all_messages[msg.format or 'cvt_format'] or 'convert: bug'
local fmt = text_code.all_messages[fmtkey] or 'convert: bug'
title = title:gsub('"', '"')
return formatsubparm(fmt, anchortitle:gsub('"', title'"'), text, cat, anchor)
end
return 'Convert internal error: unknown message'
Line 328 ⟶ 358:
if level <= (tonumber(config.warnings) or 1) then
if parms.warnings == nil then
parms.warnings = message(parms, { key, text1, text2 })
end
end
Line 373 ⟶ 403:
-- Table gives speed of sound in miles per hour at various altitudes:
-- altitude = -17,499 to 302,499 feet
-- mach_table[scalea + 4] = s where
-- scalea = (altitude / 5000) rounded to nearest integer (-3 to 60) incl
-- s = speed of sound (mph) at that altitude
-- LATER: Should calculate result from an interpolation between the next
-- lower and higher altitudes in table, rather than rounding to nearest.
-- From: http://www.aerospaceweb.org/question/atmosphere/q0112.shtml
local mach_table = { -- scalea =
799.5, 787.0, 774.2, 761.207051, -- -3 to 0
748.0, 734.6, 721.0, 707.0, 692.8, 678.3, 663.5, 660.1, 660.1, 660.1, -- 1 to 10
Line 389 ⟶ 419:
}
altitude = altitude or 0
local a = (altitude < 0) and -altitude or altitude
-- divide altitude and add a fudge factor (complication of 0) before we floor it
a = floor(a / 5000 + 0.5)
-- ensure it is within range [-3..60]
if altitude < 0 then
scale = math.max(-3, math.min(60,
a = -a
floor(altitude / 5000 + ((altitude < 0) and 0.4998 or 0.5))))
end
return mach_table[scale + 4] * 0.44704 -- mph converted to m/s
if a < -3 then
a = -3
elseif a > 60 then
a = 60
end
return mach_table[a + 4] * 0.44704 -- mph converted to m/s
end
-- END: Code required only for built-in units.
Line 626 ⟶ 662:
-- Wikignomes may also put two spaces or "&nbsp;" in combinations, so
-- replace underscore, "&nbsp;", and multiple spaces with a single space.
utable = utable or parms.unittable or all_units
fails = fails or {}
depth = depth and depth + 1 or 1
Line 988 ⟶ 1,024:
-- When using gaps, they are inserted before and after the decimal mark.
-- Separators are inserted only before the decimal mark.
-- A trailing dot (as in '123.') is removed because their use appears to
-- be accidental, and such a number should be shown as '123' or '123.0'.
-- It is useful for convert to suppress the dot so, for example, '4000.'
-- is a simple way of indicating that all the digits are significant.
if text:sub(-1) == '.' then
text = text:sub(1, -2)
end
if #text < 4 or parms.opt_nocomma or numsep == '' then
return from_en(text)
Line 1,730 ⟶ 1,773:
end
end
elseif en_name == 'stylein' or en_name == 'styleout' or en_name == 'qid' or en_name == 'input' then
en_value = loc_value ~= '' and loc_value or nil -- accept non-empty user text with no validation
if en_name == 'input' then
-- May have something like {{convert|input=}} (empty input) if source is an infobox
-- with optional fields. In that case, want to output nothing rather than an error.
parms.input_text = loc_value -- keep input because parms.input is nil if loc_value == ''
end
else
en_value = text_code.en_option_value[en_name][loc_value]
Line 1,947 ⟶ 1,995:
local function simple_get_values(parms)
-- If input is like "{{convert|valid_value|valid_unit|...}}",
-- return true, 3i, in_unit, in_unit_table
-- 3i = index in parms of whateverwhat follows valid_unit, if anything).
-- The valid_value is not negative and does not use a fraction, and
-- no options requiring further processing of the input are used.
Line 1,973 ⟶ 2,021:
end
 
local function get_parmswikidata_call(argsoperation, ...)
-- Return true, s where s is the result of a Wikidata operation,
-- If successful, return true, parms, unit where
-- or return false, t where t is an error message table.
local function worker(...)
wikidata_code = wikidata_code or require(wikidata_module)
wikidata_data = wikidata_data or mw.loadData(wikidata_data_module)
return wikidata_code[operation](wikidata_data, ...)
end
local success, status, result = pcall(worker, ...)
if success then
return status, result
end
return false, { 'cvt_wd_fail' }
end
 
local function get_parms(parms, args)
-- If successful, update parms and return true, unit where
-- parms is a table of all arguments passed to the template
-- converted to named arguments, and
-- unit is the input unit table;
-- or return false, t where t is an error message table.
-- For special processing (not a convert), can also return
-- true, wikitext where wikitext is the final result.
-- The returned input unit table may be for a fake unit using the specified
-- unit code as the symbol and name, and with bad_mcode = message code table.
Line 1,985 ⟶ 2,050:
-- whitespace entered in the template, and whitespace is used by some
-- parameters (example: the numbered parameters associated with "disp=x").
local parms = {} -- arguments passed to template, after translation
local kv_pairs = {} -- table of input key:value pairs where key is a name; needed because cannot iterate parms and add new fields to it
for k, v in pairs(args) do
Line 1,993 ⟶ 2,057:
kv_pairs[k] = v
end
end
if parms.test == 'wikidata' then
local ulookup = function (ucode)
-- Use empty table for parms so it does not accummulate results when used repeatedly.
return lookup({}, ucode, 'no_combination')
end
return wikidata_call('_listunits', ulookup)
end
local success, msg = translate_parms(parms, kv_pairs)
if not success then return false, msg end
if parms.input then
success, msg = wikidata_call('_adjustparameters', parms, 1)
if not success then return false, msg end
end
local success, i, in_unit, in_unit_table = simple_get_values(parms)
if not success then
Line 2,005 ⟶ 2,080:
success, in_unit_table = lookup(parms, in_unit, 'no_combination')
if not success then
if in_unit == nilin_unit or then''
in_unit = ''
end
if parms.opt_ignore_error then -- display given unit code with no error (for use with {{val}})
in_unit_table = '' -- suppress error message and prevent processing of output unit
end
in_unit_table = setmetatable({ symbol = in_unit, name2 = in_unit,
defaultsymbol = "m"in_unit, defkeyname2 = "m"in_unit, linkeyutype = "m"in_unit,
utypescale = "length"1, scaledefault = 1'', bad_mcodedefkey = in_unit_table }'', unit_mt)linkey = '',
bad_mcode = in_unit_table }, unit_mt)
end
in_unit_table.valinfo = valinfo
Line 2,048 ⟶ 2,122:
end
end
local nextword = strip(parms[i])
i = i + 1
local precision, is_bad_precision
Line 2,063 ⟶ 2,137:
end
end
if not set_precision(nextword) then
parms.out_unit = nextword
if set_precision(strip(parms[i])) then
i = i + 1
Line 2,070 ⟶ 2,144:
end
if parms.opt_adj_mid then
nextword = parms[i]
i = i + 1
if nextword then -- mid-text words
if nextword:sub(1, 1) == '-' then
parms.mid = nextword
else
parms.mid = ' ' .. nextword
end
end
Line 2,116 ⟶ 2,190:
parms.precision = precision
end
return true, parms, in_unit_table
end
 
Line 3,377 ⟶ 3,451:
 
local function process(parms, in_unit_table, out_unit_table)
-- Return true, s, outunit where s = final wikitext result,
-- or return false, t where t is an error message table.
linked_pages = {}
local success, bad_output
local bad_input_mcode = in_unit_table.bad_mcode -- falsenil if input unit is a valid convert unit
local invalue1 = in_unit_table.valinfo[1].value
local out_unit = parms.out_unit
Line 3,415 ⟶ 3,489:
parts[part] = process_input(parms, in_unit_table)
elseif bad_output then
parts[part] = (bad_output == '') and '' or message(parms, bad_output)
else
local outputs = {}
Line 3,477 ⟶ 3,551:
wikitext = parts[1]
else
wikitext = parts[1] .. message(parms, bad_input_mcode)
end
elseif parms.table_joins then
Line 3,492 ⟶ 3,566:
local function main_convert(frame)
-- Do convert, and if needed, do it again with higher default precision.
local parms = { frame = frame } -- will hold template arguments, after translation
set_config(frame.args)
local success, result = get_parms(parms, out_unit_tableframe:getParent().args)
local success, parms, in_unit_table = get_parms(frame:getParent().args)
if success then
if type(result) ~= 'table' then
return tostring(result)
end
local in_unit_table = result
local out_unit_table
for _ = 1, 2 do -- use counter so cannot get stuck repeating convert
success, result, out_unit_table = process(parms, in_unit_table, out_unit_table)
Line 3,504 ⟶ 3,583:
end
end
else
result = parms
end
-- If input=x gives a problem, the result should be just the user input
-- (if x is a property like P123 it has been replaced with '').
-- An unknown input unit would display the input and an error message
-- with success == true at this point.
-- Also, can have success == false with a message to output an empty string.
if success then
if parms.have_problem and parms.input_text then
return parms.input_text
end
return result
end
return parms.input_text and parms.input_text or message(parms, result)
end
 
Line 3,551 ⟶ 3,636:
opt_sortable_debug = options.sort == 'debug',
}
local utable
if options.si then
-- Make a dummy table of units (just one unit) for lookup to use.
-- This makes lookup recognize any SI prefix in the unitcode.
local symbol = options.si[1] or '?'
utableparms.unittable = { [symbol] = {
_name1 = symbol,
_name2 = symbol,
Line 3,567 ⟶ 3,651:
}}
end
local success, unit_table = lookup(parms, unitcode, 'no_combination', utable)
if not success then
unit_table = setmetatable({
symbol = unitcode, name2 = unitcode, utype = unitcode,
scale = 1, default = "m"'', defkey = "m"'', linkey = "m"'' }, unit_mt)
utype = "length", scale = 1 }, unit_mt)
end
local value = tonumber(options.value) or 1
Anonymous user