Module:Routelist row/sandbox
Jump to navigation
Jump to search
__EXPECTUNUSEDTEMPLATE__This is the module sandbox page for Module:Routelist row (diff). |
local p = { } -- Package to be exported
local getArgs = require('Module:Arguments').getArgs -- Import module function to work with passed arguments
local lang = mw.getContentLanguage() -- Retrieve built-in locale for date formatting
local frame = mw.getCurrentFrame()
-- Change to "" upon deployment.
local moduleSuffix = "/sandbox"
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
local util = require("Module:Road data/util")
local routeStates = { } -- Table with route statuses.
--[[ The following tables include the following entries:
row: The start of the row, for this particular type (color)
established: The string to be output in the "Formed" column. For future routes, "proposed" is displayed here. Otherwise, display the year passed in the established parameter.
removed: The string to be output in the "Removed" column. In the case of routeStates.former, the year that the route was decommissioned is output instead.
]]--
routeStates.current = {row = "|-", removed = "current"} -- Data for current routes
routeStates.future = {row = '|- style="background-color:#ffdead;" title="Future route"', established = "proposed", removed = "—"} -- Data for future routes
routeStates.former = {row = '|- style="background-color:#d3d3d3;" title="Former route"'} -- Data for former routes
routeStates.formeroverride = {row = '|- style="background-color:#d3d3d3;" title="Former route"', removed = "—"} -- Data for routes marked as former by override
routeStates.unknown = {row = "|-", removed = "—"} -- Data for route with unknown status
routeStates.proposed = {row = '|- style="background-color:#ffdead;" title="Proposed route"', established = "–", removed = "—"} -- Data for proposed routes
function getRouteState(established, decommissioned)
--[[ This function is passed the dates given for the established and decommissioned fields to the template.
It then returns the entry in the routeStates table corresponding to the status of the route.
]]--
if decommissioned == 'yes' then --If the decommissioned property just says "yes", then mark it as a former route and display default data.
return routeStates.formeroverride
elseif decommissioned then -- If the route is decommissioned, then it must be a former route.
return routeStates.former
elseif not established then -- Without the establishment date, there is not enough information to determine the status of the route.
return routeStates.unknown
elseif established == '~~' then
return routeStates.proposed
elseif established == 'proposed' then
return routeStates.future
else -- If none of the first three conditions are true, then it must be a current route.
return routeStates.current
end
end
function dtsYearCore(date, circa)
-- A limited replacement for {{dts}}. This is passed a date and derives a sort key from it. It returns a string with the hidden sort key, along with the year of the original date.
if not date then return false end -- If the date is an empty string, stop and go back to whence it came.
local year = lang:formatDate('Y', date) -- This invocation of lang:formatDate returns just the year.
if year == date then -- If the provided date is just the year:
date = date .. "-01-01" -- Tack on January 1 for the sort key to work right.
end
local month = lang:formatDate('m', date) -- Stores the month of the date.
local day = lang:formatDate('d', date) -- Stores the day for this date.
local dtsStr = string.format("%05d-%02d-%02d", year, month, day) -- Create and store the formatted hidden sort key. The year must be five digits, per convention.
local spanParams = {style = "display:none; speak:none"} -- These CSS properties hide the sort key from normal view.
local dtsSpan = mw.text.tag({name='span', content=dtsStr, attrs=spanParams}) -- This generates the HTML code necessary for the hidden sort key.
if circa == 'yes' then -- If the date is tagged as circa,
return dtsSpan .. "<abbr title=\"circa\">c.</abbr><span style=\"white-space:nowrap;\"> " .. year .. "</span>" -- Add the circa abbreviation to the display. Derived from {{circa}}
else -- Otherwise,
return dtsSpan .. year -- Return the hidden sort key concatenated with the year for this date.
end
end
function dtsYear(date, circa)
local success, result = pcall(dtsYearCore, date, circa)
if success then
return result
else
return string.format('%s<span class="error">Error: Invalid date "%s".</span>', circa and '<abbr title="circa">c.</abbr> ' or '', date)
end
end
function removed(routeState, decommissioned, circa)
-- This function returns the proper value for the removed column.
return routeState.removed or dtsYear(decommissioned, circa) -- Returns the removed attribute of the provided routeState table or, if empty, the dtsYear-formatted decommissioned date.
end
function formed(routeState, established, circa)
-- This function returns the proper value for the formed column.
return routeState.established or dtsYear(established, circa) or "—" -- Returns 'proposed' if the route is proposed, the dtsYear-formatted established date if one was provided, or an em-dash.
end
function sortkey(args)
-- This function return the sort key for the route (not to be confused with the previous function, which generates a sort key for the established and decommissioned dates.)
local key = args.sortkey
local type = args.type
local route = args.route or ''
if key then -- If a sort key already exists:
return key -- Simply return it.
else -- Otherwise:
local routeKey
local routeNum = tonumber(route)
if routeNum then
routeKey = string.format('%04d', route) -- This invocation is equivalent to the {{0000expr}} template. It zero-pads the given route number up to 4 digits.
else
local num, suffix = string.match(route, "(%d*)(.+)")
routeKey = (tonumber(num) and string.format('%04d', num) or '') .. suffix
end
return type .. routeKey -- Return the sort key for this route, composed of the type and zero-padded route number.
end
end
function termini(args)
-- This function determines if this is a beltway or not, and displays the termini columns appropriately.
local beltway = args["beltway"] -- Text in this parameter will span both termini columns.
local terminus_a = args["terminus_a"] or '—' -- Southern or western terminus
local terminus_b = args["terminus_b"] or '—' -- Northern or eastern terminus
if beltway then
return "|colspan=2 align=center|" .. beltway -- This text will, again, span both columns.
else
return '|' .. terminus_a .. '||' .. terminus_b -- Fill in the termini columns
end
end
function dates(established, decommissioned, routeState, args)
-- This function displays the date columns.
if args.gazette == 'yes' then
local established = args.established or "—"
local established_ref = args.established_ref or ''
return "|align=center|" .. established .. established_ref
else
local established_ref = args.established_ref or '' -- Reference for date established
local decommissioned_ref = args.decommissioned_ref or '' -- Reference for date decommissioned
return "|align=center|" .. formed(routeState, established, args.circa_established) ..
established_ref .. "||align=center|" .. removed(routeState, decommissioned, args.circa_decommissioned) ..
decommissioned_ref
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("|align=right|") .. mi .. ref .. format("||align=right|") .. frame:expandTemplate{ title = 'convert', args = { mi, "mi", "km", disp = "output number only"}}
else
return format("|align=right|") .. km .. ref .. format("||align=right|") .. frame:expandTemplate{ title = 'convert', args = { km, "km", "mi", disp = "output number only"}}
end
end
function localname(args)
-- This function generates a "Local names" cell if necessary
local enabled = args[1] or ''
local localName = args["local"] or ''
if mw.text.trim(enabled) == "local" then
return "|" .. localName
else
return ''
end
end
function notes(notes)
-- This function generates a "Notes" cell if necessary.
if notes == 'none' then
return '| ' --create empty cell
elseif notes then
return '|' .. notes --display notes in cell
else
return '' --create no cell
end
end
function gap(args)
local text = args.text or "''Number not designated''"
if notes then
return '|align=center colspan=7|' .. text --display notes in cell
else
return '|align=center colspan=6|' .. text --display notes in cell
end
end
local defaultShieldSize = 30
local function addContextBanner(route, name, suffix, bannerSpec)
local bannerModule = 'Module:Road data/banners/' .. string.upper(route.country)
local shieldfield = name .. 'shield'
local shield = parser(route, shieldfield)
if shield == nil then
-- This route type does not define shield.
-- Find shield in the default banner table.
shield = parser(route, 'shield', name, bannerModule)
if shield and shield ~= '' then
if suffix == nil then
suffix = parser(route, '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
if route.dir then
addContextBanner(route, 'dir', bannerSuffix, banners)
end
if route.to then
addContextBanner(route, 'to', bannerSuffix, banners)
end
return banners
end
local function shieldSpec(route, mainShield, shieldList)
local shieldSpec = {}
local shield
local shield = parser(route, 'shieldlist') or parser(route, 'shield') or ''
if shield == '' then return shieldSpec end
local orientation = parser(route, 'orientation')
local function size(route)
if orientation == "upright" then
return defaultShieldSize
else return "x" .. defaultShieldSize
end
end
local shieldsize = size(route)
local banner = parser(route, 'banner') or {}
local bannersize = defaultShieldSize
local bannersuffix = parser(route, '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 or shieldsize[i]
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("[[File:%s|%spx|link=|alt=]]", shield[1], size)
if not banners[1] then return shieldCode end
for _,banner in ipairs(banners) do
shieldCode = format("[[File:%s|%spx|link=|alt=]]<br>%s",
banner[1],
defaultShieldSize,
shieldCode)
end
return '<span style="display: inline-block; vertical-align: baseline; line-height: 0; text-align: center;">' .. shieldCode .. '</span>'
end
function p.shield(route, scale, showLink, mainShield, shieldList)
local noshield = route.noshield
missingShields = {}
scale = scale or 1
local rendered = {}
for _,entry in ipairs(shieldSpec(route, mainShield, shieldList)) do
insert(rendered, render(entry, scale, showLink))
end
if noshield then return '' end
return concat(rendered), missingShields
end
function p.link(route)
local nolink = route.nolink
local abbr, errMsg = parser(route, 'abbr')
if not abbr then
route.typeerror = true
return util.err(errMsg or format("Invalid type: %s", route.type or "(nil)"))
end
if nolink then return abbr, abbr end
local link = parser(route, 'link') or ''
if link == '' then return abbr, abbr end
return format("[[%s|%s]]", link, abbr), abbr
end
function route(args)
local shield = p.shield(args)
local link = p.link(args)
local sortkey = sortkey(args)
local sortedLink = format("<span data-sort-value=\"%s !\">%s</span>", sortkey, link)
return '!scope="row" class="nowrap"|' .. shield .. ' ' .. sortedLink
end
function p.row(frame)
local args = getArgs(frame) -- Gather passed arguments into easy-to-use table
local established = args.established
local decommissioned = args.decommissioned
local routeState = getRouteState(established, decommissioned)
local anchor = args.anchor or sortkey(args)
local rowdef = routeState.row .. string.format(' id="%s"', anchor)
local route = route(args)
local length = length(args)
local termini = termini(args)
local localname = localname(args)
local dates = dates(established, decommissioned, routeState, args)
local notesArg = args.notes
local notes = notes(notesArg)
local row = {rowdef, route, length, termini, localname, dates, notes}
return table.concat(row, '\n')
end
function p.gap(frame)
local args = getArgs(frame) -- Gather passed arguments into easy-to-use table
local routeState = getRouteState(established, decommissioned)
local anchor = args.anchor or sortkey(args)
local rowdef = routeState.row .. string.format(' id="%s"', anchor)
local route = route(args)
local gap = gap(args)
local row = {rowdef, route, gap}
return table.concat(row, '\n')
end
return p