Module:Convert: Difference between revisions
Jump to navigation
Jump to search
>WOSlinker (km2|sqmi) |
>Johnuniq (sorry, but I'm dropping in a new version to see whether others want to work with this) |
||
Line 1: | Line 1: | ||
-- | --[[ | ||
Start of convert that I hope will be driven from tables. | |||
Lots to do (first want to fix the default rounding). | |||
I (enwiki Johnuniq) started this two days ago; it's not ready for release | |||
but I'm dropping it here per BRD to see if others want to work with this. | |||
Testing makes me think these results will occur with current code: | |||
- | {{convert|3.21|kg|lb|2}} 3.21 kg (7.08 lb) | ||
-- | {{convert|212|F|C|2}} 212 F (100.00 C) | ||
{{convert|5|to|10|kg|lb|2}} 5 to 10 kg (11.02 to 22.05 lb) | |||
{{convert|5|-|10|kg|lb|2}} 5–10 kg (11.02–22.05 lb) | |||
{{convert|15|ft|m|2}} 15 ft (4.57 m) | |||
{{convert|3.21|kg|m|2}} Error: Cannot convert mass to length | |||
]]-- | |||
--[[-----BEGIN DATA TABLE----- | |||
Plan to write a program to generate the conversion tables below. | |||
The input would be a text file in human-friendly format, and | |||
the output would be the tables following like length/mass/units. | |||
Values from http://en.wikipedia.org/wiki/Conversion_of_units | |||
Check with http://en.wikipedia.org/wiki/Template:Convert/list_of_units | |||
]]-- | |||
function | local function scaled(self, value, in_unit, out_unit) | ||
-- Return scaled value for a simple convert. | |||
if out_unit == nil then | |||
if | out_unit = self[in_unit][2] | ||
end | end | ||
return | return value * (self[in_unit] / self[out_unit]) | ||
end | end | ||
local length = { | |||
convert = scaled, | |||
['NAME'] = 'length', | |||
['m'] = 1, | |||
['km'] = 1000, | |||
['mi'] = 1609.344, | |||
['ft'] = 0.3048, | |||
} | |||
local mass = { | |||
convert = scaled, | |||
['NAME'] = 'mass', | |||
['kg'] = 1, | |||
['lb'] = 0.45359237, | |||
} | |||
-- | local temperature = { | ||
convert = function (self, value, in_unit, out_unit) | |||
-- FIXME I have thought of a better way, but not implemented | |||
-- (each value will be a table of two functions: convert to/from C). | |||
local icode = self[in_unit] | |||
local ocode = self[out_unit] | |||
if icode ~= ocode then | |||
if icode == 1 then | |||
if ocode == 2 then | |||
return value - 273.15 | |||
elseif ocode == 3 then | |||
return value*(9/5) - 459.67 | |||
end | |||
elseif icode == 2 then | |||
if ocode == 1 then | |||
return value + 273.15 | |||
elseif ocode == 3 then | |||
return value*(9/5) + 32 | |||
end | |||
elseif icode == 3 then | |||
if ocode == 1 then | |||
return (value + 459.67)*(5/9) | |||
elseif ocode == 2 then | |||
return (value - 32)*(5/9) | |||
end | |||
end | |||
end | |||
return value | |||
end, | |||
['NAME'] = 'temperature', | |||
['K'] = 1, | |||
['C'] = 2, | |||
['F'] = 3, | |||
} | |||
if | local units = { | ||
error ( | -- Each value is {converter_table, default_out_unit}. | ||
return | lookup = function (self, unit) | ||
-- If unit is known, return its converter_table, default_out_unit. | |||
local t = self[unit] | |||
if t == nil then | |||
local msg = 'Unit %s not known' | |||
error(msg:format(unit)) | |||
end | |||
return t[1], t[2] | |||
end, | |||
['kg'] = {mass, 'lb'}, | |||
['lb'] = {mass, 'kg'}, | |||
['m'] = {length, 'ft'}, | |||
['km'] = {length, 'mi'}, | |||
['mi'] = {length, 'km'}, | |||
['ft'] = {length, 'm'}, | |||
['K'] = {temperature, 'C'}, | |||
['C'] = {temperature, 'F'}, | |||
['F'] = {temperature, 'C'}, | |||
} | |||
-------END DATA TABLE----- | |||
local function require_number(value, missing, invalid) | |||
-- If value is missing or not a number, throw an error. | |||
-- Return value as a number if valid. | |||
if value == nil then error(missing) end | |||
local number = tonumber(value) | |||
if number == nil then error(invalid:format(value)) end | |||
return number | |||
end | |||
local function require_integer(value, missing, invalid) | |||
-- If value is missing or not an integer, throw an error. | |||
-- Return value as a number if valid. | |||
local number = require_number(value, missing, invalid) | |||
if number ~= math.floor(number) then | |||
error(invalid:format(value)) | |||
end | |||
return number | |||
end | |||
local function get_parms(pframe) | |||
-- Return table with all arguments passed by template converted to | |||
-- named arguments. The numeric args are used to add named args: | |||
-- in_text, in_text2 (strings given for value, value2) | |||
-- value, in_unit, out_unit, value2, range, round_to | |||
-- (except for range, which is nil or a table, the named args that are | |||
-- added here could be provided by the user of the template). | |||
local range_types = { -- text to separate input, output ranges | |||
['and'] = {' and ', ' and '}, | |||
['by'] = {' by ', ' by '}, | |||
['to'] = {' to ', ' to '}, | |||
['-'] = {'–', '–'}, | |||
['to(-)'] = {' to ', '–'}, | |||
['x'] = {' by ', ' × '}, | |||
['+/-'] = {' ± ', ' ± '}, | |||
} | |||
local args = {} -- arguments passed to template | |||
for k,v in pframe:argumentPairs() do | |||
args[k] = v | |||
end | end | ||
args.in_text = args[1] | |||
args.value = require_number(args.in_text, 'Need value', 'Value "%s" must be a number') | |||
local in_unit = args[2] | |||
local i = 3 | |||
local range = range_types[in_unit] | |||
if range ~= nil then | |||
args.in_text2 = args[3] | |||
args.value2 = require_number(args.in_text2, 'Need second value', 'Second value "%s" must be a number') | |||
in_unit = args[4] | |||
i = 5 | |||
end | |||
local out_unit = args[i] | |||
local round_to = args[i+1] | |||
if in_unit == nil then error('Need input unit') end | |||
args.in_unit = in_unit | |||
args.out_unit = out_unit | |||
args.range = range | |||
args.round_to = round_to | |||
return args | |||
end | |||
if | local function converter(parms) | ||
-- If we can convert from given in to out unit, return the table | |||
-- that handles the two given unit types. | |||
-- If no out_unit, change it to the default for the in_unit. | |||
local t1, outdefault = units:lookup(parms.in_unit) | |||
if parms.out_unit == nil then -- need to catch empty string also? | |||
parms.out_unit = outdefault | |||
end | |||
local t2 = units:lookup(parms.out_unit) | |||
if t1 ~= t2 then | |||
local msg = 'Cannot convert %s to %s' | |||
error(msg:format(t1.NAME, t2.NAME)) | |||
end | |||
return t1 | |||
end | |||
local function cvtround(invalue, parms, cvt) | |||
-- Convert given invalue using parms, cvt (return '' if invalue == nil). | |||
-- Return rounded, formatted string for result, using rounding | |||
-- specified in parms. | |||
-- This code combines convert/round because some rounding requires | |||
-- knowledge of what we are converting. | |||
-- TODO Fix! | |||
local text = '' | |||
if invalue == nil then return text end | |||
local outvalue = cvt:convert(invalue, parms.in_unit, parms.out_unit) | |||
local round_to = parms.round_to | |||
local sigfig = parms.sigfig | |||
local disp = parms.disp | |||
if round_to then | |||
-- Ignore sigfig, disp. | |||
round_to = require_integer(round_to, 'Need value', 'round_to "%s" must be an integer') | |||
if round_to >= 0 then | |||
local fmt = '%.' .. string.format('%.0f', round_to) .. 'f' | |||
text = string.format(fmt, outvalue) | |||
else | else | ||
factor = 10^(-round_to) | |||
zeroes = string.sub(tonumber(factor), 2) | |||
text = string.format('%.0f', outvalue/factor) .. zeroes | |||
end | end | ||
elseif sigfig then | |||
-- Ignore disp. | |||
sigfig = require_integer(sigfig, 'Need value', 'sigfig "%s" must be an integer') | |||
if sigfig <= 0 then | |||
msg = 'sigfig "%s" must be positive' | |||
error(msg:format(parms.sigfig)) | |||
end | |||
local fmt = '%.' .. string.format('%.0f', sigfig) .. 'g' | |||
text = string.format(fmt, outvalue) | |||
elseif disp then | |||
text = 'TODO: disp' | |||
else | else | ||
-- | -- Default rounding. | ||
text = 'TODO: default rounding' | |||
end | |||
return text | |||
end | |||
local function process(parms) | |||
local cvt = converter(parms) | |||
local outext = cvtround(parms.value, parms, cvt) | |||
local outext2 = cvtround(parms.value2, parms, cvt) | |||
local intext = parms.in_text | |||
local intext2 = parms.in_text2 | |||
local range = parms.range | |||
local wikitext | |||
if range == nil then | |||
wikitext = '%s %s (%s %s)' | |||
wikitext = wikitext:format(intext, parms.in_unit, outext, parms.out_unit) | |||
else | |||
wikitext = '%s%s%s %s (%s%s%s %s)' | |||
wikitext = wikitext:format(intext, range[1], intext2, parms.in_unit, outext, range[2], outext2, parms.out_unit) | |||
end | end | ||
return wikitext | |||
end | |||
return | |||
-- Used by template {{convert2}}. | |||
-- We will have to keep old {{convert}} for a long time, and run | |||
-- {{convert2}} in parallel with {{convert}} while testing/developing. | |||
local p = {} | |||
function p.convert(frame) | |||
local pframe = frame:getParent() | |||
local parms = get_parms(pframe) | |||
return process(parms) | |||
end | end | ||
return p | return p |
Revision as of 16:34, 4 September 2012
Documentation for this module may be created at Module:Convert/doc
--[[ Start of convert that I hope will be driven from tables. Lots to do (first want to fix the default rounding). I (enwiki Johnuniq) started this two days ago; it's not ready for release but I'm dropping it here per BRD to see if others want to work with this. Testing makes me think these results will occur with current code: {{convert|3.21|kg|lb|2}} 3.21 kg (7.08 lb) {{convert|212|F|C|2}} 212 F (100.00 C) {{convert|5|to|10|kg|lb|2}} 5 to 10 kg (11.02 to 22.05 lb) {{convert|5|-|10|kg|lb|2}} 5–10 kg (11.02–22.05 lb) {{convert|15|ft|m|2}} 15 ft (4.57 m) {{convert|3.21|kg|m|2}} Error: Cannot convert mass to length ]]-- --[[-----BEGIN DATA TABLE----- Plan to write a program to generate the conversion tables below. The input would be a text file in human-friendly format, and the output would be the tables following like length/mass/units. Values from http://en.wikipedia.org/wiki/Conversion_of_units Check with http://en.wikipedia.org/wiki/Template:Convert/list_of_units ]]-- local function scaled(self, value, in_unit, out_unit) -- Return scaled value for a simple convert. if out_unit == nil then out_unit = self[in_unit][2] end return value * (self[in_unit] / self[out_unit]) end local length = { convert = scaled, ['NAME'] = 'length', ['m'] = 1, ['km'] = 1000, ['mi'] = 1609.344, ['ft'] = 0.3048, } local mass = { convert = scaled, ['NAME'] = 'mass', ['kg'] = 1, ['lb'] = 0.45359237, } local temperature = { convert = function (self, value, in_unit, out_unit) -- FIXME I have thought of a better way, but not implemented -- (each value will be a table of two functions: convert to/from C). local icode = self[in_unit] local ocode = self[out_unit] if icode ~= ocode then if icode == 1 then if ocode == 2 then return value - 273.15 elseif ocode == 3 then return value*(9/5) - 459.67 end elseif icode == 2 then if ocode == 1 then return value + 273.15 elseif ocode == 3 then return value*(9/5) + 32 end elseif icode == 3 then if ocode == 1 then return (value + 459.67)*(5/9) elseif ocode == 2 then return (value - 32)*(5/9) end end end return value end, ['NAME'] = 'temperature', ['K'] = 1, ['C'] = 2, ['F'] = 3, } local units = { -- Each value is {converter_table, default_out_unit}. lookup = function (self, unit) -- If unit is known, return its converter_table, default_out_unit. local t = self[unit] if t == nil then local msg = 'Unit %s not known' error(msg:format(unit)) end return t[1], t[2] end, ['kg'] = {mass, 'lb'}, ['lb'] = {mass, 'kg'}, ['m'] = {length, 'ft'}, ['km'] = {length, 'mi'}, ['mi'] = {length, 'km'}, ['ft'] = {length, 'm'}, ['K'] = {temperature, 'C'}, ['C'] = {temperature, 'F'}, ['F'] = {temperature, 'C'}, } -------END DATA TABLE----- local function require_number(value, missing, invalid) -- If value is missing or not a number, throw an error. -- Return value as a number if valid. if value == nil then error(missing) end local number = tonumber(value) if number == nil then error(invalid:format(value)) end return number end local function require_integer(value, missing, invalid) -- If value is missing or not an integer, throw an error. -- Return value as a number if valid. local number = require_number(value, missing, invalid) if number ~= math.floor(number) then error(invalid:format(value)) end return number end local function get_parms(pframe) -- Return table with all arguments passed by template converted to -- named arguments. The numeric args are used to add named args: -- in_text, in_text2 (strings given for value, value2) -- value, in_unit, out_unit, value2, range, round_to -- (except for range, which is nil or a table, the named args that are -- added here could be provided by the user of the template). local range_types = { -- text to separate input, output ranges ['and'] = {' and ', ' and '}, ['by'] = {' by ', ' by '}, ['to'] = {' to ', ' to '}, ['-'] = {'–', '–'}, ['to(-)'] = {' to ', '–'}, ['x'] = {' by ', ' × '}, ['+/-'] = {' ± ', ' ± '}, } local args = {} -- arguments passed to template for k,v in pframe:argumentPairs() do args[k] = v end args.in_text = args[1] args.value = require_number(args.in_text, 'Need value', 'Value "%s" must be a number') local in_unit = args[2] local i = 3 local range = range_types[in_unit] if range ~= nil then args.in_text2 = args[3] args.value2 = require_number(args.in_text2, 'Need second value', 'Second value "%s" must be a number') in_unit = args[4] i = 5 end local out_unit = args[i] local round_to = args[i+1] if in_unit == nil then error('Need input unit') end args.in_unit = in_unit args.out_unit = out_unit args.range = range args.round_to = round_to return args end local function converter(parms) -- If we can convert from given in to out unit, return the table -- that handles the two given unit types. -- If no out_unit, change it to the default for the in_unit. local t1, outdefault = units:lookup(parms.in_unit) if parms.out_unit == nil then -- need to catch empty string also? parms.out_unit = outdefault end local t2 = units:lookup(parms.out_unit) if t1 ~= t2 then local msg = 'Cannot convert %s to %s' error(msg:format(t1.NAME, t2.NAME)) end return t1 end local function cvtround(invalue, parms, cvt) -- Convert given invalue using parms, cvt (return '' if invalue == nil). -- Return rounded, formatted string for result, using rounding -- specified in parms. -- This code combines convert/round because some rounding requires -- knowledge of what we are converting. -- TODO Fix! local text = '' if invalue == nil then return text end local outvalue = cvt:convert(invalue, parms.in_unit, parms.out_unit) local round_to = parms.round_to local sigfig = parms.sigfig local disp = parms.disp if round_to then -- Ignore sigfig, disp. round_to = require_integer(round_to, 'Need value', 'round_to "%s" must be an integer') if round_to >= 0 then local fmt = '%.' .. string.format('%.0f', round_to) .. 'f' text = string.format(fmt, outvalue) else factor = 10^(-round_to) zeroes = string.sub(tonumber(factor), 2) text = string.format('%.0f', outvalue/factor) .. zeroes end elseif sigfig then -- Ignore disp. sigfig = require_integer(sigfig, 'Need value', 'sigfig "%s" must be an integer') if sigfig <= 0 then msg = 'sigfig "%s" must be positive' error(msg:format(parms.sigfig)) end local fmt = '%.' .. string.format('%.0f', sigfig) .. 'g' text = string.format(fmt, outvalue) elseif disp then text = 'TODO: disp' else -- Default rounding. text = 'TODO: default rounding' end return text end local function process(parms) local cvt = converter(parms) local outext = cvtround(parms.value, parms, cvt) local outext2 = cvtround(parms.value2, parms, cvt) local intext = parms.in_text local intext2 = parms.in_text2 local range = parms.range local wikitext if range == nil then wikitext = '%s %s (%s %s)' wikitext = wikitext:format(intext, parms.in_unit, outext, parms.out_unit) else wikitext = '%s%s%s %s (%s%s%s %s)' wikitext = wikitext:format(intext, range[1], intext2, parms.in_unit, outext, range[2], outext2, parms.out_unit) end return wikitext end -- Used by template {{convert2}}. -- We will have to keep old {{convert}} for a long time, and run -- {{convert2}} in parallel with {{convert}} while testing/developing. local p = {} function p.convert(frame) local pframe = frame:getParent() local parms = get_parms(pframe) return process(parms) end return p