Module:Routelist row/sandbox 2

local p = {} -- Package to be exported

-- Change to "" upon deployment. local moduleSuffix = ""

local lang = mw.getContentLanguage -- Built-in locale for date formatting local format = mw.ustring.format -- String formatting function local insert = table.insert local concat = table.concat local util = require("Module:Road data/util") local frame = mw.getCurrentFrame

local parserModuleName = "Module:Road data/parser" .. moduleSuffix local statenameModuleName = "Module:Jct/statename" .. moduleSuffix -- TODO transition

local concat = table.concat local insert = table.insert local format = mw.ustring.format local trim = mw.text.trim

local parserModule = require(parserModuleName) local parser = parserModule.parser

-- Shields local defaultShieldSize = 28

local function addContextBanner(args, name, suffix, bannerSpec) local bannerModule = 'Module:Road data/banners/' .. string.upper(args.country) local shieldfield = name .. 'shield' local shield = parser(args, shieldfield) if shield == nil then -- This route type does not define shield. -- Find shield in the default banner table. shield = parser(args, 'shield', name, bannerModule) if shield and shield ~= '' then if suffix == nil then suffix = parser(args, 'shield', 'suffix', bannerModule) end if suffix and suffix ~= '' then shield = shield .. " " .. suffix end shield = shield .. ".svg" end end if shield and shield ~= '' then local shieldSize = defaultShieldSize -- Add banner plate. insert(bannerSpec, {shield, shieldSize}) end end

local function bannerSpec(banner, bannerSize, bannerSuffix, route) local banners = {} if type(banner) == "table" then local bannerSizeIsNotTable = type(bannerSize) ~= "table" for i,filename in ipairs(banner) do			local bannersize = bannerSizeIsNotTable and bannerSize or bannerSize[i] or defaultShieldSize insert(banners, {filename, bannersize}) end elseif banner ~= '' then insert(banners, {banner, bannerSize}) end

return banners end

local function shieldSpec(args, mainShield, shieldList) local shieldSpec = {}

local shield

if not shield then shield = parser(args, 'shieldlist') or parser(args, 'shield') or '' end if shield == '' then return shieldSpec end local orientation = parser(args, 'orientation')

local function size(args) if orientation == "upright" then return defaultShieldSize else return "x" .. defaultShieldSize end end local shieldsize = size(args) local banner = parser(args, 'banner') or {} local bannersize = defaultShieldSize local bannersuffix = parser(args, 'bannersuffix')

local bannerIsNotTable = type(banner) ~= "table" local bannersizeIsNotTable = type(bannersize) ~= "table" local bannersuffixIsNotTable = type(bannersuffix) ~= "table"

if type(shield) == "table" then for i,filename in ipairs(shield) do			local size = shieldsize[i] or shieldsize if size == "" then size = nil end -- banner.all describes banners that apply to all multiple shields. local shieldBanner = bannerIsNotTable and banner or (banner[i] or banner.all or {}) -- Banner size is default if the corresponding entry -- in bannerSize table is not set. local shieldBannerSize = bannersizeIsNotTable and bannersize or (bannersize[i] or bannersize.all or defaultShieldSize) local shieldBannerSuffix = bannersuffix and (bannersuffixIsNotTable and bannersuffix or bannersuffix[i]) insert(shieldSpec, {				shield = {filename, size},				banners = bannerSpec(shieldBanner, shieldBannerSize, shieldBannerSuffix, route)			}) end elseif shield ~= '' then if shieldsize == "" then shieldsize = nil end insert(shieldSpec, {			shield = {shield, shieldsize},			banners = bannerSpec(banner, bannersize, bannersuffix, route)		}) end

return shieldSpec end

local missingShields

local shieldExistsCache = {}

local function render(shieldEntry, scale, showLink) local shield = shieldEntry.shield local banners = shieldEntry.banners

local size if shield[2] then local width, height = mw.ustring.match(shield[2], "(%d*)x?(%d*)") width = tonumber(width) height = tonumber(height) local sizeparts = {} if width then insert(sizeparts, format("%d", width * scale)) end if height then insert(sizeparts, format("x%d", height * scale)) end size = concat(sizeparts) else size = format("%s%d", landscape and "x" or "", defaultShieldSize * scale) end local shieldCode = format("", shield[1], size) if not banners[1] then return shieldCode end

for _,banner in ipairs(banners) do		shieldCode = format(" %s",			banner[1],			defaultShieldSize,			shieldCode) end return ' ' .. shieldCode .. ' ' end

function p.shield(args, scale, showLink, mainShield, shieldList) missingShields = {}

scale = scale or 1

local rendered = {} for _,entry in ipairs(shieldSpec(args, mainShield, shieldList)) do		insert(rendered, render(entry, scale, showLink)) end return concat(rendered), missingShields end

-- Links/abbreviations function p.link(args) local nolink = args.nolink local abbr = parser(args, 'abbr') if nolink then return abbr else local link = parser(args, 'link') if not link or link == '' then return abbr else return format("%s", link, abbr) end end end

local function stateName(args) -- TODO transition local data = mw.loadData(statenameModuleName) local abbr = args.state or args.province local countryData = data[args.country] return countryData and countryData[abbr] end

-- --- @type status @field --- Route statuses. @list < local routeStatuses = { -- current routes current = { row = "|-", removed = "current" },	-- future routes future = { row = '|- style="background-color:#ffdead;" title="Future route"', established = "proposed", removed = "—" },	-- former routes former = { row = '|- style="background-color:#d3d3d3;" title="Former route"' },	-- routes marked as former by override -- deprecated formeroverride = { row = '|- style="background-color:#d3d3d3;" title="Former route"', removed = "—" },	-- route with unknown status unknown = { row = "|-", removed = "—" } }

--- Return the route status. @param local function getRouteStatus(established, decommissioned) if decommissioned == 'yes' then -- a former route with no decommission information return routeStatuses.formeroverride elseif decommissioned then -- If the route is decommissioned, then it must be a former route. return routeStatuses.former elseif not established then -- Without the establishment date, there is not enough information -- to determine the status of the route. return routeStatuses.unknown elseif established == 'proposed' then -- a future route return routeStatuses.future else -- a current route return routeStatuses.current end end

--- A limited replacement for . Derive the sort key from a given date. @param local function dtsYearCore(date) local year = lang:formatDate('Y', date) -- year for this date if year == date then -- If the provided date is just the year, -- tack on January 1 for the sort key to work right. date = date .. "-01-01"	end local month = lang:formatDate('m', date) -- month for this date local day = lang:formatDate('d', date) -- day for this date -- Create and store the formatted hidden sort key. -- The year must be five digits, per convention. local dtsStr = format("%05d-%02d-%02d", year, month, day) -- Return the hidden sort key and the year for this date. return {dtsStr, year} end

local function dtsYear(date, circa) local success, result = pcall(dtsYearCore, date) if not success then result = { "00001-01-01",			util.err(format('Invalid date "%s".', date)) }	end -- Generate the HTML code necessary for the hidden sort key. local dtsStyle = format("style=\"white-space:nowrap;\" data-sort-value=\"%s\"", result[1]) local year = result[2] if circa == 'yes' then -- If the date is tagged as circa, -- add the circa abbreviation to the display. Derived from c.. year = "c. &thinsp;" .. year .. " "	end return dtsStyle, year end

--- Return formatting and output for a date column. local function date(text, date, circa, ref) -- Returns the text if specified, or the dtsYear-formatted date, and an em-dash. local style, output if text then output = text elseif date then style, output = dtsYear(date, circa) else output = "—" end return format("|align=center %s|%s%s", style or "", output, ref) end

--- Return output for the date columns for a given route. local function dates(established, decommissioned, routeStatus, args) local established_ref = args.established_ref or '' -- Reference for date established local decommissioned_ref = args.decommissioned_ref or '' -- Reference for date decommissioned return format("%s\n%s",		date(routeStatus.established, established, args.circa_established, established_ref),		date(routeStatus.removed, decommissioned, args.circa_decommissioned, decommissioned_ref)) end

--- Return output for the termini columns for a given route. local function termini(args) local beltway = args["beltway"] if beltway then -- The given route is a beltway. -- `beltway` text will span both termini columns. return "|colspan=2 align=center|" .. beltway else local terminus_a = args["terminus_a"] or '—' -- Southern or western terminus local terminus_b = args["terminus_b"] or '—' -- Northern or eastern terminus -- Fill in the termini columns return '|' .. terminus_a .. '||' .. terminus_b end end

--- Return output for the length columns for a given route, with the appropriate conversions. local function length(args) local km = args["length_km"] or '' -- Length in kilometers local mi = args["length_mi"] or '' -- Length in miles local ref = args["length_ref" ] or ''

if mi ==  and km ==  then return format("|align=right|—||align=right|—") elseif mi ~= '0' and km == '' then return format("|") .. frame:expandTemplate{ title = 'convert', args = { mi, "mi", "km", disp = "table"}} else return format("|") .. frame:expandTemplate{ title = 'convert', args = { km, "km", "mi", disp = "table"}} end end

--- Generate a "Local names" cell if necessary. local function localname(args) local enabled = args[1] or '' if enabled == "local" then local localName = args["local"] or '' return "|" .. localName else return '' end end

--- Generate a "Notes" cell if necessary. local function notes(notes) if notes == 'none' then return '| ' --create empty cell elseif notes then return '|' .. notes --display notes in cell else return '' --create no cell end end

--- Derive the sort key from a given route. local function sortkey(abbr) -- Split `abbr` into three possibly empty parts, with number in the middle. local prefix, num, suffix = mw.ustring.match(abbr, "([^0-9]*)(%d*)(.*)") -- If `abbr` does not contain a number, the entry appears at the bottom. num = tonumber(num) num = type(num) == "number" and format("%04d", num) or "" -- The sort key is `abbr`, but with route number zero-padded to 4 digits -- and prefix moved to the end. return mw.text.trim(		mw.ustring.gsub(format("%s%s %s", num, suffix, prefix), " ", " "),		"- ") end

local function route(args, shieldSize) local link, abbr = p.link(args) -- Use the sort key if already specified. local sortkey = args.sortkey or sortkey(abbr or "") local shield = p.shield(args)

if shield == nil or args.noshield then return format('!scope="row" class="nowrap" data-sort-value="%s"|%s',			sortkey, link) else return format('!scope="row" class="nowrap" data-sort-value="%s"|%s %s',			sortkey, shield, link) end end

--- Derive the anchor from a given route. local function anchor(routeType, routeNo) -- Split `routeNo` into three possibly empty parts, with number in the middle. local prefix, num, suffix = mw.ustring.match(routeNo, "([^0-9]*)(%d*)(.*)") -- Zero-pad route number to 4 digits if `routeNo` does contain a number. num = tonumber(num) num = type(num) == "number" and format("%04d", num) or "" -- The anchor is the concatenation of `type` and zero-padded `routeNo`. return format("%s%s%s%s", routeType, prefix, num, suffix) end

function p._row(args) local established = args.established local decommissioned = args.decommissioned local routeStatus = getRouteStatus(established, decommissioned) local anchor = args.anchor or anchor(args.type, args.route) local rowdef = format('%s id="%s"', routeStatus.row, anchor) local route = route(args) local length = length(args) local termini = termini(args) local localname = localname(args) local dates = dates(established, decommissioned, routeStatus, args) local notes = notes(args.notes)

local row = {rowdef, route, length, termini, localname, dates, notes} return concat(row, '\n') end

function p.row(frame) -- Import module function to work with passed arguments local getArgs = require('Module:Arguments').getArgs local args = getArgs(frame) -- Gather passed arguments into easy-to-use table return p._row(args); end

return p