Module:Adjacent stations

local p = {}

local lang = 'en-GB' -- local default language

--	Below these comments: Internationalization table --	How to translate this module (for languages without variants): --	• Characters inside single and double quotation marks are called strings. --	 The strings in this i18n table are used as output. --	• Strings within square brackets are keys. --	• Strings are concatenated (joined) with two dots. --	• Set the string after «local lang =» to your language's code. --	 Change the first key after "i18n" (usually "en-GB") to the same thing. --	• For each string which is not inside a function, translate it directly. --	• Strings with keys named "format" are Lua regular expressions. --	 «» is a match; «.+» means all characters; «%s+» means all spaces. --	• For each string which is concatenated to the variable «var», --	 translate the phrase assuming that «var» will be a noun. --	• Remove any unnecessary translations. --	• Lua coders: Replace the English-specific code in the rest of the module.

local i18n = { ['en-GB'] = { --		['word_space'] = ' ', ['preceding'] = function(var) return 'Preceding ' .. var end, ['following'] = function(var) return 'Following ' .. var end, ['stop_noun'] = 'station', ['nonstop_past'] = function(var) return var .. ' did not stop here' end, ['nonstop_present'] = function(var) return var .. ' does not stop here' end, ['comma'] = function(var) return ', ' .. var end, ['or'] = function(var) return ' or ' .. var end, ['via-first'] = false, -- If the «via» text comes before termini, change to «true» ['via'] = function(var) return ' via ' .. var end, ['comma-format'] = ',%s+', ['or-format'] = '%s+or%s+', ['via-format'] = '%s+via%s+(.+)$', -- first match is station name ['towards'] = function(var) return 'towards ' .. var end, ['through'] = function(var) return 'through to ' .. var end, ['reverse'] = 'Reverses direction', ['oneway'] = 'One-way operation', ['terminus'] = 'Terminus', ['error_duplicate'] = function(var) return 'Same row number used multiple times for ' .. var end, ['error_format'] = 'Station format table missing in data module', ['error_line'] = 'Lines table missing in data module', ['error_unknown'] = 'Unknown line' },	['en-US'] = { --		['word_space'] = ' ', ['preceding'] = function(var) return 'Preceding ' .. var end, ['following'] = function(var) return 'Following ' .. var end, ['stop_noun'] = 'station', ['nonstop_past'] = function(var) return var .. ' did not stop here' end, ['nonstop_present'] = function(var) return var .. ' does not stop here' end, ['comma'] = function(var) return ', ' .. var end, ['or'] = function(var) return ' or ' .. var end, ['via-first'] = false, -- If the «via» text comes before termini, change to «true» ['via'] = function(var) return ' via ' .. var end, ['comma-format'] = ',%s+', ['or-format'] = '%s+or%s+', ['via-format'] = '%s+via%s+(.+)$', -- first match is station name ['towards'] = function(var) return 'toward ' .. var end, ['through'] = function(var) return 'through to ' .. var end, ['reverse'] = 'Reverses direction', ['oneway'] = 'One-way operation', ['terminus'] = 'Terminus', ['error_duplicate'] = function(var) return 'Same row number used multiple times for ' .. var end, ['error_format'] = 'Station format table missing in data module', ['error_line'] = 'Lines table missing in data module', ['error_unknown'] = 'Unknown line' } }

local require = require

local function getData(system, verify) if verify then local title = mw.title.new('Module:Adjacent stations/' .. system -- .. '/sandbox'			) if not title.exists then return nil end end return require('Module:Adjacent stations/' .. system -- .. '/sandbox'		) end

local function getLine(data, line) local lower = mw.ustring.lower return data['lines'][line] or data['lines'][data['aliases'][lower(line)]] end

function p._main(_args) -- Arguments are processed here instead of the main function local concat = table.concat local error = error local gsub = mw.ustring.gsub local insert = table.insert local ipairs = ipairs local match = string.match local pairs = pairs local tonumber = tonumber local type = type local yesno = require("Module:Yesno") local boolean = { ['oneway-left'] = true, ['oneway-right'] = true, ['reverse'] = true, ['reverse-left'] = true, ['reverse-right'] = true }	local args = {} -- Processed arguments local index = {} -- A list of addresses corresponding to number suffixes in the arguments for k, v in pairs(_args) do -- Maps each raw argument to processed arguments by string matching _args[k] = v:match('^%s*(.-)%s*$') if _args[k] and _args[k] ~= '' then local a = match(k, '^(.*%D)%d+$') or k -- The parameter; address 1 can be omitted local b = tonumber(match(k, '^.*%D(%d+)$')) or 1 -- The address for a given argument; address 1 can be omitted if boolean[a] then v = yesno(v) end if not args[b] then args[b] = {[a] = v}				insert(index, b)			elseif args[b][a] then return error(i18n[lang]['error_duplicate'](a .. b)) else args[b][a] = v			end end end table.sort(index) local function small(s, italic) return italic and ' ' .. s .. ' '			or ' ' .. s .. ' '	end

local style = { -- Style for each cell type ['header cell'] = 'style="width: 30%; vertical-align: middle;"|', ['header midcell'] = 'colspan="3" style="vertical-align: middle;"|', ['body cell'] = 'style="text-align: center; vertical-align: middle;"|', ['body banner'] = 'style="text-align: center; width: 8px; min-width: 8px; background: #',	}

local function _subst(s1, s2) if s2 then return match(s2, '%[%[') and gsub(s2, '%%1', s1) or concat({, s1, }) else return s1 or '' end end local Format local function subst(var1, var2) return type(var1) == 'string' and _subst(var1, (Format[var1] or Format[1])) or type(var1) == 'table' and #var1 > 0 and _subst(var1[var2], (Format[var1[var2]] or Format[1])) or '' end local function station(var) if Format then if type(var) == 'string' then return subst(var) elseif type(var) == 'table' and #var > 0 then local t = {subst(var, 1)}

for i = 2, #var - 1 do					t[i] = i18n[lang]['comma'](subst(var, i)) end if #var > 1 then t[#var] = i18n[lang]['or'](subst(var, #var)) end if var['via'] then if i18n[lang]['via-first'] then table.insert(t, 1, i18n[lang]['via'](subst(var, 'via'))) else table.insert(t, i18n[lang]['via'](subst(var, 'via'))) end end

return concat(t) else return '' end else return var or '' end end --	local greatercontrast --	local function service(var) --		if (not var) or (type(var) == 'string') then --			return var --		else --			greatercontrast = greatercontrast or require('Module:Color contrast')._greatercontrast --			return concat({'', var[1], ' '}) --		end --	end local data = {} -- A table of data modules for each address local wikitable = {'{| class="wikitable adjacent-stations" style="max-width: 50em; margin:0.5em auto; font-size:95%; clear:both;"'} for i, v in ipairs(index) do		-- If an address has a system argument, indexes the data module data[v] = args[v]['system'] and getData(args[v]['system']) -- If an address has no system, the row uses data from the previous address. or data[index[i - 1]] if args[v]['system'] then -- Header row local stop_noun = data[v]['header stop noun'] or i18n[lang]['stop_noun'] insert(wikitable, concat({'\n|-', '\n!', style['header cell'], i18n[lang]['preceding'](stop_noun), '\n!', style['header midcell'], (data[v]['system title'] or (.. args[v]['system'] ..)), '\n!', style['header cell'], i18n[lang]['following'](stop_noun) }))			insert(wikitable, '') insert(wikitable, '') insert(wikitable, '') end if args[v]['header'] then -- Subheader insert(wikitable, '\n|-\n!colspan="5" style="vertical-align: middle;"|'.. args[v]['header']) insert(wikitable, '') insert(wikitable, '') insert(wikitable, '') end if args[v]['line'] or args[v]['left'] or args[v]['right'] or args[v]['nonstop'] then if not args[v]['line'] and i > 1 then args[v]['line'] = args[index[i - 1]]['line'] end local line = data[v]['lines'] and (data[v]['lines'][args[v]['line']] or error(i18n[lang]['error_unknown'])) or error(i18n[lang]['error_line']) if type(line) == 'string' then line = data[v]['lines'][line] or error(i18n[lang]['error_unknown']) end if args[v]['nonstop'] then insert(wikitable, 					concat({'\n|-\n|colspan="5" ', style['body cell'], ((args[v]['nonstop'] == 'former') and i18n[lang]['nonstop_past'] or i18n[lang]['nonstop_present'])(line['line title']) })				)				insert(wikitable, '') insert(wikitable, '') insert(wikitable, '') else Format = data[v]['station format'] or i18n[lang]['error_format'] -- British/American English. Chinese modules will require a separate modification. -- For other translations: colour = line.colour or line.color local lang = data[v]['lang'] or line['color'] and 'en-US' or data[v]['color'] and 'en-US' or 'en-GB'

local colour = lang == 'en-US' and 'color' or lang == 'en-GB' and 'colour' colour = line[colour] or data[v][colour] -- Alternate termini can be specified based on type local sideCell = {true, true} for i, b in ipairs({'left', 'right'}) do					if not args[v][b] then -- If no station is given on one side, the station is assumed to be the terminus on that side local _through = args[v]['through-' .. b] or args[v]['through'] sideCell[i] = _through and "''" .. i18n[lang]['through'](data[v]['lines'][_through]['line title']) .. ""							or "" .. ((args[v]['reverse-' .. b]							or args[v]['reverse']) and i18n[lang]['reverse']							or i18n[lang]['terminus']) .. "''"					else local terminus local _terminus = line[b .. ' terminus']

if type(_terminus) == 'string' or (type(_terminus) == 'table' and (_terminus[2] or _terminus['via'])) then -- If the terminus table has more than one numbered key or has the via key then the table shows only the default termini, since _terminus[2] cannot be used and _terminus[via] is reserved if args[v]['type-' .. b] then terminus = args[v]['type-' .. b] local _or = match(args[v]['type-' .. b], i18n[lang]['or-format']) if _or then terminus = gsub(terminus, i18n[lang]['or-format'], '\127_OR_\127') terminus = gsub(terminus, i18n[lang]['comma-format'], '\127_OR_\127') end local _via = (match(terminus, i18n[lang]['via-format'])) if _via then terminus = gsub(terminus, i18n[lang]['via-format'], '') terminus = mw.text.split(terminus, '\127_OR_\127') terminus['via'] = _via elseif _or then terminus = mw.text.split(terminus, '\127_OR_\127') end else terminus = _terminus end elseif type(_terminus) == 'table' then terminus = _terminus[args[v]['type-' .. b]] or _terminus[args[v]['type']] --							_terminus[args[v]['type']] or (args[v]['service'] and args[v]['branch'] and _terminus[args[v]['service'] .. '|' .. args[v]['branch']]) or _terminus[args[v]['branch']] or _terminus[args[v]['service']] or							or _terminus[1] end local mainText = args[v]['note-' .. b] and station(args[v][b]) .. small(args[v]['note-' .. b]) or station(args[v][b]) local subText = (args[v]['oneway-' .. b] or line['oneway-' .. b]) and i18n[lang]['oneway'] or args[v][b] == terminus and i18n[lang]['terminus'] or line['circular'] and terminus or i18n[lang]['towards'](station(terminus)) subText = small(subText, true) sideCell[i] = mainText .. subText end end insert(wikitable, '\n|-') insert(wikitable, '\n|' .. style['body cell'] .. sideCell[1]) insert(wikitable, concat({'\n|', style['body banner'], colour, '"|',					'\n|', style['body cell'], line['line title'],					-- Service; table key 'services' in subpages (data type table, with strings as keys and tables {'text', 'hex triplet'} as values). If table does not exist then the input is displayed as the text --					(args[v]['service'] and small(line['services'] and service(line['services'][args[v]['service']]) or args[v]['service']) or line['services'] and line['services'][1] and small(service(line['services'][1])) or ),					-- Branch; table key 'branches' in subpages (data type table, with strings as keys). If table does not exist then the input is displayed as the text --					(args[v]['branch'] and small(line['branches'] and line['branches'][args[v]['branch']] or args[v]['branch']) or line['branches'] and line['branches'][1] and small(line['branches'][1]) or ),					-- Note-mid; no table key. The input is displayed as the text (args[v]['note-mid'] and small(args[v]['note-mid']) or ''), -- Transfer; uses system's station link table (args[v]['transfer'] and small('transfer at ' .. station(args[v]['transfer']), true) or ''), '\n|', style['body banner'], colour, '"|'}))				insert(wikitable, '\n|' .. style['body cell'] .. sideCell[2])			end		end		if args[v]['note-row'] then -- Note			insert(wikitable, '\n|-\n|colspan="5" ' .. style['body cell'] .. args[v]['note-row'])			insert(wikitable, )			insert(wikitable, )			insert(wikitable, )		end	end	local function combine(t, n)		if t[n + 4] ~=  and t[n + 4] == t[n] then			t[n + 4] =  -- The cell in the next row is deleted			local rowspan = 2			while t[n + rowspan * 4] == t[n] do				t[n + rowspan * 4] = 				rowspan = rowspan + 1			end			t[n] = gsub(t[n], '\n|style="', '\n|rowspan="' .. rowspan .. '" style="')		end	end	local M = #wikitable	for i = 3, M, 4 do combine(wikitable, i) end	for i = 4, M, 4 do combine(wikitable, i) end	for i = 5, M, 4 do combine(wikitable, i) end	insert(wikitable, '\n|}')	return concat(wikitable) end

local getArgs = require('Module:Arguments').getArgs

local function makeInvokeFunction(funcName) -- makes a function that can be returned from #invoke, using -- Module:Arguments. return function (frame) local args = getArgs(frame, {parentOnly = true}) return p[funcName](args, frame) end end

p.main = makeInvokeFunction('_main')

local function getColour(data, system, line, branch, frame) if system then if line then return frame:expandTemplate{ title = system .. ' color', args = {line, ['branch'] = branch} } end return frame:expandTemplate{ title = system .. ' color' } else if line then local line = getLine(data, line) local colour = line['colour'] or line['color'] if type(colour) == 'table' then colour = colour[branch or 1] end return colour end return data['colour'] or data['color'] end end

function p._colour(args, frame) if args[1] then local data = getData(args[1], true) if not data then return getColour(nil, args[1], args[2], args[3], frame) end return getColour(data, nil, args[2], args[3]) end end

p.colour = makeInvokeFunction('_colour')

p.color = p.colour

function p._box(args, frame) local system = args[1] or args.system local line = args[2] or args.line local inline = args[3] or args.inline if inline == 'box' then line = nil end if system then local data, colour = getData(system, true) if data then colour = getColour(data, nil, line, args.branch) if line then line = getLine(data, line)['line title'] if not line then return error(i18n[lang]['error_unknown']) end if type(line) == 'table' then line = line[args.branch or 1] end end else colour = getColour(nil, system, line, args.branch, frame) if line then line = frame:expandTemplate{ title = system .. ' lines', args = {line, ['branch'] = branch} } if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown']) end end end local function square(var) -- Template:Color box return '      ' end local result if args.branch then result = ' – ' .. args.branch end if args.note then result = (result or '') .. ' ' .. args.note end result = result or '' if inline == 'yes' then result = square(colour) .. ' ' .. line .. result elseif inline == 'box' then result = square(colour) .. result elseif inline == 'link' then result = '%+)') .. '|' .. square(colour) .. ']]' .. result elseif inline == 'small' then -- Template:Color box result = '   ' .. ' ' .. line .. result else -- Template:Legend result = '   ' .. line .. result .. ' '		end return result end return error(i18n[lang]['error_unknown']) end

p.box = makeInvokeFunction('_box')

return p