Jump to content

Module:Convert/show

From Wikipedia, the free encyclopedia

-- Prepare tables of wikitext to display simple documentation about
-- specified units. Data is obtained by calling Module:Convert.
-- Also provides a function to show convert usage examples.

local Collection  -- a table to hold items
Collection = {
	add = function (self, item)
		if item ~= nil then
			self.n = self.n + 1
			self[self.n] = item
		end
	end,
	join = function (self, sep)
		return table.concat(self, sep)
	end,
	remove = function (self, pos)
		if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then
			self.n = self.n - 1
			return table.remove(self, pos)
		end
	end,
	sort = function (self, comp)
		table.sort(self, comp)
	end,
	new = function ()
		return setmetatable({n = 0}, Collection)
	end
}
Collection.__index = Collection

local function strip(text)
	-- Return text with no leading/trailing whitespace.
	return text:match("^%s*(.-)%s*$")
end

local function fakeFrame(selfArgs, parentArgs)
	-- Simulate enough of a MediaWiki module frame for convert.
	-- This is a cheap way to call convert with specified arguments.
	return {
		args = selfArgs,
		parent = parentArgs and fakeFrame(parentArgs, nil),
		getParent = function (self) return self.parent end,
	}
end

local cvtFunction
local function callConvert(args)
	if not cvtFunction then
		cvtFunction = require('Module:Convert').convert
	end
	return cvtFunction(fakeFrame({}, args))
end

local function makeTable(frame, results, units)
	results:add('{| class="wikitable"')
	results:add('! Unit code !! Unit symbol !! Unit name !! US name, if different')
	for i, ucode in ipairs(units) do
		local row = Collection.new()
		row:add(ucode)
		local args = { '1', ucode, abbr = 'on', disp = 'unit' }
		row:add(callConvert(args))
		args.abbr = 'off'
		local name1 = callConvert(args)
		row:add(name1)
		args.sp = 'us'
		local name1_us = callConvert(args)
		if name1_us == name1 then
			row:add('')
		else
			row:add(name1_us)
		end
		results:add('|-')
		results:add(strip('| ' .. row:join(' || ')))
	end
	results:add('|}')
	results:add('')
end

-- Commonly used units for main documentation.
-- Can only be input units (not combinations or multiples).
local commonUnits = {
	["Area"] = {
		heading = "Area",
		examples = { "1.5|sqmi|km2", "1.5|sqmi|km2|abbr=off", "1.5|sqmi|km2|abbr=on" },
		"acre",
		"ha",
		"m2",
		"cm2",
		"km2",
		"sqin",
		"sqft",
		"sqyd",
		"sqmi",
	},
	["Fuel efficiency"] = {
		heading = "Fuel efficiency",
		examples = { "12|mpgus|km/L", "12|mpgus|km/L|abbr=off", "12|mpgus|km/L|abbr=off|sp=us", "12|mpgus|km/L|abbr=on" },
		"km/L",
		"mpgimp",
		"mpgus",
		"L/km",
		"L/100 km",
	},
	["Length"] = {
		heading = "Length",
		examples = { "123|cm|in", "123|cm|in|abbr=off|sp=us", "123|cm|in|abbr=on" },
		"uin",
		"in",
		"ft",
		"yd",
		"mi",
		"nmi",
		"m",
		"cm",
		"mm",
		"km",
		"angstrom",
	},
	["Mass"] = {
		heading = "Mass",
		examples = { "72.3|kg|lb", "72.3|kg|lb|abbr=off", "72.3|kg|lb|abbr=on" },
		"g",
		"kg",
		"oz",
		"lb",
		"st",
		"LT",
		"MT",
		"ST",
	},
	["Pressure"] = {
		heading = "Pressure",
		examples = { "28|psi|Pa", "28|psi|Pa|abbr=off", "28|psi|Pa|abbr=on" },
		"atm",
		"mbar",
		"psi",
		"Pa",
	},
	["Speed"] = {
		heading = "Speed",
		examples = { "60|mph|km/h", "60|mph|km/h|abbr=off", "60|mph|km/h|abbr=on" },
		"km/h",
		"km/s",
		"kn",
		"mph",
	},
	["Temperature"] = {
		heading = "Temperature",
		examples = { "100|C|F", "100|C|F|abbr=off", "100|C-change|F-change", "100|C-change|F-change|abbr=out" },
		"C",
		"F",
		"K",
		"C-change",
		"F-change",
		"K-change",
	},
	["Torque"] = {
		heading = "Torque",
		examples = { "12.5|Nm|lb.in", "12.5|Nm|lb.in|abbr=off", "12.5|Nm|lb.in|abbr=on|lk=on" },
		"lb.in",
		"lb.ft",
		"Nm",
	},
	["Volume"] = {
		heading = "Volume",
		examples = { "125|cuin|l", "125|cuin|l|abbr=off", "125|cuin|l|abbr=on" },
		"cuin",
		"cuft",
		"cuyd",
		"cumi",
		"impgal",
		"impoz",
		"usgal",
		"usoz",
		"L",
		"l",
		"m3",
		"cc",
		"mm3",
	},
}

-- Order in which sections are wanted when doing all common units.
local commonSections = {
	"Area",
	"Fuel efficiency",
	"Length",
	"Mass",
	"Pressure",
	"Speed",
	"Temperature",
	"Torque",
	"Volume",
}

local function _showExamples(frame, results, examples, wantTable)
	local fmt
	if wantTable then
		results:add('{|')
		fmt = '|<code>%s</code>|| → ||%s'
	else
		fmt = '*<code>%s</code> → %s'
	end
	for i, item in ipairs(examples) do
		if wantTable and i > 1 then
			results:add('|-')
		end
		item = item:gsub('!', '|')
		item = '{{convert' .. (item:sub(1, 1) == '|' and '' or '|') .. item .. '}}'
		results:add(fmt:format(mw.text.nowiki(item), frame:preprocess(item)))
	end
	if wantTable then
		results:add('|}')
	end
end

local function _showLinks(frame, results, args)
	local sandbox = args[1] == 'sandbox' and '/sandbox' or ''
	local dataModule = 'Module:Convert/data' .. sandbox
	local textModule = 'Module:Convert/text' .. sandbox
	local dataCode = require(dataModule)
	local textCode = require(textModule)
	local uniqueLinks = {}
	local links = Collection.new()
	local function addLink(link)
		if link and link ~= '' then
			-- Some items (alias symlink, chainlk symbol) are already linked.
			-- Therefore, add link syntax if not present, before testing for uniqueness.
			-- There will be some duplicate targets such as [[Chain (unit)|chain]] + [[Chain (unit)|ch]].
			if link:sub(1, 2) ~= '[[' then
				link = '[[' .. link .. ']]'
			end
			if not uniqueLinks[link] then
				uniqueLinks[link] = true
				links:add(link)
			end
		end
	end
	for _, v in ipairs(textCode.customary_units) do
		addLink(v.link)
	end
	for _, v in pairs(dataCode.all_units) do
		-- This does not add anything for automatic per units (assuming they do not define a link).
		-- That is ok because per unit x/y has link LINK(x)/LINK(y).
		if v.symbol and v.symbol:sub(1, 2) == '[[' then
			addLink(v.symbol)
		end
		if v.name1 and v.name1:sub(1, 2) == '[[' then
			addLink(v.name1)
		end
		addLink(v.symlink)
		addLink(v.link or v.name1 or (not v.per and not v.target) and v.symbol)
	end
	for _, v in pairs(dataCode.link_exceptions) do
		addLink(v)
	end
	for _, v in pairs(dataCode.per_unit_fixups) do
		if type(v) == 'table' then
			addLink(v.link)
		end
	end
	local function comp(a, b)
		local la = a:lower(a)
		local lb = b:lower(b)
		if la == lb then
			return a < b
		end
		return la < lb
	end
	links:sort(comp)
	for _, v in ipairs(links) do
		results:add('*' .. v)
	end
end

local function _showUnits(frame, results, args)
	local doFull
	if args[1] == nil then
		doFull = true
		args = commonSections
	end
	local group = Collection.new()
	for _, item in ipairs(args) do
		local units = commonUnits[item] or commonUnits[item:sub(1, 1):upper() .. item:sub(2)]
		if units then
			if group.n > 0 then
				makeTable(frame, results, group)
				group = Collection.new()
			end
			if doFull then
				if units.heading then
					results:add('===' .. units.heading .. '===')
				end
				if units.examples then
					results:add('Examples:')
					_showExamples(frame, results, units.examples)
				end
			end
			makeTable(frame, results, units)
		else
			group:add(item)
		end
	end
	if group.n > 0 then
		makeTable(frame, results, group)
	end
end

local function showExamples(frame, wantTable)
	local results = Collection.new()
	local ok, msg = pcall(_showExamples, frame, results, frame.args, wantTable)
	if ok then
		return results:join('\n')
	end
	return '<strong class="error">Error</strong>\n' .. msg
end

local function showLinks(frame)
	local results = Collection.new()
	local ok, msg = pcall(_showLinks, frame, results, frame.args)
	if ok then
		return results:join('\n')
	end
	return '<strong class="error">Error</strong>\n' .. msg
end

local function showUnits(frame)
	local results = Collection.new()
	local ok, msg = pcall(_showUnits, frame, results, frame.args)
	if ok then
		return results:join('\n')
	end
	return '<strong class="error">Error</strong>\n' .. msg
end

return {
	links = showLinks,
	unit = showUnits,
	units = showUnits,
	['list'] = function (frame) return showExamples(frame, false) end,
	['table'] = function (frame) return showExamples(frame, true) end,
}