모듈:Adjacent stations

require('strict')
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 = require("Module:Adjacent stations/i18n")
local function getData(system, verify)
	if verify then
		local title = mw.title.new('Module:Adjacent stations/' .. system -- .. '/sandbox'
			)
		if not (title and title.exists) then return nil end
	end
	return require('Module:Adjacent stations/' .. system -- .. '/sandbox'
		)
end
local function getLine(data, lineN)
	if lineN then
		if data'aliases' then
			lineN = data'aliases'mw.ustring.lower(lineN) or lineN
		end
		local default = data'lines_default' or {}
		local line = data'lines'lineN or {}
		for k, v in pairs(default) do
			if v then linek = linek or v end
		end
		line'title' = line'title' and mw.ustring.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 lineN, typeN
local function somethingMissing(name, key, formats)
	local formatKeys = {}
	for k in pairs(formats) do
		table.insert(formatKeys, k)
	end
	return name .. ' was "' .. key .. '" but neither an entry for it nor a default was found. Choices were: ' .. table.concat(formatKeys, ', ')
end
local function getStation(station, _Format)
	if type(_Format) == 'table' then
		local lineNformats = _Format
		_Format = lineNformatslineN or lineNformats1
		if not _Format then
			error(somethingMissing('lineN', lineN, lineNformats))
		elseif type(_Format) == 'table' then
			local typeNformats = _Format
			_Format = typeNformatstypeN or typeNformats1
			if not _Format then
				error(somethingMissing('typeN', typeN, typeNformats))
			end
		end
	end
	if typeN then _Format = mw.ustring.gsub(_Format, '%%3', typeN) end
	if lineN then _Format = mw.ustring.gsub(_Format, '%%2', lineN) end
	return (mw.ustring.match(_Format, '%%.+%%')) and (mw.ustring.gsub(_Format, '%%1', station)) or table.concat({, station, })
end
local function getTerminusText(var, 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, (Formatvar1 or Format1))
		or type(var1) == 'table' and #var1 > 0 and getStation(var1var2, (Formatvar1var2 or Format1))
		or 
	end
	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
				ti = i18nlang'comma'(subst(var, i))
			end
			if #var > 1 then t#var = i18nlang'or'(subst(var, #var)) end
			if var'via' then
				if i18nlang'via-first' then
					table.insert(t, 1, i18nlang'via'(subst(var, 'via')))
				else
					table.insert(t, i18nlang'via'(subst(var, 'via')))
				end
			end
			return table.concat(t)
		else
			return 
		end
	else
		return var or 
	end
end
function p._main(_args) -- Arguments are processed here instead of the main function
	local yesno = require('Module:Yesno')
	local trimq = require('Module:Trim quotes')._trim
	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
		_argsk = v:match('^%s*(.-)%s*$')
		if _argsk and _argsk ~=  then
			local a = mw.ustring.match(k, '^(.*%D)%d+$') or k -- The parameter; address 1 can be omitted
			local b = tonumber(mw.ustring.match(k, '^.*%D(%d+)$')) or 1 -- The address for a given argument; address 1 can be omitted
			if booleana then
				v = yesno(v)
			end
			if not argsb then
				argsb = {a = v}
				table.insert(index, b)
			elseif argsba then
				return error(i18nlang'error_duplicate'(a .. b))
			else
				argsba = 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' = 'class="hcA"|',
		'header midcell' = 'colspan="3" class="hmA"|',
		'body cell' = 'class="bcA"|',
		'body banner' = 'class="bbA" style="background-color:#',
	}
	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 noclearclass = (((_args.noclear or ) ~= ) and ' adjacent-stations-noclear' or )
	local wikitable = {)
	return table.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 pfuncName(args, frame)
	end
end
p.main = makeInvokeFunction('_main')
function p._color(args, frame)
	local data = args.data
	if args1 or data then
		data = data or getData(args1, true)
		if not data then return getColor(nil, args1, args2, args3, frame) end
		return getColor(data, nil, args2, args3)
	end
end
p.color = makeInvokeFunction('_color')
function p._box(args, frame)
	local system = args1 or args.system
	lineN = args2 or args.line
	if not (system or lineN) then return  end
	local line, Type, line_data
	local inline = args3 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
			local default = data'lines_default' or {}
			line, lineN = getLine(data, lineN)
			if typeN then
				typeN = data'aliases' and data'aliases'mw.ustring.lower(typeN) or typeN
				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(i18nlang'error_unknown'(lineN))
				line = line_data'title' or default'title' or error(i18nlang'error_missing'('title'))
				line = mw.ustring.gsub(line, '%%1', lineN)
			end
		else
			color = getColor(nil, system, lineN, typeN, frame)
			if inline ~= 'box' then
				line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, 'branch' = typeN} }
				if mw.text.trim(line) ==  then return error(i18nlang'error_unknown'(lineN)) end
			end
			Type = typeN
		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 = '  ' .. line .. result .. 
		elseif inline == 'yes' then
			result = '     ' .. line .. result
		elseif inline == 'box' then
			result = '    ' .. result
		elseif inline == 'link' then
			local link = args.link or mw.ustring.match(line, '%%(^%%+)%')
			if link then
				result = '    ' .. result
			else
				result = '    ' .. result
			end
		elseif inline == 'square' then
			result = '■ ' .. line .. result
		elseif inline == 'lsquare' then
			local link = args.link or mw.ustring.match(line, '%%(^%%+)%')
			if link then
				result = '■'
			else
				result = '■'
			end
		elseif inline == 'dot' then
			result = '● ' .. line .. result
		elseif inline == 'ldot' then
			local link = args.link or mw.ustring.match(line, '%%(^%%+)%')
			if link then
				result = '●'
			else
				result = '●'
			end
		elseif inline == 'small' then
			result = ' ' .. ' ' .. line .. result
		else
			local yesno = require("Module:Yesno")
			local link = args.link or mw.ustring.match(line, '%%(^%%+)%')
			local border_color, text_color
			local color_box = data'color box format' or data'rail box format' or {}
			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'
					if color_box == 'title' and not args4 then
						lineN = Type_data'short name' or line_data'short name' or require('Module:Delink')._delink{line}
					else
						lineN = Type_data'short name' or line_data'short name' or lineN
					end
				else
					border_color = line_data'border color' or color
					text_color = line_data'text color'
					if color_box == 'title' and not args4 then
						lineN = line_data'short name' or require('Module:Delink')._delink{line}
					else
						lineN = line_data'short name' or lineN
					end
				end
			else
				border_color = color
			end
			text_color = text_color and '#' .. text_color or require('Module:Color contrast')._greatercontrast{color}
			local bold = ';font-weight:bold'
			if (yesno(args.bold) == false) then bold =  end
			if inline == 'route' then -- Template:RouteBox
				if link then
					result =  .. lineN .. 
				else
					result =  .. lineN .. 
				end
			elseif inline == 'croute' then -- Template:Bahnlinie
				if link then
					result =  .. lineN .. 
				else
					result =  .. lineN .. 
				end
			elseif inline == 'xroute' then -- Template:Bahnlinie
				if link then
					result =  .. lineN .. 
				else
					result =  .. lineN .. 
				end
			elseif inline == 'broute' then
				if link then
					result =  .. lineN .. 
				else
					result =  .. lineN .. 
				end
			else -- Template:Legend (fallback; duplication to simplify logic)
				result = '  ' .. line .. result .. 
			end
		end
		result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')
		return result
	end
end
p.box = makeInvokeFunction('_box')
function p._icon(args, frame)
	local system = args1 or args.system
	local data = args.data
	if not system and not data then
		return
	end
	data = data or getData(system)
	local line, line_name = getLine(data, args2 or args.line)
	local icon
	local icon_format
	if line then
		local line_type = args3 or args.type
		if line_type then
			line_type = data.aliases and  data.aliasesmw.ustring.lower(line_type) or line_type
			line_type = line.types and line.typesline_type -- If there's no type table or entry for this type, then it can't have its own icon
			icon_format = line_type'icon format' or data'type icon format'
			if line_type.icon then
				icon = line_type.icon
			end
		end
		if not icon then
			icon = line.icon
		end
		-- Only if there is no icon use the icon_format.
		if not icon and not icon_format then
			icon_format = line'icon format' or data'line icon format'
		end
		local default = data.lines._default or {}
		if icon and string.find(icon, "%%1") and default and default.icon then
			icon = mw.ustring.gsub(default.icon, '%%1', line_name)
		end
	end
	if not icon then
		icon = data'system icon'
	end
	if not icon_format then
		icon_format = data'system icon format'
	end
	if icon_format then
		if icon_format ~= 'image' then
			icon = p._box({data = data, 2 = (args2 or args.line), 3 = icon_format, type = (args3 or args.type), bold = args.bold, link = args.link}, frame)
			if args.name then
				if line and line.title then
					return icon .. " " .. line.title
				end
				return icon .. " " .. data"system title"
			end
		end
	end
	local size = args.size
	if size then
		if mw.ustring.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 regex = {
			'|%s*%d*x?%d+px%s*(%|)', -- '|%s*upright=%d+%.?%d*%s*(%|)', '|%s*upright%s*(%|)'
		}
		if mw.ustring.match(icon, regex1) then
			icon = mw.ustring.gsub(icon, regex1, size .. '%1')
	--	elseif mw.ustring.match(icon, regex2) then
	--		icon = gsub(icon, regex2, size .. '%1')
	--	elseif mw.ustring.match(icon, regex3) then
	--		icon = gsub(icon, regex3, size .. '%1')
		else
			icon = mw.ustring.gsub(icon, '(%%^%|+)(%|)', '%1' .. size .. '%2')
		end
	end
	local link = args.link
	if link then
		if mw.ustring.match(icon, '|%s*link=^%|*%|') then
			icon = mw.ustring.gsub(icon, '|%s*link=^%|*(%|)', '|link=' .. link .. '%1')
		else
			icon = mw.ustring.gsub(icon, '(%%^%|+)(%|)', '%1|link=' .. link .. '%2')
		end
	end
	local alt = args.alt or link
	if alt then
		if mw.ustring.match(icon, '|%s*alt=^%|*%|') then
			icon = mw.ustring.gsub(icon, '|%s*alt=^%|*(%|)', '|alt=' .. alt .. '%1')
		else
			icon = mw.ustring.gsub(icon, '(%%^%|+)(%|)', '%1|alt=' .. alt .. '%2')
		end
	end
	if args.name then
		if line and line.title then
			return icon .. " " .. line.title
		end
		return icon .. " " .. data"system title"
	end
	return icon
end
p.icon = makeInvokeFunction('_icon')
function p._line(args, frame)
	local system = args1 or args.system
	local line = args2 or args.line
	if not line then return  end
	local Type = args3 or args.type
	local data = args.data
	if system or data then
		data = data or getData(system, true)
		if data then
			line = (getLine(data, line)) or error(i18nlang'error_unknown'(line))
			if Type then
				Type = data'aliases' and data'aliases'mw.ustring.lower(Type) or Type
				Type = line'types' and line'types'Type and line'types'Type'title' or Type
			end
			line = line'title' or error(i18nlang'error_missing'('title'))
		else
			line = frame:expandTemplate{ title = system .. ' lines', args = {line, 'branch' = Type} }
			if mw.text.trim(line) ==  then return error(i18nlang'error_unknown'(lineN)) end
		end
		if Type and Type ~=  then
			if line ==  then
				line = Type
			else
				line = line .. ' – ' .. Type
			end
		end
		return line
	end
end
p.line = makeInvokeFunction('_line')
function p._shortline(args, frame)
	local system = args1 or args.system
	lineN = args2 or args.line
	if not (system or lineN) then return  end
	local line, Type, line_data
	typeN = args.type
	local data = args.data
	if system or data then
		data = data or getData(system, true)
		if data then
			local default = data'lines_default' or {}
			line, lineN = getLine(data, lineN)
			if typeN then
				typeN = data'aliases' and data'aliases'mw.ustring.lower(typeN) or typeN
				Type = line'types' and line'types'typeN and line'types'typeN'title' or typeN
			end
			line_data = line or error(i18nlang'error_unknown'(lineN))
			line = line_data'title' or default'title' or error(i18nlang'error_missing'('title'))
			line = mw.ustring.gsub(line, '%%1', lineN)
		else
			line = frame:expandTemplate{ title = system .. ' lines', args = {lineN, 'branch' = typeN} }
			if mw.text.trim(line) ==  then return error(i18nlang'error_unknown'(lineN)) end
			Type = typeN
		end
		local result
		if Type and Type ~=  then
			if line ==  then
				line = Type
			else
				result = ' – ' .. Type
			end
		end
		if args.note then result = (result or ) .. ' ' .. args.note end
		result = result or 
		local link = args.link or mw.ustring.match(line, '%%(^%%+)%')
		if line_data then
			if line_data'types' and line_data'types'typeN then
				local Type_data = line_data'types'typeN
				lineN = Type_data'short name' or line_data'short name' or lineN
			else
				lineN = line_data'short name' or lineN
			end
		end
			
		if link then
			result =  .. lineN .. 
		else
			result = lineN
		end
	
		result = mw.ustring.gsub(result, ':%s*#transparent', ':transparent')
		return result
	end
end
p.shortline = makeInvokeFunction('_shortline')
function p._station(args, frame)
	local system = args1 or args.system
	local station = args2 or args.station
	if not station then return  end
	lineN = args3 or args.line
	typeN = args4 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'mw.ustring.lower(lineN) or lineN
					end
					if typeN then
						typeN = data'aliases'mw.ustring.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._terminusTable(args, frame)
	local system = args1 or args.system
	lineN = args2 or args.line
	local side = mw.ustring.sub(mw.ustring.lower(args3 or args.side or ), 1, 1)
	typeN = args.type
	local prefix = (side == 'r') and 'right' or 'left'
	local data = args.data
	if system or data then
		data = data or getData(system, true)
	end
	if data then
		local line = getLine(data, lineN) or error(i18nlang'error_unknown'(lineN))
		if typeN and data and data'aliases' then typeN = data'aliases'mw.ustring.lower(typeN) or typeN end
		local Type = line'types' and line'types'typeN
		local circular
		if Type then
			if Type'circular' then
				-- Type may override the circular status of the line
				circular = Type'circular'
			end
		else
			circular = line'circular'
		end
		return Type and Typeprefix .. ' terminus' or lineprefix .. ' terminus', data'station format' or i18nlang'error_format', circular
	else
		local terminus = frame:expandTemplate{ title = 'S-line/' .. system .. ' ' .. prefix .. '/' .. lineN }
		return mw.ustring.gsub(terminus, , typeN)
	end
end
function p._terminus(args, frame)
	local var, Format, circular = p._terminusTable(args, frame)
	return circular and var or getTerminusText(var, Format)
end
p.terminus = makeInvokeFunction('_terminus')
function p._style(args, frame)
	local style = args1 or args.style
	local system = args2 or args.system
	local line = args3 or args.line
	local station = args4 or args.station
	local result = {}
	local data = args.data
	local default = 'background-color:#efefef' -- Default background color for 
	if system or data then
		data = data or getData(system, true)
	end
	if data then
		local function getValue(var)
			if type(var) == 'table' then
				var = varline or var1
				if type(var) == 'table' then
					var = varstation or var1
				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
		if style == 'subheader' then
			result = default
		else
			result = 
		end
	end
	return result
end
function p.style(frame)
	local args = getArgs(frame, {frameOnly = true})
	return p._style(args, frame)
end
function p.convert(frame)
	local args = frame.args
	local code = mw.text.split(mw.ustring.gsub(args1, '^%s*%s*$', '%1'), '%s*}}%s*