Module:Routelist row: Difference between revisions

Content deleted Content added
sortkey -> data-sort-value
take sandbox live
Tag: Reverted
Line 1:
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 lang = mw.getContentLanguage() -- Built-in locale for date formatting
local routeStates = { } -- Table with route statuses.
local format = mw.ustring.format -- String formatting function
--[[ The following tables include the following entries:
local insert = table.insert
row: The start of the row, for this particular type (color)
local concat = table.concat
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.
local roadDataModule = require("Module:Road data")
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.
local util = require("Module:Road data/util")
]]--
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
 
local shieldScale = 1.25
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.
@type status
]]--
@field #string row: The start of the row, for this particular type (color)
if decommissioned == 'yes' then --If the decommissioned property just says "yes", then mark it as a former route and display default data.
@field #string established: The string to be output in the "Formed" column.
return routeStates.formeroverride
For future routes, "proposed" is displayed here.
elseif decommissioned then -- If the route is decommissioned, then it must be a former route.
Otherwise, display the year passed in the established parameter.
return routeStates.former
@field #string removed: The string to be output in the "Removed" column.
elseif not established then -- Without the establishment date, there is not enough information to determine the status of the route.
return In the case of routeStates.unknownformer, the year that the route was
decommissioned is output instead.
elseif established == 'proposed' then -- If the "established date" is the string 'proposed', then it must be a future route.
]]
return routeStates.future
--[[-
else -- If none of the first three conditions are true, then it must be a current route.
Route statuses.
return routeStates.current
@list <#status>
]]
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 #string established `established` argument passed to the module
@param #string decommissioned `decommissioned` argument passed to the module
@return #status the status of the route.
]]
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
 
--[[-
function getLength(args)
A limited replacement for {{dts}}.
-- This function is passed the length fields from the {{routelist row}} invocation, and calculates the missing length from the other.
Derive the sort key from a given date.
local math = require "Module:Math" -- This module contains functions needed later in this function.
@param #string date
local precision = math._precision -- The math._precision function provides the precision of a given string representing a number.
@param #string circa "yes" if `date` is tagged as circa
local round = math._precision_format -- This method rounds a given number to the given number of digits. In Lua, storing these functions locally results in more efficient execution.
@return #string true the hidden sort key, along with the year of the original date
local length = {} -- This table will store the computed lengths.
@return #boolean false if the sort key cannot be derived
local km = args["length_km"] -- The kilometer length from the {{routelist row}} call.
]]
local mi = args["length_mi"] -- The length in miles as passed to {{routelist row}}.
local function dtsYearCore(date)
if not km then -- This signifies that a length in kilometers was not passed.
local year = lang:formatDate('Y', date) -- year for this date
local n = tonumber(mi) -- The first step is to convert the miles (passed as a string from the template) into a number.
if year == date then -- If the provided date is just the year,
if n then -- If the passed mile value is an empty string, n will equal nil, which would make this statement false. Otherwise, the length in kilometers is computed and stored.
-- tack on January 1 for the sort key to work right.
local ten_mult = (n % 10 == 0) -- Rounding is handled differently if the input distance is a multiple of 10.
date = date .. "-01-01"
local prec = precision(mi) -- Retrieve the precision of the passed mile value (as a string).
if ten_mult and prec < 0 then -- If the distance is a multiple of 10 and a whole number...
prec = prec + 1 -- Add a digit to the precision.
end
length.km = round(tostring(n * 1.609344), tostring(prec)) -- Compute and round the length in kilometers, and store it in the length table.
else -- No mile value was passed
length.km = '—'
end
else -- If the length in kilometers was passed, the computed lengths table will simply contain the passed length.
local prec = precision(km)
length.km = round(km, tostring(prec))
end
if not mi then -- The same as above, but this time converting kilometers to mile if necessary.
local n = tonumber(km) -- Kilometers as a number
if n then -- If a kilometer value was passed:
local ten_mult = (n % 10 == 0) -- Rounding is handled differently if the input distance is a multiple of 10.
local prec = precision(km) -- Precision of the passed length
if ten_mult and prec < 0 then -- If the distance is a multiple of 10 and a whole number...
prec = prec + 1 -- Add a digit to the precision
end
length.mi = round(tostring(n / 1.609344), tostring(prec)) -- Compute and store the conversion into miles.
else -- If not:
length.mi = '—' -- Store a dash.
end
else -- And if the length in miles was passed:
local prec = precision(mi) -- Get the precision...
length.mi = round(mi, tostring(prec)) -- and format it appropriately
end
local month = lang:formatDate('m', date) -- month for this date
return length -- Return the length table with the computed lengths.
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 dtsYearCoredtsYear(date, circa)
local success, result = pcall(dtsYearCore, date)
-- 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 success then
if not date then return false end -- If the date is an empty string, stop and go back to whence it came.
result = {
local year = lang:formatDate('Y', date) -- This invocation of lang:formatDate returns just the year.
"00001-01-01",
if year == date then -- If the provided date is just the year:
util.err(format('Invalid date "%s".', date))
date = date .. "-01-01" -- Tack on January 1 for the sort key to work right.
}
end
-- Generate the HTML code necessary for the hidden sort key.
local month = lang:formatDate('m', date) -- Stores the month of the date.
local dtsStyle = format("style=\"white-space:nowrap;\" data-sort-value=\"%s\"", result[1])
local day = lang:formatDate('d', date) -- Stores the day for this date.
local year = result[2]
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;\">&thinsp;" .. year .. "</span>" -- Addadd the circa abbreviation to the display. Derived from {{circa}}.
year = "<span style=\"white-space:nowrap;\"><abbr title=\"circa\">c.</abbr>&thinsp;" .. year .. "</span>"
else -- Otherwise,
return dtsSpan .. year -- Return the hidden sort key concatenated with the year for this date.
end
return dtsStyle, year
end
 
--- Return formatting and output for a date column.
function dtsYear(date, circa)
local success,function result = pcalldate(dtsYearCoretext, date, circa, ref)
-- Returns the text if specified, or the dtsYear-formatted date, and an em-dash.
if success then
local style, output
return result
if text then
output = text
elseif date then
style, output = dtsYear(date, circa)
else
output = "—"
return string.format('%s<span class="error">Error: Invalid date "%s".</span>', circa and '<abbr title="circa">c.</abbr>&thinsp;' or '', date)
end
return format("|align=center %s|%s%s", style or "", output, ref)
end
 
--- Return output for the date columns for a given route.
function removed(routeState, decommissioned, circa)
local function dates(established, decommissioned, routeStatus, args)
-- This function returns the proper value for the removed column.
local established_ref = args.established_ref or '' -- Reference for date established
return routeState.removed or dtsYear(decommissioned, circa) -- Returns the removed attribute of the provided routeState table or, if empty, the dtsYear-formatted decommissioned date.
local decommissioned_ref = args.decommissioned_ref or '' -- Reference for date decommissioned
end
return format("%s\n%s",
 
date(routeStatus.established, established, args.circa_established, established_ref),
function formed(routeState, established, circa)
date(routeStatus.removed, decommissioned, args.circa_decommissioned, decommissioned_ref))
-- 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
 
--- Return output for the termini columns for a given route.
function termini(args)
local 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
-- The given route is a beltway.
return "|colspan=2 align=center|" .. beltway -- This text will, again, span both columns.
-- `beltway` text will span both termini columns.
return "|colspan=2 align=center|" .. beltway
else
returnlocal '|'terminus_a ..= args["terminus_a"] ..or '||' .. terminus_b -- Fill inSouthern theor terminiwestern columnsterminus
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.
function dates(established, decommissioned, routeState, args)
-- Thislocal function displays the date columns.length(args)
local established_refmi = args.established_ref or ''["length_mi"] -- Reference forLength datein establishedmiles
local decommissioned_refkm = args.decommissioned_ref or ''["length_km"] -- Reference forLength datein decommissionedkilometers
-- Convert lengths.
return "|align=center|" .. formed(routeState, established, args.circa_established) ..
local lengths = util.convertLengths({mi = mi, km = km}, '—')
established_ref .. "||align=center|" .. removed(routeState, decommissioned, args.circa_decommissioned) ..
decommissioned_ref
end
 
function length(args)
-- This function generate the length columns, with the appropriate conversions.
local miles = args["length_mi"] -- Length in miles
local kilometers = args["length_km"] -- Length in kilometers
local lengths = {length_mi = miles, length_km = kilometers} -- This time, we compile the lengths into a table,
local Lengths = getLength(lengths) -- which makes for an easy parameter. This function call will return the lengths in both miles and kilometers,
local lengthRef = args["length_ref"] or ''
return format("|align=right|%s%s%s||align=right|%s",
local first, second
lengths[lengths.orig], lengthRef, lengths.error or "", lengths[lengths.comp])
if kilometers then
first = Lengths.km
second = Lengths.mi
else
first = Lengths.mi
second = Lengths.km
end
return "|align=right|" .. first .. lengthRef .. "||align=right|" .. second -- which are then spliced in here and returned to the template.
end
 
--- Generate a "Local names" cell if necessary.
function localname(args)
local function localname(args)
-- This function generates a "Local names" cell if necessary
local enabled = args[1] or ''
localif localNameenabled == args["local"] or ''then
if local mw.text.trim(enabled)localName == args["local"] or then''
return "|" .. localName
else
Line 186 ⟶ 181:
end
 
--- Generate a "Notes" cell if necessary.
function notes(notes)
local function notes(notes)
-- This function generates a "Notes" cell if necessary.
if notes == 'none' then
return '| ' --create empty cell
Line 197 ⟶ 192:
end
 
--- Derive the sort key from a given route.
function route(args)
-- Thislocal function displays the shield and link.sortkey(abbr)
-- Split `abbr` into three possibly empty parts, with number in the middle.
local format = mw.ustring.format
local prefix, num, suffix = mw.ustring.match(abbr, "([^0-9]*)(%d*)(.*)")
local parserModule = require "Module:Road data/parser"
-- If `abbr` does not contain a number, the entry appears at the bottom.
local parser = parserModule.parser
num = tonumber(num)
num = type(num) == "number" and format("%04d", num) or ""
local noshield = args.noshield
-- The sort key is `abbr`, but with route number zero-padded to 4 digits
local bannerFile = parser(args, 'banner')
-- and prefix moved to the end.
local banner
return mw.text.trim(
if not noshield and bannerFile and bannerFile ~= '' then
mw.ustring.gsub(format("%s%s %s", num, suffix, prefix), "&nbsp;", " "),
local widthCode = parser(args, 'width') or 'square'
"- ")
if widthCode == 'square' then
end
banner = format("[[File:%s|25px|link=|alt=]]", bannerFile)
elseif widthCode == 'expand' then
local route = args.route
if #route >= 3 then
banner = format("[[File:No image.svg|3px|link=|alt=]][[File:%s|25px|link=|alt=]][[File:No image.svg|3px|link=|alt=]]", bannerFile)
else
banner = format("[[File:%s|25px|link=|alt=]]", bannerFile)
end
elseif widthCode == 'wide' then
banner = format("[[File:No image.svg|3px|link=|alt=]][[File:%s|25px|link=|alt=]][[File:No image.svg|3px|link=|alt=]]", bannerFile)
elseif widthCode == 'MOSupp' then
local route = args.route
if #route >= 2 then
banner = format("[[File:No image.svg|3px|link=|alt=]][[File:%s|25px|link=|alt=]][[File:No image.svg|3px|link=|alt=]]", bannerFile)
else
banner = format("[[File:%s|25px|link=|alt=]]", bannerFile)
end
elseif widthCode == 'US1926' then
banner = format("[[File:%s|25px|link=|alt=]][[File:No image.svg|1px|link=|alt=]]", bannerFile)
elseif args.state == 'CA' then
local route = args.route
local type = args.type
if type == 'US-Bus' then
if #route >= 3 then
banner = format("[[File:No image.svg|2px|link=|alt=]][[File:%s|25px|link=|alt=]][[File:No image.svg|2px|link=|alt=]]", bannerFile)
else
banner = format("[[File:%s|25px|link=|alt=]]", bannerFile)
end
elseif type == 'CA-Bus' or type == 'SR-Bus' then
if #route >= 3 then
banner = format("[[File:No image.svg|1px|link=|alt=]][[File:%s|25px|link=|alt=]][[File:No image.svg|2px|link=|alt=]]", bannerFile)
else
banner = format("[[File:%s|24px|link=|alt=]]", bannerFile)
end
end
end
banner = banner .. '<br>'
else
banner = ''
end
 
--- Return output for displaying the shield and link for a given route.
local shield
local function route(args)
if not noshield then
local shieldFile, secondshield = parserroadDataModule.shield(args, 'shield'shieldScale)
local link, abbr = roadDataModule.link(args)
if type(shieldFile) == 'table' then
-- Use the sort key if already specified.
shieldFile, second = shieldFile[1], shieldFile[2]
local sortkey = args.sortkey or sortkey(abbr or "")
end
return format('!scope="row" class="nowrap" data-sort-value="%s"|%s %s',
if second and type(second) == 'string' then
sortkey, shield, link)
local shield1 = format("[[File:%s|x25px|alt=|link=]]", shieldFile)
local shield2 = format("[[File:%s|x25px|alt=|link=]]", second)
shield = shield1 .. shield2
else
shield = shieldFile and format("[[File:%s|x25px|alt=|link=]]", shieldFile) or ''
end
else
shield = ''
end
local linkTarget = (not args.nolink) and parser(args, 'link')
local abbr = parser(args, 'abbr')
local link
if linkTarget then
link = format("[[%s|%s]]", linkTarget, abbr)
else
link = abbr
end
if not link then error("Type not in database: " .. args.type) end
local sortkey = sortkey(args)
local sortedLink = format("<span data-sort-value=\"%s&#32;!\">%s</span>", sortkey, link)
local route = banner .. shield .. ' ' .. sortedLink
return '!scope="row" class="nowrap"|' .. route
end
 
--- Derive the anchor from a given route.
function p.row(frame)
local function anchor(routeType, routeNo)
local args = getArgs(frame) -- Gather passed arguments into easy-to-use table
-- 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 routeStaterouteStatus = getRouteStategetRouteStatus(established, decommissioned)
local anchor = args.anchor or sortkeyanchor(args.type, args.route)
local rowdef = routeState.row .. string.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, routeStaterouteStatus, args)
local notesArgnotes = notes(args.notes)
 
local notes = notes(notesArg)
local row = {rowdef, route, length, termini, localname, dates, notes}
return table.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