Difference between revisions of "Module:Adjacent stations"

From blackwiki
Jump to navigation Jump to search
blackwiki>Pi.1415926535
(that change is breaking numerous other templates in infobox station - I'm temporarily reverting until it can be fixed)
blackwiki>Ythlev
Line 1: Line 1:
require('Module:No globals')
+
local getArgs = require('Module:Arguments').getArgs
 
+
local p, data, lineData = {}
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.
 
 
 
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 page',
 
['error_line'] = 'Lines table missing in data module',
 
['error_missing'] = function(var)
 
return '"' .. (var or '') .. '" is missing from the data page'
 
end,
 
['error_unknown'] = function(var)
 
return 'Unknown system or line "' .. (var or '') .. '"'
 
end
 
},
 
['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_missing'] = function(var)
 
return '"' .. (var or '') .. '" is missing from the data page'
 
end,
 
['error_unknown'] = function(var)
 
return 'Unknown system or line "' .. (var or '') .. '"'
 
end
 
}
 
}
 
  
local require = require
+
local function dig(...)
 
+
-- Digs through a table with sub-tables using arguments as keys, returning the value of the last key argument
local function getData(system, verify)
+
-- Analogous to returning a file given a file path with sub-folders
if verify then
+
-- Returns nil if any given sub-table does not exist
local title = mw.title.new('Module:Adjacent stations/' .. system -- .. '/sandbox'
+
local arg = {...}
)
+
local a, i, n = arg[1], 1, select('#', ...)
if not (title and title.exists) then return nil end
+
while a and i < n do
 +
a = a[arg[i + 1]]
 +
i = i + 1
 
end
 
end
return require('Module:Adjacent stations/' .. system -- .. '/sandbox'
+
if i == n then return a end
)
 
 
end
 
end
  
local lower = mw.ustring.lower
+
local function makeInvokeFunc(funcName)
local gsub = mw.ustring.gsub
+
return function (frame)
 
+
local args = getArgs(frame)
local function getLine(data, lineN)
+
if funcName ~= '_main' then
if lineN then
+
local t = mw.title.new('Module:Adjacent stations/' .. (args.system or args[1]))
if data['aliases'] then
+
data = t.exists and mw.loadData('Module:Adjacent stations/' .. (args.system or args[1]))
lineN = data['aliases'][lower(lineN)] or lineN
+
if funcName ~= '_station' and (args.line or args[2]) then
end
+
lineData =  
local default = data['lines']['_default'] or {}
+
dig(data, 'lines', args.line or args[2]) or
local line = data['lines'][lineN] or {}
+
dig(data, 'lines', dig(data, 'aliases', string.lower(args.line or args[2])))
for k, v in pairs(default) do
 
if v then line[k] = line[k] or v end
 
end
 
line['title'] = line['title'] and gsub(line['title'], '%%1', lineN)
 
return line, lineN
 
end
 
end
 
 
 
local function getColor(data, system, line, Type, frame)
 
if system then
 
if line then return frame:expandTemplate{ title = system .. ' color', args = {line, ['branch'] = Type} } end
 
return frame:expandTemplate{ title = system .. ' color' }
 
else
 
line = (getLine(data, line))
 
local default = data['lines']['_default']
 
if line or default then
 
default = default or {}
 
if not line then line = mw.clone(default) end
 
local color = line['color'] or line['background color'] or default['color'] or default['background color'] or data['system color']
 
local Type_value = Type and line['types'] and (line['types'][Type] and line['types'][Type]['color'])
 
if Type_value then color = Type_value end
 
return color
 
end
 
return (default and (default['color'] or default['background color']) or data['system color'] or '')
 
end
 
end
 
 
 
local match = mw.ustring.match
 
local concat = table.concat
 
local lineN, typeN
 
 
 
local function getStation(station, _Format)
 
if type(_Format) == 'table' then
 
_Format = _Format[lineN] or _Format[1]
 
if type(_Format) == 'table' then
 
_Format = _Format[typeN] or _Format[1]
 
end
 
end
 
if typeN then _Format = gsub(_Format, '%%3', typeN) end
 
if lineN then _Format = gsub(_Format, '%%2', lineN) end
 
return (match(_Format, '%[%[.+%]%]')) and (gsub(_Format, '%%1', station)) or concat({'[[', gsub(_Format, '%%1', station), '|', station, ']]'})
 
end
 
 
 
function p._main(_args) -- Arguments are processed here instead of the main function
 
local insert = table.insert
 
local lower = mw.ustring.lower
 
 
 
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 '<div class="isA">' .. s .. '</div>'
 
or '<div class="smA">' .. s .. '</div>'
 
end
 
 
 
local style = { -- Style for each cell type
 
['header cell'] = 'class="hcA"|',
 
['header midcell'] = 'colspan="3" class="hmA"|',
 
['body cell'] = 'class="bcA"|',
 
['body banner'] = 'class="bbA" style="background-color:#',
 
}
 
 
 
local Format
 
local function subst(var1, var2)
 
-- var1 is the terminus or table of termini; var2 is the key for the table of termini
 
return type(var1) == 'string' and getStation(var1, (Format[var1] or Format[1]))
 
or type(var1) == 'table' and #var1 > 0 and getStation(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 function rgb(var)
 
if var:len() == 3 then
 
return {tonumber(var:sub(1, 1), 16) * 17, tonumber(var:sub(2, 2), 16) * 17, tonumber(var:sub(2, 2), 16) * 17}
 
elseif var:len() == 6 then
 
return {tonumber(var:sub(1, 2), 16), tonumber(var:sub(3, 4), 16), tonumber(var:sub(5, 6), 16)}
 
end
 
return {}
 
end
 
 
 
local data = {} -- A table of data modules for each address
 
local wikitable = {'{| class="wikitable adjacent-stations"'}
 
 
 
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]]
 
or error(i18n[lang]['error_unknown'](args[v]['system']))
 
 
 
local lang = data[v]['lang'] or lang
 
 
 
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 icon'] and data[v]['system icon'] .. ' ' or ''), (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" class="hmA"|'.. 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 and not args[v]['system'] then
 
args[v]['line'] = args[index[i - 1]]['line']
 
end
 
 
 
lineN = args[v]['line'] or '_default'
 
typeN = args[v]['type']
 
if data[v]['aliases'] then
 
lineN = data[v]['aliases'][lower(lineN)] or lineN
 
if typeN then typeN = data[v]['aliases'][lower(typeN)] or typeN end
 
end
 
 
 
-- get the line table
 
local line = data[v]['lines'] and (mw.clone(data[v]['lines'][lineN]) or error(i18n[lang]['error_unknown'](args[v]['line']))) or error(i18n[lang]['error_line'])
 
local default = data[v]['lines']['_default'] or {}
 
line['title'] = line['title'] or default['title']
 
line['title'] = gsub(line['title'], '%%1', lineN)
 
 
 
-- cell across row for non-stop service
 
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'])(p._box({data = data[v], line = lineN, Type = typeN, inline = 'yes'}))
 
})
 
)
 
insert(wikitable, '')
 
insert(wikitable, '')
 
insert(wikitable, '')
 
else
 
Format = data[v]['station format'] or i18n[lang]['error_format']
 
 
 
local color, background_color
 
local Type = line['types'] and line['types'][typeN] -- get the line type table
 
 
 
if Type then
 
if Type['color'] then
 
-- line color is used as background if there is no background color in the line type table
 
background_color = Type['background color'] or line['color']
 
color = Type['color']
 
elseif Type['background color'] then
 
background_color = Type['background color']
 
color = line['color'] or default['color'] or ''
 
else
 
background_color = line['background color']
 
color = line['color'] or default['color'] or ''
 
end
 
else
 
background_color = line['background color']
 
color = line['color'] or default['color'] or ''
 
end
 
 
 
-- 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']
 
local _through_data = getLine(data[v], _through)
 
if _through_data then _through = _through_data['title'] or _through end
 
sideCell[i] = _through and "''" .. i18n[lang]['through'](_through) .. "''"
 
or "''" .. ((args[v]['reverse-' .. b]
 
or args[v]['reverse']) and i18n[lang]['reverse']
 
or i18n[lang]['terminus']) .. "''"
 
else
 
local terminusT
 
local terminusN = Type and Type[b .. ' terminus'] or line[b .. ' terminus']
 
 
 
-- If the terminus table has more than one numbered key or has the via key then the table shows only the default termini, since terminusN[2] cannot be used and terminusN[via] is reserved
 
if type(terminusN) == 'string' or (type(terminusN) == 'table' and (terminusN[2] or terminusN['via'])) then
 
if args[v]['to-' .. b] then
 
terminusT = args[v]['to-' .. b]
 
local _or = match(terminusT, i18n[lang]['or-format'])
 
if _or then
 
terminusT = gsub(terminusT, i18n[lang]['or-format'], '\127_OR_\127')
 
terminusT = gsub(terminusT, i18n[lang]['comma-format'], '\127_OR_\127')
 
end
 
local _via = (match(terminusT, i18n[lang]['via-format']))
 
if _via then
 
terminusT = gsub(terminusT, i18n[lang]['via-format'], '')
 
terminusT = mw.text.split(terminusT, '\127_OR_\127')
 
terminusT['via'] = _via
 
elseif _or then
 
terminusT = mw.text.split(terminusT, '\127_OR_\127')
 
end
 
else
 
terminusT = terminusN
 
end
 
elseif type(terminusN) == 'table' then
 
terminusT = terminusN[args[v]['to-' .. b]] or terminusN[args[v]['to']] or terminusN[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] == terminusT and i18n[lang]['terminus']
 
or line['circular'] and terminusT
 
or i18n[lang]['towards'](station(terminusT))
 
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'], color, '"|',
 
'\n|', (background_color and 'class="bcA" style="background-color:rgba(' .. concat(rgb(background_color), ',') .. ',.2)"|' or style['body cell']), line['title'],
 
 
 
-- Type; table key 'types' in subpages (datatype table, with strings as keys). If table does not exist then the input is displayed as the text
 
(typeN and '<div>' .. (Type and Type['title'] or typeN) .. '</div>' or ''),
 
 
 
-- Note-mid; table key 'note-mid' in subpages. Overridden by user input
 
((args[v]['note-mid'] and small(args[v]['note-mid'])) or (Type and Type['note-mid'] and small(Type['note-mid'])) or (line['note-mid'] and small(line['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'], color, '"|'}))
 
insert(wikitable, '\n|' .. style['body cell'] .. sideCell[2])
 
 
end
 
end
 
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|class="', '\n|rowspan="' .. rowspan .. '" class="')
 
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)
 
return p[funcName](args, frame)
 
end
 
end
 
end
 
end
  
p.main = makeInvokeFunction('_main')
+
p.main = makeInvokeFunc('_main')
 
+
function p._main(args)
function p._color(args, frame)
+
local renderSuccessionBox = require(
local data = args.data
+
'Module:Adjacent stations/renderSuccessionBox' -- .. '/sandbox'
if args[1] or data then
+
)
data = data or getData(args[1], true)
+
return renderSuccessionBox(args)
if not data then return getColor(nil, args[1], args[2], args[3], frame) end
 
return getColor(data, nil, args[2], args[3])
 
end
 
 
end
 
end
  
p.color = makeInvokeFunction('_color')
+
p.station = makeInvokeFunc('_station')
 
+
function p._station(args, frame)
function p._box(args, frame)
+
local station = args.station or args[2]
local system = args[1] or args.system
+
if station then
lineN = args[2] or args.line
 
if not (system or lineN) then return '' end
 
local line, Type, line_data
 
local inline = args[3] or args.inline
 
typeN = args.type
 
local data = args.data
 
if system or data then
 
data = data or getData(system, true)
 
local color
 
 
if data then
 
if data then
local default = data['lines']['_default'] or {}
+
local stationFormat = data['station format'] or error(i18n[lang]['error_format'])
line, lineN = getLine(data, lineN)
+
local link = stationFormat[station] or stationFormat[1]
if typeN then
+
return string.match(link, '%[%[.+%]%]') and (string.gsub(link, '%%1', station)) or
typeN = data['aliases'] and data['aliases'][lower(typeN)] or typeN
+
table.concat({'[[', (string.gsub(link, '%%1', station)), '|', station, ']]'})
Type = line['types'] and line['types'][typeN] and line['types'][typeN]['title'] or typeN
 
end
 
color = getColor(data, nil, lineN, typeN)
 
if inline ~= 'box' then
 
line_data = line or error(i18n[lang]['error_unknown'](lineN))
 
line = line_data['title'] or default['title'] or error(i18n[lang]['error_missing']('title'))
 
line = gsub(line, '%%1', lineN)
 
end
 
 
else
 
else
color = getColor(nil, system, lineN, typeN, frame)
+
return frame:expandTemplate{
if inline ~= 'box' then
+
title = (args.system or args[1]) .. ' stations',
line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, ['branch'] = typeN} }
+
args = {
if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end
+
['station'] = station,
end
+
['line'] = args.line or args[3],
Type = typeN
+
['branch'] = args['type'] or args[4]
end
+
}
 
 
local result
 
 
 
if Type and Type ~= '' and inline ~= 'box' then
 
if line == '' then
 
line = Type
 
else
 
result = ' – ' .. Type
 
end
 
end
 
if args.note then result = (result or '') .. ' ' .. args.note end
 
result = result or ''
 
 
 
if not inline then -- [[Template:Legend]]
 
result = '<div class="legend" style="-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;width:1.5em;height:1.5em;margin:1px 0;border:1px solid black;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'
 
elseif inline == 'yes' then
 
result = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span> ' .. line .. result
 
elseif inline == 'box' then
 
result = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>' .. result
 
elseif inline == 'link' then
 
local link = args.link or match(line, '%[%[([^%[:|%]]+)[|%]]')
 
if link then
 
result = '[[' .. link .. '|<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>]]' .. result
 
else
 
result = '<span style="background-color:#' .. color .. ';border:1px solid #000">    </span>' .. result
 
end
 
elseif inline == 'square' then
 
result = '<span style="color:#' .. color .. ';line-height:initial">■</span> ' .. line .. result
 
elseif inline == 'lsquare' then
 
local link = args.link or match(line, '%[%[([^%[:|%]]+)[|%]]')
 
if link then
 
result = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">■</span>]]'
 
else
 
result = '<span style="color:#' .. color .. ';line-height:initial">■</span>'
 
end
 
elseif inline == 'dot' then
 
result = '<span style="color:#' .. color .. ';line-height:initial">●</span> ' .. line .. result
 
elseif inline == 'ldot' then
 
local link = args.link or match(line, '%[%[([^%[:|%]]+)[|%]]')
 
if link then
 
result = '[[' .. link .. '|<span style="color:#' .. color .. ';line-height:initial">●</span>]]'
 
else
 
result = '<span style="color:#' .. color .. ';line-height:initial">●</span>'
 
end
 
elseif inline == 'small' then
 
result = '<span style="background-color:#' .. color .. '"> </span>' .. ' ' .. line .. result
 
else
 
local yesno = require("Module:Yesno")
 
local link = args.link or match(line, '%[%[([^%[:|%]]+)[|%]]')
 
local border_color, text_color
 
if line_data then
 
if line_data['types'] and line_data['types'][typeN] then
 
local Type_data = line_data['types'][typeN]
 
border_color = Type_data['border color'] or line_data['border color'] or color
 
text_color = Type_data['text color'] or line_data['text color']
 
lineN = Type_data['short name'] or line_data['short name'] or lineN
 
else
 
border_color = line_data['border color'] or color
 
text_color = line_data['text color']
 
lineN = line_data['short name'] or lineN
 
end
 
else
 
border_color = color
 
end
 
local greatercontrast = require('Module:Color contrast')._greatercontrast
 
text_color = text_color and '#' .. text_color or greatercontrast{color}
 
local bold = (yesno(args.bold) == false) or ';font-weight:bold'
 
if inline == 'route' then -- [[Template:RouteBox]]
 
if link then
 
result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
 
else
 
result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
 
end
 
elseif inline == 'croute' then -- [[Template:Bahnlinie]]
 
if link then
 
result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
 
else
 
result = '<span style="background-color:#' .. color .. ';border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:' .. text_color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
 
end
 
elseif inline == 'xroute' then -- [[Template:Bahnlinie]]
 
if link then
 
result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em">[[' .. link .. '|<span style="color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>]]</span>'
 
else
 
result = '<span style="border:.075em solid #' .. border_color .. ';border-radius:.5em;padding:0 .3em;color:#' .. color .. bold .. ';font-size:inherit;white-space:nowrap">' .. lineN .. '</span>'
 
end
 
else -- [[Template:Legend]] (fallback; duplication to simplify logic)
 
result = '<div class="legend" style="-webkit-column-break-inside:avoid;page-break-inside:avoid;break-inside:avoid-column"><span class="legend-color" style="display:inline-block;width:1.5em;height:1.5em;margin:1px 0;border:1px solid black;background-color:#' .. color .. '"> </span> ' .. line .. result .. '</div>'
 
end
 
end
 
 
 
result = gsub(result, ':%s*#transparent', ':transparent')
 
 
 
return result
 
end
 
end
 
 
 
p.box = makeInvokeFunction('_box')
 
 
 
function p._icon(args, frame)
 
local system = args[1] or args.system
 
local line = args[2] or args.line
 
local Type = args[3] or args.type
 
local data = args.data
 
if system or data then
 
data = data or getData(system)
 
 
 
local icon, Format
 
 
 
line = (getLine(data, line))
 
 
 
if line then
 
if Type then
 
Type = data['aliases'] and data['aliases'][lower(Type)] or Type
 
Type = line['types'] and line['types'][Type] -- If there's no type table or entry for this type, then it can't have its own icon
 
Format = Type['icon format'] or data['type icon format']
 
icon = Type['icon']
 
end
 
if not (Format or icon) then
 
Format = line['icon format'] or data['line icon format']
 
icon = line['icon']
 
end
 
end
 
if not (Format or icon) then
 
Format = data['system icon format']
 
icon = data['system icon']
 
end
 
 
 
if Format then
 
if Format ~= 'image' then return p._box({data = data, [2] = (args[2] or args.line), [3] = Format, type = (args[3] or args.type), bold = args.bold, link = args.link}, frame) end
 
end
 
 
 
local size = args.size
 
if size then
 
if match(size, '%d$') then
 
size = '|' .. size .. 'px'
 
else
 
size = '|' .. size
 
end
 
-- Upright values are to be disabled until there is use of upright scaling in subpages; doesn't seem to work anyway as of 2018-08-10
 
local tmp = {
 
'|%s*%d*x?%d+px%s*([%]|])', -- '|%s*upright=%d+%.?%d*%s*([%]|])', '|%s*upright%s*([%]|])'
 
 
}
 
}
if match(icon, tmp[1]) then
 
icon = gsub(icon, tmp[1], size .. '%1')
 
-- elseif match(icon, tmp[2]) then
 
-- icon = gsub(icon, tmp[2], size .. '%1')
 
-- elseif match(icon, tmp[3]) then
 
-- icon = gsub(icon, tmp[3], size .. '%1')
 
else
 
icon = gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1' .. size .. '%2')
 
end
 
 
end
 
end
 
local link = args.link
 
if link then
 
if match(icon, '|%s*link=[^%]|]*[%]|]') then
 
icon = gsub(icon, '|%s*link=[^%]|]*([%]|])', '|link=' .. link .. '%1')
 
else
 
icon = gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|link=' .. link .. '%2')
 
end
 
end
 
 
local alt = args.alt or link
 
if alt then
 
if match(icon, '|%s*alt=[^%]|]*[%]|]') then
 
icon = gsub(icon, '|%s*alt=[^%]|]*([%]|])', '|alt=' .. alt .. '%1')
 
else
 
icon = gsub(icon, '(%[%[[^%]|]+)([%]|])', '%1|alt=' .. alt .. '%2')
 
end
 
end
 
 
return icon
 
 
end
 
end
 
end
 
end
  
p.icon = makeInvokeFunction('_icon')
+
p.line = makeInvokeFunc('_line')
 
 
 
function p._line(args, frame)
 
function p._line(args, frame)
local system = args[1] or args.system
+
if data then
local line = args[2] or args.line
+
if args['type'] or args[3] then
if not line then return '' end
+
local typ = dig(lineData, 'types', args['type'] or args[3], 'title')
local Type = args[3] or args.type
+
return table.concat({lineData['title'], ' (', typ, ')'})
local data = args.data
 
if system or data then
 
data = data or getData(system, true)
 
if data then
 
line = (getLine(data, line)) or error(i18n[lang]['error_unknown'](line))
 
if Type then
 
Type = data['aliases'] and data['aliases'][lower(Type)] or Type
 
Type = line['types'] and line['types'][Type] and line['types'][Type]['title'] or Type
 
end
 
line = line['title'] or error(i18n[lang]['error_missing']('title'))
 
 
else
 
else
line = frame:expandTemplate{ title = system .. ' lines', args = {line, ['branch'] = Type} }
+
return
if mw.text.trim(line) == '' then return error(i18n[lang]['error_unknown'](lineN)) end
+
lineData and lineData['title'] or
 +
(string.gsub(dig(data, 'lines', '_default', 'title'), '%%1', args.line or args[2] or '_default'))
 
end
 
end
 
+
else
if Type then
+
return frame:expandTemplate{
if line == '' then
+
title = (args.system or args[1]) .. ' stations',
line = Type
+
args = {
else
+
args.line or args[2],
line = line .. ' – ' .. Type
+
['branch'] = args['type'] or args[3]
end
+
}
end
+
}
return line
 
 
end
 
end
 
end
 
end
  
p.line = makeInvokeFunction('_line')
+
p.color = makeInvokeFunc('_color')
 
+
function p._color(args, frame)
function p._station(args, frame)
 
local system = args[1] or args.system
 
local station = args[2] or args.station
 
if not station then return '' end
 
lineN = args[3] or args.line
 
typeN = args[4] or args.type
 
local data = args.data
 
if system or data then
 
data = data or getData(system, true)
 
if data then
 
local _Format = data['station format'][station] or data['station format'][1]
 
if _Format then
 
if data['aliases'] then
 
if lineN then
 
lineN = data['aliases'][lower(lineN)] or lineN
 
end
 
if typeN then
 
typeN = data['aliases'][lower(typeN)] or typeN
 
end
 
end
 
station = getStation(station, _Format)
 
else
 
station = station or ''
 
end
 
else
 
station = frame:expandTemplate{ title = system .. ' stations', args = {['station'] = station, ['line'] = lineN, ['branch'] = typeN} }
 
end
 
 
 
return station
 
end
 
end
 
 
 
p.station = makeInvokeFunction('_station')
 
 
 
function p._style(args, frame)
 
local style = args[1] or args.style
 
local system = args[2] or args.system
 
local line = args[3] or args.line
 
local station = args[4] or args.station
 
local result = {}
 
local data = args.data
 
local default = 'background-color:#efefef' -- Default background color for {{Infobox station}}
 
if system or data then
 
data = data or getData(system, true)
 
end
 
 
if data then
 
if data then
local function getValue(var)
+
return
if type(var) == 'table' then
+
dig(lineData, 'types', args['type'] or args[3], 'color') or
var = var[line] or var[1]
+
lineData and lineData['color'] or
if type(var) == 'table' then
+
dig(data, 'lines', '_default', 'color')
var = var[station] or var[1]
 
end
 
end
 
if var ~= '' then return var end
 
end
 
 
 
if style == 'header' then
 
local tmp = data['name format'] and getValue(data['name format'])
 
if tmp then table.insert(result, tmp) end
 
elseif style == 'subheader' then
 
local tmp = data['header background color'] and getValue(data['header background color'])
 
if tmp then
 
table.insert(result, 'background-color:#' .. tmp)
 
local color = data['header text color'] and getValue(data['header text color'])
 
if color then
 
table.insert(result, 'color:#' .. color)
 
else
 
local greatercontrast = require('Module:Color contrast')._greatercontrast
 
if greatercontrast{tmp} == '#FFFFFF' then table.insert(result, 'color:#FFFFFF') end
 
end
 
else
 
table.insert(result, default)
 
local color = data['header text color'] and getValue(data['header text color'])
 
if color then table.insert(result, 'color:#' .. color) end
 
end
 
end
 
result = table.concat(result, ';')
 
elseif system then
 
local title = 'Template:' .. system .. ' style'
 
local titleObj = mw.title.new(title)
 
if titleObj and titleObj.exists then
 
local tmp
 
if style == 'header' then
 
tmp = frame:expandTemplate{ title = title, args = {'name_format', line, station} }
 
if tmp ~= '' then table.insert(result, tmp) end
 
elseif style == 'subheader' then
 
tmp = frame:expandTemplate{ title = title, args = {'thbgcolor', line, station} }
 
if tmp ~= '' then
 
table.insert(result, 'background-color:#' .. tmp)
 
local color = frame:expandTemplate{ title = title, args = {'thcolor', line, station} }
 
if color ~= '' then
 
table.insert(result, 'color:#' .. color)
 
else
 
local ratio = require('Module:Color contrast')._ratio
 
if ratio{tmp, '222222'} < 4.5 then table.insert(result, 'color:#FFFFFF') end -- 222222 is the default text color in Vector
 
end
 
else
 
table.insert(result, default)
 
tmp = frame:expandTemplate{ title = title, args = {'thcolor', line, station} }
 
if tmp ~= '' then
 
table.insert(result, 'color:#' .. tmp)
 
end
 
end
 
end
 
result = table.concat(result, ';')
 
else
 
if style == 'subheader' then
 
result = default
 
else
 
result = ''
 
end
 
end
 
 
else
 
else
if style == 'subheader' then
+
return frame:expandTemplate{
result = default
+
title = (args.system or args[1]) .. ' color',
else
+
args = {
result = ''
+
args.line or args[2],
end
+
['branch'] = args['type'] or args[3]
 +
}
 +
}
 
end
 
end
 
return result
 
 
end
 
end
  
function p.style(frame)
+
p.icon = makeInvokeFunc('_icon')
local args = getArgs(frame, {frameOnly = true})
+
function p._icon(args)
return p._style(args, frame)
+
return
 +
dig(lineData, 'types', args['type'] or args[3], 'icon') or
 +
lineData and lineData['icon'] or
 +
data['system icon']
 
end
 
end
  
function p.convert(frame)
+
p.box = makeInvokeFunc('_box')
local args = frame.args
+
function p._box(args)
local code = mw.text.split(mw.ustring.gsub(args[1], '^%s*{{(.*)}}%s*$', '%1'), '%s*}}%s*{{%s*')
+
local colour, root = p._color(args), mw.html.create('div')
local system
+
local style = args.style or args.inline
local group = 0
+
local typeData = dig(lineData, 'types', args['type'] or args[3])
local delete = {
+
if colour then
['s-rail'] = true,
+
colour = string.match(colour, '#') and colour or '#' .. colour
['s-rail-next'] = true,
+
end
['s-rail-national'] = true,
+
local linkedBox =
['s-start'] = true,
+
style == 'link' or
['s-rail-start'] = true,
+
style == 'ldot' or
['start'] = true,
+
style == 'lsquare' or
['s-end'] = true,
+
style and string.match(style, 'route')
['end'] = true
+
if linkedBox then
}
+
root
local order = {
+
:wikitext(string.match(p._line(args), '%[%[.+|'))
'line', 'left', 'right', 'to-left', 'to-right',
+
end
'oneway-left', 'oneway-right', 'through-left', 'through-right',
+
local box = root:tag('span')
'reverse', 'reverse-left', 'reverse-right',
+
if
'note-left', 'note-mid', 'note-right', 'transfer'
+
style == 'dot' or
-- circular: use module subpage
+
style == 'ldot' or
-- state: not implemented
+
style == 'square' or
}
+
style == 'lsquare' then
local replace = {
+
box
['previous'] = 'left',
+
:css('line-height', 'initial')
['next'] = 'right',
 
['type'] = 'to-left',
 
['type2'] = 'to-right',
 
['branch'] = 'type',
 
['note'] = 'note-left',
 
['notemid'] = 'note-mid',
 
['note2'] = 'note-right',
 
['oneway1'] = 'oneway-left',
 
['oneway2'] = 'oneway-right',
 
['through1'] = 'through-left',
 
['through2'] = 'through-right'
 
}
 
local remove_rows = {}
 
local data = {}
 
for i, v in ipairs(code) do
 
code[i] = mw.ustring.gsub(code[i], '\n', ' ')
 
local template = mw.ustring.lower(mw.text.trim(mw.ustring.match(code[i], '^[^|]+')))
 
code[i] = mw.ustring.match(code[i], '(|.+)$')
 
if template == 's-line' then
 
data[i] = {}
 
local this_system = mw.text.trim(mw.ustring.match(code[i], '|%s*system%s*=([^|]+)'))
 
code[i] = mw.text.split(code[i], '%s*|%s*')
 
for m, n in ipairs(code[i]) do
 
local tmp = mw.text.split(n, '%s*=%s*')
 
if tmp[3] then
 
tmp[2] = mw.ustring.gsub(n, '^.-%s*=', '')
 
end
 
tmp[1] = replace[tmp[1]] or tmp[1]
 
if tmp[2] then
 
-- checks for matching brackets
 
local curly = select(2, mw.ustring.gsub(tmp[2], "{", ""))-select(2, mw.ustring.gsub(tmp[2], "}", ""))
 
local square = select(2, mw.ustring.gsub(tmp[2], "%[", ""))-select(2, mw.ustring.gsub(tmp[2], "%]", ""))
 
if not (curly+square==0) then
 
local count = mw.clone(m)+1
 
while not (curly+square==0) do
 
tmp[2] = tmp[2]..'|'..code[i][count]
 
curly = curly+select(2, mw.ustring.gsub(code[i][count], "{", ""))-select(2, mw.ustring.gsub(code[i][count], "}", ""))
 
square = square+select(2, mw.ustring.gsub(code[i][count], "%[", ""))-select(2, mw.ustring.gsub(code[i][count], "%]", ""))
 
code[i][count] = ''
 
count = count+1
 
end
 
end
 
data[i][tmp[1]] = tmp[2]
 
end
 
end
 
if (this_system ~= system) or (not system) then
 
system = this_system
 
data[i]['system'] = system
 
else
 
data[i]['system'] = nil
 
end
 
local last = data[i-1] or data[i-2] or data[i-3]
 
if last then
 
for r, s in pairs({
 
['hide1'] = {'left', 'to-left', 'note-left', 'oneway-left'},
 
['hide2'] = {'right', 'to-right', 'note-right', 'oneway-right'},
 
['hidemid'] = {'type', 'note-mid'}
 
}) do
 
if data[i][r] then
 
for m, n in ipairs(s) do
 
if not data[i][n] then
 
data[i][n] = last[n]
 
end
 
end
 
end
 
end
 
end
 
code[i] = {}
 
local X = '|'
 
local Y = (i+group)..'='
 
if data[i]['system'] then
 
table.insert(code[i], '|system')
 
table.insert(code[i], Y)
 
table.insert(code[i], data[i]['system'])
 
table.insert(code[i], '\n')
 
end
 
for m, n in ipairs(order) do
 
if data[i][n] then
 
table.insert(code[i], X)
 
table.insert(code[i], n)
 
table.insert(code[i], Y)
 
table.insert(code[i], data[i][n])
 
end
 
end
 
code[i] = table.concat(code[i])
 
elseif template == 's-note' then
 
code[i] = mw.ustring.gsub(code[i], '|%s*text%s*=', '|header'..i+group..'=')
 
code[i] = mw.ustring.gsub(code[i], '|%s*wide%s*=[^|]*', '')
 
elseif template == 's-text' then
 
code[i] = mw.ustring.gsub(code[i], '|%s*text%s*=', '|note-row'..i+group..'=')
 
elseif delete[template] then
 
code[i] = ''
 
table.insert(remove_rows, 1, i) -- at the start, so that the rows are deleted in reverse order
 
group = group-1
 
end
 
 
end
 
end
for i, v in ipairs(remove_rows) do
+
if
table.remove(code, v)
+
style == 'dot' or
 +
style == 'ldot' or
 +
style == 'square' or
 +
style == 'lsquare' or
 +
style == 'xroute' then
 +
box
 +
:css('color', colour)
 +
else
 +
box
 +
:css('background-color', colour)
 
end
 
end
code = table.concat(code, '\n')
+
if
local t = {'{{Adjacent stations', '\n}}'}
+
style == nil or
system = mw.ustring.match(code, '|system(%d*)=')
+
style == 'link' or
code = mw.ustring.gsub(code, '\n\n+', '\n')
+
style == 'inline' or style == 'yes' or
if tonumber(system) > 1 then
+
style == 'box' then
-- If s-line isn't the first template then the system will have to be moved to the top
+
box
system = mw.ustring.match(code, '|system%d*=([^|]*[^|\n])')
+
:css('border', '1px solid #000')
code = mw.ustring.gsub(code, '|system%d*=[^|]*', '')
+
elseif style and string.match(style, 'route') then
code = '\n|system1='..system..code
+
local borderColour = typeData and typeData['border color'] or lineData and lineData['border color'] or colour
elseif not mw.ustring.match(code, '^[^{%[]*|[^=|]+2=') then
+
box
-- If there's only one parameter group then there's no need to have line breaks
+
:css('border', '.075em solid ' .. borderColour)
code = mw.ustring.gsub(code, '\n', '')
+
:css('padding', '0 .3em')
code = mw.ustring.gsub(code, '(|[^=|]+)1=', '%1=')
+
if style ~= 'route' then
t[2] = '}}'
+
box
if not mw.ustring.match(code, '[%[{]') then
+
:css('border-radius', '.5em')
code = mw.ustring.gsub(code, '|[^=|]*=$', '')
 
code = mw.ustring.gsub(code, '|[^=|]*$', '')
 
 
end
 
end
 
end
 
end
if not mw.ustring.match(code, '[%[{]') then
+
if style == 'dot' or style == 'ldot' then
code = mw.ustring.gsub(code, '|[^=|]*=|', '|')
+
box:wikitext('●')
code = mw.ustring.gsub(code, '|[^=|]*|', '|')
+
elseif style == 'square' or style == 'lsquare' then
code = mw.ustring.gsub(code, '|[^=|]*=\n', '\n')
+
box:wikitext('■')
code = mw.ustring.gsub(code, '|[^=|]*\n', '\n')
+
elseif style and string.match(style, 'route') then
 +
box
 +
:css('color',
 +
typeData and typeData['text color'] or
 +
lineData and lineData['text color'] or
 +
style == 'xroute' and colour or
 +
'black'
 +
)
 +
:css('font-weight', args.bold == 'no' or 'bold')
 +
:css('font-size', 'inherit')
 +
:css('white-space', 'nowrap')
 +
:wikitext(args.line or args[2])
 +
else
 +
box
 +
:wikitext(string.rep('&nbsp;', style == 'small' and 1 or 4))
 +
end
 +
if linkedBox then root:wikitext(']]') end
 +
if
 +
style == nil or
 +
style == 'inline' or style == 'yes' or
 +
style == 'small' or
 +
style == 'dot' or
 +
style == 'square' then
 +
root
 +
:wikitext('&nbsp;', p._line(args))
 
end
 
end
return t[1]..code..t[2]
+
return root
 
end
 
end
  
 
return p
 
return p

Revision as of 12:11, 22 January 2019

Template:Sidebar Adjacent stations

This module implements {{Adjacent stations}}, {{Rail icon}}, {{Rail color box}}, {{Line link}}, {{Station link}} and {{Rail color}}. Please see those templates' pages for documentation on how to use those templates. (Instructions for the convert function of this module are in the {{Adjacent stations}} documentation.)

The aforementioned templates rely on data stored in subpages for this module (list). For example, {{Rail icon}} generates Template:Rail icon using Module:Adjacent stations/MTR.

It is possible to create and edit data by following existing examples, but having some knowledge of Lua helps prevent mistakes. If you have programmed or used Lua before, you may like to skip the next subsection.

Terms

  • Lua has data types. The ones relevant here are boolean, string, number, and table.
    • A boolean is either true or false.
    • A string is text, stored as a list of characters. While Lua has several ways of indicating strings within code, in the Lua examples here, strings are indicated by enclosing text in double quotes (e.g. "This is a string").
    • A number is a numerical value, like 0.5 or 42.
    • A table is a structure that can contain other objects, including other tables.
      • An empty table looks like {} in the code.
      • Tables have keys and values, which are typically of the structure ["key"] = value; each key–value pair is separated by a comma. All keys used here are strings or numbers.
      • {"text", "more text"} is equivalent to {[1] = "text", [2] = "more text"}.
  • A variable can be defined using local variable_name = "value".
  • Whitespace is any tab, line break or other space. Whitespace doesn't matter in Lua, but all examples here except for those inline with text are neatly indented for readability, with separate table keys on separate lines.
  • A return statement (e.g. return variable_name) causes a function to exit and reports the value of variable_name. The "function" here is the code in the main module calling the subpage, and the variable_name should be the data table.

Basic structure

Two modules are demonstrated below:

Blank
local p = {
	["system title"] = "",
	["system icon"] = "",
	["station format"] = {
		"",
		[""] = "",
	},
	["lines"] = {
		[""] = {
			["title"] = "",
			["color"] = "",
			["left terminus"] = "",
			["right terminus"] = "",
		},
	},
}

return p
Example
local x = "%1 light rail station"

local p = {
	["system title"] = "[[Kaohsiung Rapid Transit]]",
	["system icon"] = "[[File:Kaohsiung Mass Rapid Transit Logo(Logo Only).svg|18px|link=Kaohsiung Rapid Transit]]",
	["station format"] = {
		"%1 metro station",
		["Central Park"] = "Central Park metro station (Taiwan)",
		["Ciaotou station"] = "Qiaotou station",
		["Kaohsiung Main Station"] = "%1",
		["Zuoying"] = "%1 HSR station",
		
		-- Circular light rail
		["Lizihnei"] = x,
		["Kaisyuan Rueitian"] = x,
		["Cianjhen Star"] = x,
		["Kaisyuan Jhonghua"] = x,
		["Dream Mall"] = x,
		["Commerce and Trade Park"] = x,
		["Software Technology Park"] = x,
		["Kaohsiung Exhibition Center"] = x,
		["Cruise Terminal"] = x,
		["Glory Pier"] = x,
		["Love Pier"] = x,
		["Dayi Pier-2"] = x,
		["Penglai Pier-2"] = x,
		["Hamasen"] = x
	},
	["lines"] = {
		["R"] = {
			["title"] = "[[Red line (Kaohsiung MRT)|Red line]]",
			["color"] = "d30547",
			["left terminus"] = "Gangshan South",
			["right terminus"] = "Siaogang"
		},
		["O"] = {
			["title"] = "[[Orange line (Kaohsiung MRT)|Orange line]]",
			["color"] = "f77f00",
			["left terminus"] = "Sizihwan",
			["right terminus"] = "Daliao"
		},
		["C"] = {
			["title"] = "[[Circular light rail]]",
			["color"] = "77c35e",
			["left terminus"] = "Lizihnei",
			["right terminus"] = "Hamasen"
		}
	},
	["aliases"] = {
		["red"] = "R",
		["orange"] = "O",
		["circular"] = "C"
	}
}

return p

The example module is Module:Adjacent stations/Kaohsiung Rapid Transit.

  • The two required table entries are "station format" and "lines". The former is a table with data to form links to station articles, and the latter is a table containing a table for each line.
  • "system title" is the text in the middle cell of the table header row.
  • "station format" defines the default name format for station articles and exceptions. The first variable ("%1 metro station") is the default. Exceptions are listed as key–value pairs (e.g. "Zuoying"–"Zuoying HSR station"), where the key is the input name. The module displays the input name and links to the article with the formatted name, replacing "%1" with the input. Alternatively, the full wikilink and be entered. This can be used to have the display be different from the input.
  • "lines" is where the lines are listed. The names here are used internally and not displayed, so choose a concise one.
  • "line title" is the text displayed in the middle of each row indicating the line; "left terminus" is the default station name for the left side terminus, and "right terminus" is the default station name for the right side terminus.
  • Each "color" entry is the colour of the line.

Below is Module:Adjacent stations/Taiwan High Speed Rail:

local x = "%1 station"

local p = {
	["system title"] = "[[Taiwan High Speed Rail]]",
	["system icon"] = "[[File:Taiwan High Speed Rail Logo(Log Only).svg|18px|link=Taiwan High Speed Rail|alt=Taiwan High Speed Rail]]",
	["system color"] =  "c35617",
	["name format"] = "color: #FFFFFF; background-color: #C35617;",
	["station format"] = {
		"%1 HSR station",
		["Taipei"] = "Taipei Main Station",
		["Nangang"] = x,
		["Banqiao"] = x
	},
	["lines"] = {
		["_default"] = {
			["title"] = "[[Taiwan High Speed Rail|THSR]]",
			["color"] = "c35617",
			["left terminus"] = "Nangang",
			["right terminus"] = "Zuoying"
		}
	}
}

return p
  • x (defined in the first line) is a string used for formatting the station name. The variable x is used inside the "station format" table for the three articles where the title ends in "station" instead of "HSR station". This practice is optional but helpful when many articles fit a second pattern.
  • The module recognises a virtual line named ["_default"]. The title and colour of this line is used for all lines unless overridden. Parameters are used in the absence of a specified line= in transclusion.

Hierarchy and list of parameters

  1. The first layer of the table is data for the entire system, as well as output options.
  2. Under the system table is the list of lines.
  3. The third layer is data for a given line.
  4. Each line can have 'types'. This can be either types of services or branches of the line.
  5. The fifth layer is data for a given type.

If not specified, all keys and values are strings.

Main layer (1)

Parameter Type Used in {{Adjacent stations}} Description
["lang"] String Yes Values are "en-US" and "en-GB". If not set, "en-GB" is assumed.
["system title"] String Yes Text in the middle cell of the header.
["system icon"] String Yes Image used in the middle cell of the {{Adjacent stations}} header and by {{Rail icon}}.
["system icon format"] String No Icon type, used by {{Rail icon}}. If specified and not "image", the value is passed to the function that implements {{Rail color box}}.
["system color"] String No RGB hex triplet (three or six characters, like "BE2D2C" or "039"). Can be called by using only one parameter in {{Rail color}}.
["header stop noun"] String Yes The noun after 'preceding' and 'following' in the left and right header cells. Default value is "station".
["name format"] String No CSS for the header of {{Infobox station}} and anything else using the style function with |1=header. Values can be strings or nested tables, with the first level being for the line (whatever's in |style2= of {{Infobox station}}). The second level is currently unused. The first entry in a nested table with no key (i.e. with key 1) is the default.
["header background color"] String No RGB hex triplet for {{Infobox station}} subheaders and anything else using the style function with |1=subheader. By default, it is a light gray. Values can be strings or nested tables, like those for "name format".
["header text color"] String No RGB hex triplet for {{Infobox station}} subheaders and anything else using the style function with |1=subheader. By default, it is calculated based on the header background color. Values can be strings or nested tables, like those for ["name format"].
["station format"] Table or string Yes Table containing station format strings. The first entry without a specified key (i.e. with the key being the number 1) is the default, and all other entries must have keys corresponding to the input. Format strings without wikilink brackets are converted to links, with the input (usually the station name) used as the displayed text. Tables can be nested within this table to indicate options based on the line and line type passed to this template.

%1, %2 and %3 can be used in all strings regardless of the level of nesting to be replaced respectively by the station input, the line input (after alias replacement) and the type input (after alias replacement).

["lines"] Table Yes Data table containing line tables.
["aliases"] Table Yes Table containing aliases (as table keys) for lines (as values). All keys are lowercase, as the input is treated as case-insensitive by being lower-cased.

Station format table (2)

Parameter Type Used in {{Adjacent stations}} Description
[1] String Yes Default format.
["non-default station name"] String or table Yes Format for a non-default station, or line-specific format table.

Line-specific format table (3)

Parameter Type Used in {{Adjacent stations}} Description
[1] String Yes Default format.
["line name"] String or table Yes Format for a non-default station, or type-specific format table.

Type-specific format table (4)

Parameter Type Used in {{Adjacent stations}} Description
[1] String Yes Default format.
["type name"] String Yes Format for a non-default station.

Line table (3)

A virtual line named ["_default"] can be added to set default values for all lines. Currently, this is available for two parameters.

Parameter Type Used in {{Adjacent stations}} Description
["title"] String Yes The text displayed in the middle cell, typically a link to the line's article. If not specified, then the data in ["_default"] is used (%1 in the default value is replaced by the input after alias replacement).
["short name"] String No Abbreviated line name used by {{Rail color box}}.
["icon"] String No Image used by {{Rail icon}}.
["icon format"] String No Icon type used by {{Rail icon}}. If specified and not "image", the value is passed to the function that implements {{Rail color box}}.
["color"] String Yes RGB hex triplet. Lines fall back to the ["_default"] colour (if any) or the system's colour if they themselves do not have one; types fall back to the line's colour (if any), to the ["_default"] colour (if any) or to the system's colour. This colour is used in the second and fourth columns of {{Adjacent stations}}, and by {{Rail color box}} and {{Rail icon}} as the emphasised colour. By default, if a type and its line both have a colour, then the line's colour will be treated as the background colour (see next section) for the line name in the middle cell. This can be turned off by setting the type's background colour to "" or "transparent".
["background color"] String Yes RGB hex triplet (three or six characters). This colour is optional and is only displayed behind the line name in the middle cell. The module adds transparency so that all text displayed over the background is legible.
["border color"] String No RGB hex triplet used by {{Rail color box}}.
["text color"] String No RGB hex triplet used by {{Rail color box}}.
["left terminus"] String Yes The station which is usually the left terminus of the line. If there are multiple stations by default, the value should be a table containing numbered values (e.g. ["left terminus"] = {"Chesham", "Amersham"}). The key ["via"] in that table can be used to append 'via' and the value's station link.
["right terminus"] String Yes The station which is usually the right terminus of the line; behaves like ["left terminus"].
["note-mid"] String Yes Default small text below line and type names. Overridden by |note-mid= in transclusion.
["circular"] Boolean Yes If the value is true then the termini will display without 'toward'/'towards'.
["oneway-left"] Boolean Yes If the value is true then 'One-way operation' will display instead of the left terminus.
["oneway-right"] Boolean Yes Right counterpart of oneway-left.
["types"] Table Yes Table containing the line type tables.

Type table (5)

Parameter Type Used in {{Adjacent stations}} Description
["title"] String Yes The name of the line type. In {{Adjacent stations}}, this is displayed as normal-sized text below the line name in the middle cell; in {{Rail color box}}, for some options this is displayed after the line name, separated from it by a spaced en dash (this is also used for the nonstop text). To avoid displaying a type name, set this to "".
["short name"] String No Abbreviated line name used by {{Rail color box}}.
["icon"] String No Image used by {{Rail icon}}.
["icon format"] String No Icon type used by {{Rail icon}}. If specified and not "image", the value is passed to the function that implements {{Rail color box}}.
["color"] String Yes RGB hex triplet. Lines fall back to the ["_default"] colour (if any) or the system's colour if they themselves do not have one; types fall back to the line's colour (if any), to the ["_default"] colour (if any) or to the system's colour. This colour is used in the second and fourth columns of {{Adjacent stations}}, and by {{Rail color box}} and {{Rail icon}} as the emphasised colour. By default, if a type and its line both have a colour, then the line's colour will be treated as the background colour (see next section) for the line name in the middle cell. This can be turned off by setting the type's background colour to "" or "transparent".
["background color"] String Yes RGB hex triplet (three or six characters). This colour is optional and is only displayed behind the line name in the middle cell. The module adds transparency so that all text displayed over the background is legible.
["border color"] String No RGB hex triplet used by {{Rail color box}}.
["text color"] String No RGB hex triplet used by {{Rail color box}}.
["left terminus"] String Yes The station which is usually the left terminus of the line. Overrides line terminus. If there are multiple stations by default, the value should be a table containing numbered values (e.g. ["left terminus"] = {"Chesham", "Amersham"}). The key ["via"] in that table can be used to append 'via' and the value's station link.
["right terminus"] String Yes The station which is usually the right terminus of the line; behaves like ["left terminus"].
["note-mid"] String Yes Default small text below line and type names. Overridden by |note-mid= in transclusion.

For developers

Suggestions are welcomed on the talk page.

To-do list

  • Convert more systems from {{S-line}}, {{rail line}}, {{J-rserv}} and {{J-route}}
  • Make an example module which contains all of the module's features, to avoid excessive examples in the documentation (maybe based on {{Rdt demo}})
  • Allow direct replacement of {{Rail line}}?
  • Function for calling a line terminus (for station layouts?)
  • Before translation: figure out how to handle grammatical gender and inflection in various languages with the i18n table (e.g. these phrases)
  • Allow inline sources to be added
  • Figure out Wikidata integration (require sources on Wikidata end)
  • Add a short list of changes from {{S-line}}, for the convenience of the many editors who have used it in the last 11 years
    • changes in function (new structure, data inside module, circular and branch functionality changed, replacement of manual cell merging…)
    • parameter name changes (-left and -right, mostly – search {{S-line}} for {{{, maybe with the TemplateData generator, to make a list)

local getArgs = require('Module:Arguments').getArgs
local p, data, lineData = {}

local function dig(...)
	-- Digs through a table with sub-tables using arguments as keys, returning the value of the last key argument
	-- Analogous to returning a file given a file path with sub-folders
	-- Returns nil if any given sub-table does not exist
	local arg = {...}
	local a, i, n = arg[1], 1, select('#', ...)
	while a and i < n do
		a = a[arg[i + 1]]
		i = i + 1
	end
	if i == n then return a end
end

local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame)
		if funcName ~= '_main' then
			local t = mw.title.new('Module:Adjacent stations/' .. (args.system or args[1]))
			data = t.exists and mw.loadData('Module:Adjacent stations/' .. (args.system or args[1]))
			if funcName ~= '_station' and (args.line or args[2]) then
				lineData = 
					dig(data, 'lines', args.line or args[2]) or
					dig(data, 'lines', dig(data, 'aliases', string.lower(args.line or args[2])))
			end
		end
		return p[funcName](args, frame)
	end
end

p.main = makeInvokeFunc('_main')
function p._main(args)
	local renderSuccessionBox = require(
		'Module:Adjacent stations/renderSuccessionBox' -- .. '/sandbox'
	)
	return renderSuccessionBox(args)
end

p.station = makeInvokeFunc('_station')
function p._station(args, frame)
	local station = args.station or args[2]
	if station then
		if data then
			local stationFormat = data['station format'] or error(i18n[lang]['error_format'])
			local link = stationFormat[station] or stationFormat[1]
			return string.match(link, '%[%[.+%]%]') and (string.gsub(link, '%%1', station)) or
				table.concat({'[[', (string.gsub(link, '%%1', station)), '|', station, ']]'})
		else
			return frame:expandTemplate{
				title = (args.system or args[1]) .. ' stations',
				args = {
					['station'] = station,
					['line'] = args.line or args[3],
					['branch'] = args['type'] or args[4]
				}
			}
		end
	end
end

p.line = makeInvokeFunc('_line')
function p._line(args, frame)
	if data then
		if args['type'] or args[3] then
			local typ = dig(lineData, 'types', args['type'] or args[3], 'title')
			return table.concat({lineData['title'], ' (', typ, ')'})		
		else
			return
				lineData and lineData['title'] or
				(string.gsub(dig(data, 'lines', '_default', 'title'), '%%1', args.line or args[2] or '_default'))
		end
	else
		return frame:expandTemplate{
			title = (args.system or args[1]) .. ' stations',
			args = {
				args.line or args[2],
				['branch'] = args['type'] or args[3]
			}
		}
	end
end

p.color = makeInvokeFunc('_color')
function p._color(args, frame)
	if data then
		return
			dig(lineData, 'types', args['type'] or args[3], 'color') or
			lineData and lineData['color'] or
			dig(data, 'lines', '_default', 'color')
	else
		return frame:expandTemplate{
			title = (args.system or args[1]) .. ' color',
			args = {
				args.line or args[2],
				['branch'] = args['type'] or args[3]
			}
		}
	end
end

p.icon = makeInvokeFunc('_icon')
function p._icon(args)
	return
		dig(lineData, 'types', args['type'] or args[3], 'icon') or
		lineData and lineData['icon'] or
		data['system icon']
end

p.box = makeInvokeFunc('_box')
function p._box(args)
	local colour, root = p._color(args), mw.html.create('div')
	local style = args.style or args.inline
	local typeData = dig(lineData, 'types', args['type'] or args[3])
	if colour then
		colour = string.match(colour, '#') and colour or '#' .. colour
	end
	local linkedBox =
		style == 'link' or
		style == 'ldot' or
		style == 'lsquare' or
		style and string.match(style, 'route')
	if linkedBox then
		root
			:wikitext(string.match(p._line(args), '%[%[.+|'))
	end
	local box = root:tag('span')
	if
		style == 'dot' or
		style == 'ldot' or
		style == 'square' or
		style == 'lsquare' then
		box
			:css('line-height', 'initial')
	end
	if
		style == 'dot' or
		style == 'ldot' or
		style == 'square' or
		style == 'lsquare' or
		style == 'xroute' then
		box
			:css('color', colour)
	else
		box
			:css('background-color', colour)
	end
	if
		style == nil or
		style == 'link' or
		style == 'inline' or style == 'yes' or
		style == 'box' then
		box
			:css('border', '1px solid #000')
	elseif style and string.match(style, 'route') then
		local borderColour = typeData and typeData['border color'] or lineData and lineData['border color'] or colour
		box
			:css('border', '.075em solid ' .. borderColour)
			:css('padding', '0 .3em')
		if style ~= 'route' then
			box
				:css('border-radius', '.5em')
		end
	end
	if style == 'dot' or style == 'ldot' then
		box:wikitext('●')
	elseif style == 'square' or style == 'lsquare' then
		box:wikitext('■')
	elseif style and string.match(style, 'route') then
		box
			:css('color',
				typeData and typeData['text color'] or
				lineData and lineData['text color'] or
				style == 'xroute' and colour or
				'black'
			)
			:css('font-weight', args.bold == 'no' or 'bold')
			:css('font-size', 'inherit')
			:css('white-space', 'nowrap')
			:wikitext(args.line or args[2])
	else
		box
			:wikitext(string.rep('&nbsp;', style == 'small' and 1 or 4))
	end	
	if linkedBox then root:wikitext(']]') end
	if
		style == nil or
		style == 'inline' or style == 'yes' or
		style == 'small' or
		style == 'dot' or
		style == 'square' then
		root
			:wikitext('&nbsp;', p._line(args))
	end
	return root
end

return p