Content deleted Content added
something (mw.text.nowiki?) is only preserving the first tab character on each line and is misaligning the second tab, so use spaces second tab for less ugly result |
accept abbr=off for an alias so it uses unit name; add pernames (names for second unit in a per) for ukwiki |
||
(22 intermediate revisions by 4 users not shown) | |||
Line 1:
-- This module generates the
-- by reading and processing the wikitext of the
-- (see conversion_data for the page title).
--
-- Script method:
Line 74 ⟶ 62:
-- ...
-- }
local ulower = mw.ustring.lower
local usub = mw.ustring.sub
local text_code
local specials = {
-- Instead, the translation_table
-- and this script
-- definitions from Module:Convert/text, if given.
-- Ask for assistance at [[:en:Module talk:Convert]].
-- LATER: It would be better if this was defined in the conversion data.
utype = {
-- ["unit type in local language"] = "name_used_in_this_script"
["fuel efficiency"] = "type_fuel_efficiency",
["temperature"] = "type_temperature",
["volume"] = "type_volume",
},
ucode = {
exception = {
-- ["unit code in local language"] = "name_used_in_module_convert"
["ft"] = "integer_more_precision",
["in"] = "subunit_more_precision",
["lb"] = "integer_more_precision",
},
istemperature = {
-- Common temperature scales (not keVT or MK).
-- ["unit code in local
["F"] = true,
["K"] = true,
["R"] = true,
},
usesymbol = {
-- Use unit symbol not name if abbr not specified.
-- ["unit code in local
["C-change"] = 1,
["F-change"] = 1,
["K-change"] = 1,
},
alttype = {
-- Unit has an alternate type that is a valid conversion.
-- ["unit code in local language"] = "alternate type in local language"
["inlbf"] = "torque",
["inoz-f"] = "torque",
["inozf"] = "torque",
},
},
}
-- Module text for the local language (localization).
-- A default table of text for enwiki is provided here.
-- If needed for another wiki, wanted sections from the table can be
-- copied into translation_table in Module:Convert/text.
-- For example, copying and modifying only the titles section may give:
--
-- local translation_table = {
-- ... -- other items
-- mtext = {
-- titles = {
-- -- name_used_in_this_script = 'Title of page'
-- conversion_data = 'Modul:Convert/documentation/conversion data/dok',
-- },
-- },
-- }
local mtext = {
section_names = {
-- name_used_in_this_script = 'Section title used in conversion data'
overrides = 'Overrides',
conversions = 'Conversions',
outmultiples = 'Output multiples',
combinations = 'Combinations',
inmultiples = 'Input multiples',
defaults = 'Defaults',
links = 'Links',
perunits = 'Automatic per units',
varnames = 'Variable names',
pernames = 'Names for second unit in a per',
},
titles = {
-- name_used_in_this_script = 'Title of page'
conversion_data = 'Module:Convert/documentation/conversion data',
},
messages = {
-- name_used_in_this_script = 'Error message ($1 = first parameter, $2 = second)'
m_als_bad = 'Alias has invalid text in field "$1".',
m_als_dup = 'Alias "$1" already defined.',
m_als_link = 'Alias "$1" must include a wikilink ("[[...]]") in the symlink text.',
m_als_mul = 'Alias "$1" has multiplier "$2" which is not a number.',
m_als_same = 'Should omit "$1" for alias "$2" because it is the same as its target.',
m_als_type = 'Target of alias "$1" has wrong type.',
m_als_undef = 'Primary unit must be defined before alias "=$1"',
m_cmb_miss = 'Missing unit code for a combination.',
m_cmb_none = 'No units specified for combination "$1"',
m_cmb_one = 'Only one unit specified for combination "$1"',
m_cmb_type = 'Unit "$1" in combination "$2" has wrong type.',
m_cmb_undef = 'Unit "$1" in combination "$2" not defined.',
m_cmp_def = 'Composite "$1" must specify a default unit code.',
m_cmp_int = 'Composite "$1" has components where scale ratios are not integers.',
m_cmp_inval = 'Composite "$1" has a component with an invalid scale, "$2".',
m_cmp_many = 'Composite "$1" has too many fields.',
m_cmp_miss = 'Missing unit code for a composite.',
m_cmp_order = 'Composite "$1" has components in wrong order or with invalid scales.',
m_cmp_scale = 'Alternate unit "$1" in composite "$2" has wrong scale.',
m_cmp_two = 'Composite "$1" must specify exactly two unit codes.',
m_cmp_type = 'Unit "$1" in composite "$2" has wrong type.',
m_cmp_undef = 'Unit "$1" in composite "$2" not defined.',
m_def_cond = 'Invalid condition in default "$1" for unit "$2".',
m_def_fmt = 'Default output "$1" for unit "$2" should have 2 or 3 "!".',
m_def_rpt = 'Default output "$1" for unit "$2" is repeated.',
m_def_same = 'Default output for unit "$1" is the same unit.',
m_def_type = 'Default output "$1" for unit "$2" has wrong type.',
m_def_undef = 'Default output "$1" for unit "$2" is not defined.',
m_dfs_code = 'Defaults section: no unit code specified.',
m_dfs_dup = 'Defaults section: unit "$1" has already been specified.',
m_dfs_none = 'Defaults section: unit "$1" has no default specified.',
m_dfs_sym = 'Defaults section: unit "$1" must have a symbol.',
m_dfs_two = 'Defaults section: unit "$1" should have two fields only.',
m_dfs_undef = 'Defaults section: unit "$1" is not defined.',
m_dup_code = 'Unit code "$1" has already been defined.',
m_error = 'Error:',
m_ftl_read = 'Could not read wikitext from "[[$1]]".',
m_ftl_table = '[[$1]] should export table "$2".',
m_ftl_type = 'Fatal error: unknown data type for "$1"',
m_hdg_lev2 = 'Level 2 heading "$1" not found.',
m_hdg_lev3 = 'No level 3 heading before: $1',
m_line_num = ' (line $1).',
m_lnk_brack = 'Link "$1" has wrong number of brackets.',
m_lnk_dup = 'Link exception "$1" is already defined.',
m_lnk_miss = 'Missing unit code for a link.',
m_lnk_none = 'No link defined for unit "$1".',
m_lnk_sym = 'Unit code "$1" for a link must have a symbol.',
m_lnk_two = 'Row for unit "$1" link should have two fields only.',
m_lnk_type = 'Link exception "$1" has wrong type.',
m_lnk_undef = 'Unit code "$1" for a link is not defined.',
m_miss_code = 'Missing unit code.',
m_miss_sym = 'Missing symbol.',
m_miss_type = 'Missing unit type.',
m_mul_int = 'Multiple "$1" has components where scale ratios are not integers.',
m_mul_miss = 'Missing unit code for a multiple.',
m_mul_none = 'No units specified for multiple "$1"',
m_mul_one = 'Only one unit specified for multiple "$1"',
m_mul_order = 'Multiple "$1" has components in wrong order or with invalid scales.',
m_mul_scale = 'Multiple "$1" has a component with an invalid scale, "$2".',
m_mul_std = 'Unit "$1" in multiple "$2" must be a standard unit.',
m_mul_type = 'Unit "$1" in multiple "$2" has wrong type.',
m_mul_undef = 'Unit "$1" in multiple "$2" not defined.',
m_no_title = 'Need title of page with unit definitions.',
m_ovr_dup = 'Override "$1" is already defined.',
m_ovr_miss = 'Missing unit code for an override.',
m_per_dup = 'Per unit "$1" already defined.',
m_per_empty = 'Unit "$1" has an empty field in the "per".',
m_per_fuel = 'Unit "$1" has invalid unit types for fuel efficiency.',
m_per_inv = 'Invalid field for a "per".',
m_per_two = 'Unit "$1" does not have exactly 2 fields in the "per".',
m_per_undef = 'Unit "$1" has undefined unit code "$2" in the "per".',
m_percent_s = 'Field "$1" must not contain "%s".',
m_pnm_cnt = 'Names for second unit in a per section: each row must have two columns.',
m_pnm_dup = 'Unit "$1" already has a per name.',
m_pnm_miss = 'Missing field for a per name.',
m_pnm_undef = 'Unit "$1" in per names is not defined.',
m_pfx_bad = 'Unknown prefix: "$1".',
m_pfx_name = 'Unit with Prefix set must include Name.',
m_scl_bad = 'Scale expression is invalid: "$1".',
m_scl_miss = 'Missing scale.',
m_scl_oflow = 'Scale expression gives an invalid value: "$1".',
m_var_cnt = 'Variable names section: each row must have the configured number of columns.',
m_var_dup = 'Unit "$1" already has a variable name.',
m_var_miss = 'Missing field for a variable name.',
m_var_undef = 'Unit "$1" in variable names is not defined.',
m_warning = 'Warning:',
m_wrn_more = ' (and more not shown)',
m_wrn_nbsp = 'Line $1 contains a nonbreaking space.',
m_wrn_nodef = 'Units with the following unit codes have no default output.',
m_wrn_ucode = ' $1',
},
}
local function message(key, ...)
-- Return a message from the message table, which can be localized.
-- '$1', '$2', ... are replaced with the first, second, ... parameters,
-- each of which must be a string or a number.
-- The global variable is_test_run can be set by a testing program to
-- check the messages generated by this program.
local rep = {}
for i, v in ipairs({...}) do
rep['$' .. i] = v
end
key = key or '???'
local extra
if is_test_run and key ~= 'm_line_num' then
extra = key .. ': '
else
extra = ''
end
return extra .. string.gsub(mtext.messages[key] or key, '$%d+', rep)
end
local function quit(key, ...)
-- Use error() to pass an error message to the surrounding pcall().
error(message(key, ...), 0)
end
local function quit_no_message()
-- Throw an error.
-- This is used in some functions which can throw an error with a message,
-- but where the message is in fact never displayed because the calling
-- function uses pcall to catch errors, and any message is ignored.
-- Using this function documents that the message (which may be useful in
-- some other application) does not need translation as it never appears.
error('this message is not displayed', 0)
end
local function collection()
end
}
end
local warnings = collection()
local function add_warning(
end
Line 172 ⟶ 323:
local operators = {
}
local function tokenizer(text)
end
end
end
end
}
end
local function evaluate_tokens(tokens, inparens)
end
end
local function evaluate(expression)
end
---End code to evaluate expressions-------------------------------------
---Begin code adapted from Module:Convert-------------------------------
local plural_suffix = 's' -- may be changed from translation.plural_suffix below
local function shallow_copy(t)
end
local function split(text, delimiter)
-- Return a numbered table with fields from splitting text.
-- The delimiter is used in a regex without escaping (for example, '.' would fail).
-- Each field has any leading/trailing whitespace removed.
local t = {}
text = text .. delimiter -- to get last item
for item in text:gmatch('%s*(.-)%s*' .. delimiter) do
table.insert(t, item)
end
return t
end
local unit_mt = {
-- Warning: The boolean value 'false' is returned for any missing field
-- so __index is not called twice for the same field in a given unit.
__index = function (self, key)
local value
if key == 'name1' or key == 'sym_us' then
value = self.symbol
elseif key == 'name2' then
value = self.name1 .. plural_suffix
elseif key == 'name1_us' then
value = self.name1
if not rawget(self, 'name2_us') then
-- If name1_us is 'foot', do not make name2_us by appending plural_suffix.
self.name2_us = self.name2
end
elseif key == 'name2_us' then
local raw1_us = rawget(self, 'name1_us')
if raw1_us then
value = raw1_us .. plural_suffix
else
value = self.name2
end
elseif key == 'link' then
value = self.name1
else
value = false
end
rawset(self, key, value)
return value
end
}
local function prefixed_name(unit, name, index)
-- Return unit name with SI prefix inserted at correct position.
-- index = 1 (name1), 2 (name2), 3 (name1_us), 4 (name2_us).
-- The position is a byte (not character) index, so use Lua's sub().
local pos = rawget(unit, 'prefix_position')
if type(pos) == 'string' then
pos = tonumber(split(pos, ',')[index])
end
if pos then
return name:sub(1, pos - 1) .. unit.si_name .. name:sub(pos)
end
return unit.si_name .. name
end
local unit_prefixed_mt = {
-- Before use, fields si_name, si_prefix must be defined.
-- The unit must define _symbol, _name1 and
-- may define _sym_us, _name1_us, _name2_us
-- (_sym_us, _name2_us may be defined for a language using sp=us
-- to refer to a variant unrelated to U.S. units).
__index = function (self, key)
local value
if key == 'symbol' then
value = self.si_prefix .. self._symbol
elseif key == 'sym_us' then
if value then
value = self.si_prefix .. value
else
value = self.symbol
end
elseif key ==
value = prefixed_name(self, self._name1, 1)
elseif key == 'name2' then
value = rawget(self, '_name2')
value = prefixed_name(self, value, 2)
else
value = self.name1 .. plural_suffix
end
elseif key == 'name1_us' then
if value then
value = prefixed_name(self, value, 3)
else
value = self.name1
end
elseif key == 'name2_us' then
value =
if value then
value = prefixed_name(self, value, 4)
elseif rawget(self, '_name1_us') then
value = self.name1_us .. plural_suffix
else
value = self.name2
end
elseif key == 'link' then
value = self.name1
else
value = false
end
rawset(self, key, value)
return value
end
}
local function lookup(units, unitcode, sp, what)
return setmetatable(result, unit_prefixed_mt)
local SIprefixes = text_code.SIprefixes
for plen = SIprefixes[1] or 2, 1, -1 do
-- 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 = units[usub(unitcode, plen+1)]
if t and t.prefixes then
local result = shallow_copy(t)
if (sp == 'us' or
result.si_name = si.name_us
else
result.si_name = si.name
end
result.si_prefix = si.prefix or prefix
-- In this script, each scale is a
result.scale = tostring(tonumber(t.scale) * 10 ^ (si.exponent * t.prefixes)) end
if not (result.offset or result.builtin
-- Do not set result.scale as this code is called for units where that is not set.
return result
end
end
return nil
end
local function evaluate_condition(value, condition)
end
end
---End
local function strip(text)
end
local function empty(text)
end
Line 480 ⟶ 668:
local per_index = {} -- all "per" units (to detect attempts to define more than once)
local function get_unit(ucode, utype)
-- If utype == nil, the unit should already have been defined.
-- Otherwise, ucode may represent an automatically generated combination
-- where each component must have the given utype; a dummy unit is returned.
if empty(ucode) then
return nil
end
local unit = lookup(units_index, ucode)
if unit or not utype then
return unit
end
local combo = collection()
if ucode:find('+', 1, true) then
for item in (ucode .. '+'):gmatch('%s*(.-)%s*%+') do
if item ~= '' then
combo:add(item)
end
end
elseif ucode:find('%s') then
for item in ucode:gmatch('%S+') do
combo:add(item)
end
end
if combo.n > 1 then
local result = setmetatable({ utype = utype }, {
__index = function (self, key)
error('Bug: invalid use of automatically generated unit')
end })
for _, v in ipairs(combo) do
local component = lookup(units_index, v)
if not component or component.shouldbe or component.combination then
return nil
end
if utype ~= component.utype then
result.utype = component.utype -- set wrong type which caller will detect
break
end
end
return result
end
end
Line 491 ⟶ 714:
local function insert_unique_unit(data, unit, index)
quit('m_dup_code', ucode)
end
local function check_condition(condition)
end
local function check_default_expression(default, ucode)
if
quit('m_def_fmt', default, ucode)
quit('m_def_cond', default, ucode)
end
local function check_default(default, ucode, utype, unit_table)
-- Normally a unit must not define itself as its default. However,
-- some units are defined merely for use in per units, and they have
-- the same ucode, utype and default.
-- Example: unit cent which cannot be converted to anything other than
-- a cent, but which can work, for example, in cent/km and cent/mi.
-- Throw an error if a problem occurs.
local done = {}
for _, default in ipairs(check_default_expression(default, ucode)) do
if done[default] then
quit('m_def_rpt', default, ucode)
end
if default == ucode and ucode ~= utype then
quit('m_def_same', ucode)
end
local default_table = get_unit(default, utype)
if not default_table then
quit('m_def_undef', default, ucode)
if not (utype == unit_table.utype and utype == default_table.utype) then
quit('m_def_type', default, ucode)
end
done[default] = true
end
end
local function check_all_defaults(
if
if
end
break
end
end
end
add_warning('m_wrn_nodef')
end
end
local function check_all_pers(
local errors = collection()
errors:add(message(key, ...))
end
for _, unit in ipairs(units) do
local per = unit.per
if per then
local ucode = unit.unitcode
if #per ~= 2 then
errmsg('m_per_two', ucode)
else
local types = {}
for i, v in ipairs(per) do
if empty(v) then
errmsg('m_per_empty', ucode)
end
if not text_code.currency[v] then
local t = get_unit(v)
if t then
types[i] = t.utype
else
errmsg('m_per_undef', ucode, v)
end
end
end
if specials.utype[unit.utype] == 'type_fuel_efficiency' then
local expected = { type_volume = 1, type_length = 2 }
local top_type = expected[specials.utype[types[1]]]
if top_type and bot_type and top_type ~= bot_type then
unit.iscomplex = true
if top_type == 1 then
unit.invert = 1
else
unit.invert = -1
end
else
errmsg('m_per_fuel', ucode)
end
end
end
end
if errors.n >= cfg.maxerrors then
break
end
if errors.n > 0 then
end
end
local function update_units(units, composites, varnames, pernames)
if varnames[unit.unitcode] then
unit.varname = varnames[unit.unitcode]
end
if pernames[unit.unitcode] then
unit.pername = pernames[unit.unitcode]
end
end
end
local function make_override(cfg, data)
quit('m_ovr_miss')
quit('m_ovr_dup', ucode)
end
local function make_default(cfg, data)
quit('m_dfs_code')
quit('m_dfs_none', ucode)
quit('m_dfs_two', ucode)
if
quit('m_dfs_undef', ucode)
quit('m_dfs_sym', ucode)
quit('m_dfs_dup', ucode)
end
local function clean_link(link, name)
-- If the resulting link is nil, no link field is stored, and
-- if a link is required, it will be set from the unit's name.
local original = link
if empty(link) then
return (name and name:sub(1, 2) == '[[') and '' or nil
end
local prefixes = { ['+'] = 1, ['*'] = 2, ['@'] = 3 }
local customary = prefixes[link:sub(1, 1)]
link
end
link = strip(link)
if link:sub(1, 1) ==
quit('m_lnk_brack', original)
end
if link == '' then
link = nil
elseif
local l = ulower(usub(link, 1, 1)) .. usub(link, 2)
local n = ulower(usub(name, 1, 1)) .. usub(name, 2)
if l == n
link = nil -- link == name, ignoring case of first letter
end
end
return link, customary
end
local function make_link(cfg, data)
quit('m_lnk_miss')
quit('m_lnk_none', ucode)
quit('m_lnk_two', ucode)
if
quit('m_lnk_undef', ucode)
quit('m_lnk_type', ucode)
quit('m_lnk_sym', ucode)
quit('m_lnk_dup', ucode)
end
local function clean_scale(scale)
quit('m_scl_miss')
quit('m_scl_bad', scale)
-- Lua can give results like "#INF" while Scribunto gives "inf". Either is an error.
quit('m_scl_oflow', scale)
end
-- Omit redundant zeros from results like '1.2e-005'.
-- Do not bother looking for results like '1.2e+005' as none occur in practice. end
local function add_alias_optional_fields(unit, start, fields, target)
end
end
end
end
quit('m_als_link', unit.unitcode)
end
quit('m_als_mul', unit.unitcode, rhs)
end
elseif item == 'abbr' then
unit.usename = 1
good = true
end
else
if target and rhs == target[item] then
quit('m_als_same', item, unit.unitcode)
end
unit[item] = rhs
good = true
end
break
end
if not good then
quit('m_als_bad', field)
end
end
end
end
local function make_alias(fields, ucode, utype, symbol)
quit('m_als_dup', ucode)
quit('m_als_type', ucode)
end
local function make_per(fields, ucode, utype, symbol)
quit('m_per_dup', ucode)
end
local function make_unit(cfg, data)
}
quit('m_miss_type')
quit('m_miss_code')
quit('m_miss_sym')
end
end
quit('m_als_undef', symbol)
end
end
local name1, name2 = unit.name1, unit.name2
if name1 then
if name1 == symbol and not prefixes then
-- A unit which takes an SI prefix must not have a nil name because,
-- for example, the name for "kW" = "kilo" .. "watt" (name for "W").
-- The "not prefixes" test is needed for bnwiki where the
-- watt unit has the same name and symbol.
unit.name1 = nil
end
else
end
if name2 then
if name2 == name1 .. plural_suffix then
unit.name2 = nil
end
else
name2 = name1 .. plural_suffix
end
local name1_us, name2_us = unit.name1_us, unit.name2_us
if name1_us then
if name1_us ==
end
end
if name2_us then
if
if name2_us == unit.name1_us .. plural_suffix then
unit.name2_us = nil
end
elseif
unit.name2_us = nil
end
-- Other changes to
unit.scale = clean_scale(unit.scale) elseif extra == 'invert' then
else
unit.builtin = extra
end
end
if prefix == '~' then
-- Magic code for units like "acre" where the symbol is not really a
-- symbol, and output should use the singular or plural name instead.
unit.usename = 1
elseif prefix == '*' then
-- Magic code for units like "pitch" which have a symbol that is the same as
-- another unit with entries defined in the default or link exceptions tables.
unit.defkey = ucode -- key for default exceptions
unit.linkey = ucode -- key for link exceptions
end
local name_for_link
if
elseif prefixes == 'SI2' then
unit.prefixes = 2
elseif prefixes == 'SI3' then
unit.prefixes = 3
else
quit('m_pfx_bad', prefixes)
end
else
-- Only units which do not accept SI prefixes have name_for_link set.
-- That is because, for example, if set name_for_link
-- then the link is "kilogram" for kg, and "yottagram" for Yg, and so on
-- for all prefixes. That might be desirable for some units, but not all.
name_for_link = name1
end
unit.link, unit.customary = clean_link(unit.link, name_for_link)
-- The SI prefix is always at the start (position = 1) for symbol and sym_us.
-- However, each name (name1, name2, name1_us, name2_us) can have the SI prefix
-- at any position, and that position can be different for each name.
-- For enwiki, the only units with names where the prefix is not at the start
-- are "square metre" and "cubic metre" ("square meter" and "cubic meter" for sp=us).
-- Some other wikis want the flexibility that the prefix position can be different
-- so the position is stored as nil (if always 1), or N (an integer, if always N),
-- or a string of four comma-separated numbers such as "5,7,9,11" which means the
-- prefix position for (name1, name2, name1_us, name2_us) is (5, 7, 9, 11)
-- respectively.
local name1, name1_us = unit.name1, unit.name1_us -- after redundancy removed
if not name1 then
quit('m_pfx_name')
end
local positions = collection()
for i, k in ipairs({ 'name1', 'name2', 'name1_us', 'name2_us' }) do
local name = unit[k]
local pos
pos = name:find('%s', 1, true)
if pos then
unit[k] = name:sub(1, pos - 1) .. name:sub(pos + 2)
end
elseif i == 2 or i == 3 then
pos = positions[1]
elseif i == 4 then
pos = positions[unit.name1_us and 3 or 2]
end
positions:add(pos or 1)
end
local pos = positions[1]
for i = 2, positions.n do
if pos ~= positions[i] then
pos = '"' .. positions:join(',') .. '"'
break
end
end
if pos ~= 1 then
unit.prefix_position = pos
end
for _, name in ipairs({ 'symbol', 'sym_us', 'name1', 'name1_us', 'name2', 'name2_us' }) do
unit['_' .. name] = unit[name]
unit[name] = nil -- force call to __index metamethod so any SI prefix can be handled
end
end
for name, v in pairs(unit) do
-- Reject if a string field includes "%s" (should not occur after above).
if type(v) == 'string' and v:find('%s', 1, true) then
quit('m_percent_s', name)
end
end
insert_unique_unit(data, unit, units_index)
end
end
local function make_combination(cfg, data)
quit('m_cmb_miss')
end
if
quit('m_cmb_undef', v, unit.unitcode)
end
quit('m_cmb_type', v, unit.unitcode)
end
end
quit(#unit.combination == 0 and 'm_cmb_none' or 'm_cmb_one', unit.unitcode)
end
insert_unique_unit(data, unit, units_index)
end
end
local function make_perunit(cfg, data)
-- Return a function which, when called, stores a table that defines a
-- fixup for an automatic per unit. The table is stored in data (also a table).
local pertype_index = {} -- to detect attempts to define a fixup twice
return function (utype, fields)
-- Store a table to define a fixup.
-- Typos or other errors in the input are not detected!
-- Parameter utype is ignored (it is nil).
-- Throw an error if a problem occurs.
local lhs, rhs, link, multiplier
for i, v in ipairs(fields) do
if v == '' then
-- Ignore empty fields.
elseif i == 1 then
lhs = v -- like "length/time"
elseif i == 2 then
rhs = v -- like "speed"
elseif i == 3 then
link = v
elseif i == 4 then
if not tonumber(v) then
quit('m_per_inv')
end
multiplier = v
else
quit('m_per_inv')
end
end
if lhs and (rhs or link or multiplier) then
if link or multiplier then
local parts = collection()
if rhs then
parts:add('utype = "' .. rhs .. '"')
end
if link then
parts:add('link = "' .. link .. '"')
end
if multiplier then
parts:add('multiplier = ' .. multiplier)
end
rhs = '{ ' .. parts:join(', ') .. ' }'
else
rhs = '"' .. rhs .. '"'
end
if pertype_index[lhs] then
quit('m_per_dup', lhs)
end
pertype_index[lhs] = rhs
table.insert(data, { lhs = lhs, rhs = rhs })
else
quit('m_per_inv')
end
end
end
local function make_varname(cfg, data)
-- Return a function which, when called, stores a table that defines a
-- variable name for a unit. The table is stored in data (also a table).
return function (utype, fields)
-- Set or update an entry in the data table to record that a unit has a variable name.
-- This is for slwiki where a unit name depends on the value.
-- The target units must be defined first.
-- Parameter utype is ignored (it is nil).
-- Throw an error if a problem occurs.
local count = #fields
if count ~= cfg.varcolumns then
quit('m_var_cnt')
end
local ucode
local names = {}
for i = 1, count do
local v = fields[i]
if empty(v) then
quit('m_var_miss')
end
if i == 1 then -- unitcode
ucode = v
if not get_unit(v) then
quit('m_var_undef', v)
end
else
table.insert(names, v)
end
end
if data[ucode] then
quit('m_var_dup', ucode)
end
data[ucode] = table.concat(names, '!')
end
end
local function make_pername(cfg, data)
-- Return a function which, when called, stores a table that defines a
-- per name for a unit. The table is stored in data (also a table).
return function (utype, fields)
-- Set or update an entry in the data table to record that a unit has a
-- non-standard per name if used as the second unit in a per unit (x per y).
-- The target units must be defined first.
-- Parameter utype is ignored (it is nil).
-- Throw an error if a problem occurs.
local count = #fields
if count ~= 2 then
quit('m_pnm_cnt')
end
local ucode, pername
for i = 1, count do
local v = fields[i]
if empty(v) then
quit('m_pnm_miss')
end
if i == 1 then -- unitcode
ucode = v
if not get_unit(v) then
quit('m_pnm_undef', v)
end
else
pername = v
end
end
if data[ucode] then
quit('m_pnm_dup', ucode)
end
data[ucode] = pername
end
end
local function reversed(t)
end
local function make_inputmultiple(cfg, data)
quit('m_cmp_miss')
end
local target = get_unit(v, (i == 4) and utype or nil) -- the default may be an auto combination
if
quit('m_cmp_undef', v, unitcode)
end
quit('m_cmp_type', v, unitcode)
end
if not target.scale then
quit('m_mul_std', v, unitcode)
end
table.insert(ucodes, v)
table.insert(scales, target.scale)
elseif i == 4 then
default_code = v
else
if scales[#scales] ~= target.scale then
quit('m_cmp_scale', v, unitcode)
end
alternate_code = v
end
end
elseif i == 6 then
if v ~= '' then
fixed_name = v
end
else
quit('m_cmp_many', unitcode)
end
quit('m_cmp_two', unitcode)
if not default_code then
quit('m_cmp_def', unitcode)
end
-- Component units must be specified from most-significant to least-significant,
-- and each ratio of a pair of scales must be very close to an integer.
-- Currently, there will be exactly two scales and one ratio.
local ratios, count = {}, #scales
for i = 1, count do
local scale = tonumber(scales[i])
if scale == nil or scale <= 0 then
quit('m_cmp_inval', unitcode, scales[i])
end
end
for i = 1, count - 1 do
local ratio = scales[i] / scales[i + 1]
local rounded = math.floor(ratio + 0.5)
quit('m_cmp_order', unitcode)
end
if math.abs(ratio - rounded)/ratio > 1e-6 then
quit('m_cmp_int', unitcode)
end
ratios[i] = rounded
end
local text = { tostring(ratios[1]) }
local function add_text(key, value)
table.insert(text, string.format('%s = %q', key, value))
if fixed_name then
add_text('name', fixed_name)
end
local subdiv = string.format('["%s"] = { %s }', ucodes[2], table.concat(text, ', '))
local main_code = ucodes[1]
local item = data[main_code]
if item then
table.insert(item.subdivs, subdiv)
else
data[main_code] = { subdivs = { subdiv } }
end
end
end
local function make_outputmultiple(cfg, data)
quit('m_mul_miss')
end
if
quit('m_mul_undef', v, unit.unitcode)
end
quit('m_mul_type', v, unit.unitcode)
end
if not target.scale then
quit('m_mul_std', v, unit.unitcode)
end
table.insert(ucodes, v)
table.insert(scales, target.scale)
end
end
if #ucodes < 2 then
quit(#ucodes == 0 and 'm_mul_none' or 'm_mul_one', unit.unitcode)
end
-- Component units must be specified from most-significant to least-significant
-- (so scale values will be in descending order),
-- and each ratio of a pair of scales must be very close to an integer.
-- The componenets and ratios are stored in reverse order (least significant first).
-- This script stores a unit scale as a string (might be an expression like "5/9"),
-- but scales in a multiple are handled as numbers (should never be expressions).
local ratios, count = {}, #scales
for i = 1, count do
local scale = tonumber(scales[i])
if scale == nil or scale <= 0 then
quit('m_mul_scale', unit.unitcode, scales[i])
end
scales[i] = scale
end
for i = 1, count - 1
local ratio = scales[i]
local rounded = math.floor(ratio + 0.5)
if rounded < 2 then
quit('m_mul_order', unit.unitcode)
end
if math.abs(ratio
quit('m_mul_int', unit.unitcode)
end
ratios[i] = rounded
end
unit.combination = reversed(ucodes)
unit.multiple = reversed(ratios)
insert_unique_unit(data, unit, units_index)
end
end
-- To make updating
-- and a postamble so the result can be used to replace the whole page.
local data_preamble = [=[
-- Conversion data used by [[Module:Convert]] which uses mw.loadData() for
-- read-only access to this module so that it is loaded only once per page.
-- See [[:en:Template:Convert/Transwiki guide]] if copying to another wiki.
--
-- These data tables follow:
Line 1,387 ⟶ 1,761:
--
-- These tables are generated by a script which reads the wikitext of a page that
-- documents the required properties of each unit; see [[:en:Module:Convert/doc]].
]=]
local data_postamble = [=[
return {
per_unit_fixups = per_unit_fixups,
}]=]
Line 1,414 ⟶ 1,789:
---------------------------------------------------------------------------
local default_exceptions = {
local out_default_suffix = [[
Line 1,422 ⟶ 1,797:
local out_default_item = [[
local out_link_prefix = [[
Line 1,430 ⟶ 1,805:
---------------------------------------------------------------------------
local link_exceptions = {
local out_link_suffix = [[
Line 1,438 ⟶ 1,813:
local out_link_item = [[
local out_perunit_prefix = [[
---------------------------------------------------------------------------
-- Do not change the data in this table because it is created by running --
-- a script that reads the wikitext from a wiki page (see note above). --
---------------------------------------------------------------------------
local per_unit_fixups = {
-- Automatically created per units of form "x/y" may have their unit type
-- changed, for example, "length/time" is changed to "speed".
-- Other adjustments can also be specified.]]
local out_perunit_suffix = [[
}
]]
local out_perunit_item = [[
["{lhs}"] = {rhs},]]
local combination_specification = { -- pure combination like 'm ft', or a multiple like 'ftin'
}
local alias_specification = {
'symlink',
'customary',
'multiplier',
}
local per_specification = {
'multiplier',
}
local shouldbe_specification = {
}
local unit_specification = {
'_name2_us',
'_sym_us',
'prefix_position',
'name1',
'name1_us',
'name2_us',
'pername',
'usesymbol',
'alttype',
'builtin',
'scale',
'offset',
'iscomplex',
'istemperature',
'exception',
'prefixes',
'default',
'subdivs',
'defkey',
'linkey',
'link',
'customary',
'sp_us',
}
local no_quotes = {
}
local function add_unit_lines(results, unit, spec)
local function add_line(line)
-- Had planned to replace sequences of spaces with 4-column tabs here
-- (because the CodeEditor now assumes the use of such tabs).
-- However, 4-column tabs are only visible when editing a module
-- with browser scripting and the CodeEditor enabled, and that is rare.
-- A module is usually viewed (with 8-column tabs), and some indents
-- would be messed up unless 8-column tabs are used. Therefore,
-- have decided to simply replace 8 spaces at start of line with a single
-- tab which reduces the size of the module, and is correct for viewing.
if line:sub(1, 8) == string.rep(' ', 8) then
line = '\t' .. line:sub(9)
end
results:add(line)
end
local first_item = ' ["' .. unit.unitcode .. '"]
local last_item = ' },'
add_line(first_item)
for _, k in ipairs(spec) do
local v = unit[k]
if v then
local want_quotes = (type(v) == 'string' and not no_quotes[k])
if type(v) == 'boolean' then
v = tostring(v)
elseif type(v) == 'number' or k == 'scale' then
-- Replace results like '1e-006' with '1e-6'.
v = string.gsub(tostring(v), '(e[+-])0+([1-9].*)', '%1%2', 1)
elseif type(v) ~= 'string' then
quit('m_ftl_type', unit.unitcode)
end
local fmt = string.format('%8s%%-9s= %%%s,', '', want_quotes and 'q' or 's')
add_line(fmt:format(k, v))
end
end
add_line(last_item)
end
local function numbered_table_as_string(data, unit)
end
local function extract_heading(line)
end
local function fields(line)
if t[2] then
local cleaned = t[2]:match('^%s*colspan%s*=.-|%s*(.*)$')
if cleaned then
t[2] = cleaned
end
end
return t
end
local function prepare_section(cfg, maker, lines, section,
end
if need_utype
quit('m_hdg_lev3', line)
end
end
break
end
end
break
end
end
if skip and
quit('m_hdg_lev2', section)
end
local function get_page_lines(page_title)
quit('m_no_title')
if content:sub(-1) ~= '\n' then
content = content .. '\n'
end
local lines = collection()
for line in string.gmatch(content, '[\t ]*(.-)[\t\r ]*\n') do
lines:add(line)
return lines
end
end
quit('m_ftl_read', page_title)
end
local function prepare_data(
{ 'overrides' , make_override , overrides , 0 },
{ 'conversions' , make_unit
{ 'inmultiples' , make_inputmultiple , composites, 0 }, -- after all units defined so default will be defined
{ 'defaults' , make_default , defaults , 0 },
{ 'links'
{ 'varnames' , make_varname , varnames , 1 },
{ 'pernames' , make_pername , pernames , 1 },
}
local lines = get_page_lines(cfg.data_title)
for _, section in ipairs(sections) do
local heading = mtext.section_names[section[1]]
local maker = section[2](cfg, section[3])
local code = section[4]
local need_section, need_utype
if code == 0 and not is_sandbox then
need_section = true
end
if code == 0 then
need_utype = true
end
prepare_section(cfg, maker, lines, heading, need_section, need_utype)
end
check_all_defaults(cfg, units)
check_all_pers(cfg, units)
update_units(units, composites, varnames, pernames)
return units, defaults, links, perunits
end
local function _makeunits(
for _, name in ipairs({ 'SIprefixes',
if type(text_code[name]) ~= 'table' then
quit('m_ftl_table', cfg.text_title, name)
end
end
local translation = text_code.translation_table
if translation then
plural_suffix = translation.plural_suffix
end
local ts = translation.specials
if ts then
end
if ts.ucode then
specials.ucode = ts.ucode
end
end
local tm = translation.mtext
if tm then
if tm.section_names then
mtext.section_names = tm.section_names
end
if tm.titles then
mtext.titles = tm.titles
end
if tm.messages then
mtext.messages = tm.messages
end
end
end
local is_sandbox
local conversion_data_title = mtext.titles.conversion_data
if cfg.data_title and cfg.data_title ~= conversion_data_title then
if is_test_run then
is_sandbox = true
data_preamble = nil
data_postamble = nil
out_unit_prefix = 'local all_units = {'
out_unit_suffix = '}'
out_default_prefix = '\nlocal default_exceptions = {'
out_default_suffix = '}'
out_default_item = '\t["{symbol}"] = "{default}",'
out_link_prefix = '\nlocal link_exceptions = {'
out_link_suffix = '}'
out_link_item = '\t["{symbol}"] = "{link}",'
out_perunit_prefix = '\nlocal per_unit_fixups = {'
out_perunit_suffix = '}'
out_perunit_item = '\t["{lhs}"] = {rhs},'
end
else
cfg.data_title = conversion_data_title
end
local units, defaults, links, perunits = prepare_data(cfg, is_sandbox)
if data_preamble then
results:add(data_preamble)
end
results:add(out_unit_prefix)
for _, unit in ipairs(units) do
local spec
if unit.target then
spec = alias_specification
elseif unit.per then
spec = per_specification
unit.per = numbered_table_as_string(unit.per, unit)
elseif unit.shouldbe then
spec = shouldbe_specification
elseif unit.combination then
spec = combination_specification
unit.combination = numbered_table_as_string(unit.combination, unit)
if unit.multiple then
unit.multiple = numbered_table_as_string(unit.multiple, unit)
end
else
spec = unit_specification
end
add_unit_lines(results, unit, spec)
end
results:add(out_unit_suffix)
for _, t in ipairs({
{ defaults, out_default_prefix, out_default_item, out_default_suffix },
{ links , out_link_prefix , out_link_item , out_link_suffix },
{ perunits, out_perunit_prefix, out_perunit_item, out_perunit_suffix } }) do
local data, prefix, item, suffix = t[1], t[2], t[3], t[4]
if #data > 0 or not is_sandbox then
results:add(prefix)
for _, unit in ipairs(data) do
results:add((item:gsub('{([%w_]+)}', unit)))
end
results:add(suffix)
end
end
if data_postamble then
results:add(data_postamble)
end
end
local function makeunits(frame)
local config = {
data_title = args[1],
text_title = args[2] or 'Module:Convert/text',
varcolumns = tonumber(args.varcolumns) or 5, -- #columns in "Variable names" section; slwiki uses 5
maxerrors = 20,
}
local results = collection()
local ok, msg = pcall(_makeunits, config, results)
if not ok then
results:add(message('m_error'))
results:add('')
results:add(msg)
end
local warn = ''
if warnings.n > 0 then
warn = message('m_warning') .. '\n\n' .. warnings:join() .. '\n\n'
end
-- Pre tags returned by a module are html tags, not like wikitext <pre>...</pre>.
-- The following renders the text as is, and preserves tab characters.
return '<pre>\n' .. mw.text.nowiki(warn .. results:join()) .. '\n</pre>\n'
end
|