Module:Road data/util
local util = {}
 
 local insert = table.insert
 local concat = table.concat
 local format = mw.ustring.format
 
 ---
 -- Add all entries in `arr` into `target`.
 -- An error is raised if `overwrite` is not true
 -- and any key in `arr` is already in `target`.
 function util.addAll(target, arr, overwrite)
 	if type(target) ~= &quot;table&quot; then
 		error(&quot;target is not a table&quot;)
 	end
 	for key,value in pairs(arr) do
 		if overwrite or target[key] == nil then
 			target[key] = value
 		else
 			error(&quot;Duplicate key: &quot; .. tostring(key))
 		end
 	end
 end
 
 local function comp(e1, e2)
 	local t1 = type(e1)
 	local t2 = type(e2)
 	if t1 ~= t2 then return t1 &lt; t2 end
 	if t1 == &quot;function&quot; then
 		error(&quot;Unexpected function type&quot;)
 	end
 	return e1 &lt; e2
 end
 
 local arrayToStringAux
 arrayToStringAux = function(arr, indent)
 	if type(arr) ~= &quot;table&quot; then
 		error(&quot;arr is not a table&quot;)
 	end
 	if type(indent) ~= &quot;number&quot; then
 		error(&quot;indent is not a number&quot;)
 	end
 	local result = {}
 	local keys = {}
 	for key in pairs(arr) do insert(keys, key) end
 	table.sort(keys, comp)
 	for _,key in ipairs(keys) do
 		local value = arr[key]
 		local keyPrint
 		if type(key) == &quot;string&quot; then
 			keyPrint = format(&quot;\&quot;%s\&quot;&quot;, key)
 		else
 			keyPrint = tostring(key)
 		end
 		local valuePrint
 		if type(value) == &quot;table&quot; then
 			valuePrint = format(&quot;{\n%s\n%s}&quot;,
 				arrayToStringAux(value, indent + 4),
 				string.rep(&quot; &quot;, indent))
 		elseif type(value) == &quot;string&quot; then
 			valuePrint = format(&quot;\&quot;%s\&quot;&quot;, value)
 		else
 			valuePrint = tostring(value)
 		end
 		insert(result, format(&quot;%s[%s] = %s&quot;,
 			string.rep(&quot; &quot;, indent),
 			keyPrint,
 			valuePrint))
 	end
 	return concat(result, &quot;, \n&quot;)
 end
 
 --- Return a string representation of `arr`.
 function util.arrayToString(arr, indent)
 	return arrayToStringAux(arr, indent or 0)
 end
 
 local function convert(distance, multiplier, desiredPrec)
 	if type(distance) ~= &quot;string&quot; then
 		error(&quot;distance is not a string&quot;)
 	end
 	if type(multiplier) ~= &quot;number&quot; then
 		error(&quot;multiplier is not a number&quot;)
 	end
 	-- Import math functions.
 	local math = require &quot;Module:Math&quot;
 	-- This function returns the precision of a given string representing a number.
 	local precision = math._precision
 	-- This function returns the order of magnitude of a given string representing a number.
 	local order = math._order
 	-- This function rounds a given number to the given number of digits.
 	local round = math._precision_format
 
 	local prec = desiredPrec or precision(distance)
 	if not desiredPrec then
 		local ord = order(distance)
 		-- Adjust precision based on multiplier, as done in {{convert}}.
 		prec = prec - order(multiplier / 0.2)
 	end
 
 	local converted = distance * multiplier
 	local magnitude = order(converted)
 	if prec &lt;= -magnitude then
 		-- Ensure the result has at least two significant digits.
 		prec = -magnitude + 1
 	end
 	return round(converted, prec)
 end
 
 --[[-
 Convert length specified in one unit (mi or km) to length in the other unit.
 @param #map&lt;#string, #string&gt; lengths
 	a map from unit to distance (as a string) in that unit;
 	may contain entry `prec` indicating desired conversion precision
 @param #string blank text to be used if length is unspecified
 @return #table a table containing the conversion result:
 	orig = source unit;
 	comp = target unit;
 	mi = length in miles;
 	ft = converted length in feet;
 	km = length in kilometers;
 	m = converted length in meters;
 	error = error message, if any
 ]]
 function util.convertLengths(lengths, blank)
 	-- Import math functions.
 	local math = require &quot;Module:Math&quot;
 	-- In Lua, storing functions locally results in more efficient execution.
 	-- This function rounds a given number to the given number of digits.
 	local round = math._precision_format
 	-- This function returns the precision of a given string representing a number.
 	local precision = math._precision
 
 	local kmPerMile = 1.609344
 	local ftPerMile = 5280
 	-- The length in kilometers as passed to the function.
 	local km = lengths.km
 	-- The length in miles as passed to the function.
 	local mi = lengths.mi
 	-- Precision for the converted length.
 	local prec = lengths.prec
 	local errMsg = {}
 	-- Sanitize inputs.
 	local km_ = tonumber(km)
 	if km and not km_ then
 		insert(errMsg, util.err(&quot;km is not a number&quot;))
 	end
 	local mi_ = tonumber(mi)
 	if mi and not mi_ then
 		insert(errMsg, util.err(&quot;mi is not a number&quot;))
 	end
 	local prec_ = tonumber(prec)
 	if prec and not prec_ then
 		insert(errMsg, util.err(&quot;prec is not a number&quot;))
 	end
 	prec = prec_
 
 	local ft
 	local m
 	local orig = &quot;mi&quot;
 	local comp = &quot;km&quot;
 	if mi and km then
 		insert(errMsg, util.err(&quot;Both mi and km are specified&quot;))
 	elseif mi then
 		-- Length in miles was passed.
 		if mi_ then
 			-- If `mi` is indeed a number, compute and round the length in kilometers.
 			km = convert(mi, kmPerMile, prec)
 			m = convert(mi, kmPerMile * 1000, prec)
 			-- format mi (insert separators as in 1,000)
 			mi = round(mi_, precision(mi))
 		else
 			-- `mi` is not a number.
 			km = blank
 			m = blank
 		end
 	elseif km then
 		-- Length in kilometers was passed.
 		-- Swap units.
 		orig, comp = comp, orig
 		if km_ then
 			-- If `km` is indeed a number, compute and round the length in miles.
 			mi = convert(km, 1 / kmPerMile, prec)
 			ft = convert(km, ftPerMile / kmPerMile, prec)
 			-- format km (insert separators as in 1,000)
 			km = round(km_, precision(km))
 		else
 			-- `km` is not a number.
 			mi = blank
 			ft = blank
 		end
 	else
 		mi = blank
 		ft = blank
 		km = blank
 		m = blank
 	end
 	local error = concat(errMsg)
 	if error == &quot;&quot; then error = nil end
 	return {mi = mi, ft = ft, km = km, m = m, orig = orig, comp = comp,
 		error = error}
 end
 
 --- Generates wikitext error messages.
 function util.err(msg)
 	if msg == nil then
 		error(&quot;Unspecified error message&quot;)
 	end
 	return format('&lt;strong class=&quot;error&quot;&gt;Error: %s&lt;/strong&gt;', msg)
 end