Module:Road data

local p = {}

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

local parserModuleName = "Module:Road data/parser" .. moduleSuffix local statenameModuleName = "Module:Jct/statename/sandbox" -- 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")

-- Shields local defaultShieldSize = 20

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 shieldsizefield = name .. 'shieldsize' local shieldSize = parser(route, shieldsizefield) if shieldSize == nil then -- This route type does not define shield size. -- Find shield size in the default banner table. shieldSize = parser(route, 'shieldsize', name, bannerModule) end if shieldSize == nil then shieldSize = "20" end -- 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) local shieldSpec = {}

local shield if mainShield then shield = parser(route, "shieldmain") end if not shield then shield = parser(route, 'shield') or '' end if shield == '' then return shieldSpec end

local shieldsize if mainShield then shieldsize = parser(route, "shieldmainsize") end if not shieldsize then shieldsize = parser(route, "shieldsize") or "" end local shieldsizeIsNotTable = type(shieldsize) ~= "table"

local banner = parser(route, 'banner') or {} local bannersize = parser(route, 'bannersize') or 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 = shieldsizeIsNotTable and 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 = {}

-- Return up to two booleans. -- The first boolean is false if `shield` does not exist, and true otherwise. -- If the first boolean is true, the second boolean is true if the shield is -- landscape (width >= height), and false otherwise. local function shieldExists(shield) local result = shieldExistsCache[shield] if result == nil then local file = mw.title.new(shield, 'Media').file -- Cache result. local exists = file.exists result = {exists} if exists then result[2] = file.width >= file.height end shieldExistsCache[shield] = result end if result[1] then return true, result[2] end insert(missingShields, shield) return false end

local function render(shieldEntry, scale, showLink) local shield = shieldEntry.shield local banners = shieldEntry.banners local exists, landscape = shieldExists(shield[1]) if not exists then return '' end

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 link = showLink and "" or "|link=" local shieldCode = format("", shield[1], size, link, shield[1]) if not banners[1] then return shieldCode end

for _,banner in ipairs(banners) do		if shieldExists(banner[1]) then shieldCode = format(" %s",				banner[1],				banner[2] * scale,				link,				banner[1],				shieldCode) end end return ' ' .. shieldCode .. ' ' end

function p.shield(route, scale, showLink, mainShield) missingShields = {} if route.rdt then local shieldSize = mw.ustring.match(route.rdt, '^(%d+)$') or 17 scale = shieldSize/defaultShieldSize end scale = scale or 1

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

function p.link(route) 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 route.nolink then return abbr, abbr end

local link = parser(route, 'link') or '' if link == '' then return abbr, abbr end

return format("%s", link, abbr), abbr 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

function p.locations(args, module, group) module = module or "" local modulearticle = module .. "article" local moduleprefix = module .. "prefix" local modulenameprefix = module .. "nameprefix" local modulenamesuffix = module .. "namesuffix"

local warnings = {}

-- Region, for disambiguation local region = parserModule.parser(args, "region", " common ") if not region then -- TODO transition if args.region then warnings.region = "region parameter is deprecated" region = args.region elseif args.country and (args.state or args.province) then warnings.region = "Inferring region from country and state/province" region = stateName(args) end end local regionName local regionText if type(region) == "table" then regionName = region.name regionText = format("%s", region.link, regionName) elseif region then regionName = region regionText = format("%s", regionName) end args.region = regionName

local locations = parserModule.parser(args, "locations", " common ") or {}

-- Primary topic requires no specialization to supplied locations. local primaryTopic = not locations and module == "jctint" and args.primary_topic ~= 'no' if args.primary_topic then -- TODO transition warnings.primary_topic = "primary_topic parameter is deprecated" end

-- Independent city local indepCityText if args.indep_city_special then indepCityText = args.indep_city_special -- Overrides `indep_city` argument. elseif args.indep_city then local indepCity = args.indep_city local spec = locations.indep_city if spec then local link = format("%s%s%s",				spec.linkprefix or "", indepCity, spec.linksuffix or "") local name = format("%s%s%s",				spec[modulenameprefix] or spec.nameprefix or "",				indepCity,				spec[modulenamesuffix] or spec.namesuffix or "") indepCityText = format("%s%s%s",				spec[modulearticle] or spec.article or "",				spec[moduleprefix] or spec.prefix or "",				link, name) else -- TODO transition warnings.indep_city = "Spec for indep_city parameter undefined in road data module" local cityLink -- Wikilink for independent city if primaryTopic then cityLink = format('%s', indepCity) else -- Specialize independent city to the region. cityLink = format('%s', indepCity, region, indepCity) end indepCityText = "City of " .. cityLink end end if indepCityText then return {region = regionText, indep_city = indepCityText, warnings = warnings} end

-- First-level subdivision, e.g., county -- Name of the type of subdivision, e.g., "County" and "Parish" local sub1name = args.sub1name -- TODO transition local sub1Text if args.sub1_special then sub1Text = args.sub1_special -- Overrides `sub1` argument. elseif args.sub1 then local sub1 = args.sub1 local article local link = sub1 local name = sub1 -- Type of first-level subdivision area, as a form of disambiguation local sub1area = args.sub1area if sub1area then local sub1areaSpec = locations.sub1areas and locations.sub1areas[sub1area] if sub1areaSpec then article = sub1areaSpec[modulearticle] or sub1areaSpec.article or "" link = format("%s%s%s",					sub1areaSpec.linkprefix or "", link, sub1areaSpec.linksuffix or "") name = format("%s%s%s",					group and "" or sub1areaSpec[modulenameprefix] or sub1areaSpec.nameprefix or "",					name,					group and "" or sub1areaSpec[modulenamesuffix] or sub1areaSpec.namesuffix or "") else -- TODO report error local errMsg = util.err(format("Undefined sub1area: %s", sub1area)) name = format("%s%s", name, errMsg) end end if locations.sub1 then local spec = locations.sub1 -- Prepend and append text from spec. link = format("%s%s%s",				spec.linkprefix or "", link, spec.linksuffix or "") name = format("%s%s%s",				spec[modulenameprefix] or spec.nameprefix or "",				name,				spec[modulenamesuffix] or spec.namesuffix or "") sub1Text = format("%s%s", article or "", link, name) else -- TODO transition warnings.sub1 = "Spec for sub1 parameter undefined in road data module" -- Add type (if specified) to wikilink for first-level subdivision. local sub1Link = sub1name and trim(format("%s %s", sub1, sub1name)) or sub1 local sub1Name = module == "jcttop" and sub1Link or sub1 if primaryTopic then sub1Text = format('%s', sub1Link, sub1Name) else -- Specialize first-level subdivision, with type added, to the region. sub1Text = format('%s', sub1Link, region, sub1Name) end end end

-- Second-level subdivision, e.g., city and town local sub2Text if args.sub2_special then sub2Text = args.sub2_special -- Overrides `sub2` argument. elseif args.sub2 then local sub2 = args.sub2 if sub2 == "none" then sub2Text = "&#8203;" -- Zero-width space elseif sub2 == " " then -- TODO transition warnings.sub2 = " argument for sub2 parameter is deprecated" sub2Text = "&#8203;" -- Zero-width space elseif primaryTopic then -- TODO transition sub2Text = format("%s", sub2) else local article local link = sub2 local name = sub2 -- Type of area, e.g., city and village, as a form of disambiguation local sub2area = args.sub2area --TODO transition or args.area if sub2area then local sub2areaSpec = locations.sub2areas and locations.sub2areas[sub2area] if not sub2areaSpec then -- TODO transition warnings.sub2 = format("Spec for area parameter '%s' undefined in road data module", sub2area) local sub2areas = { -- table of different area types city = { linksuffix = " (city)", jcttoparticle = "the ", nameprefix = "City of " },						town = { linksuffix = " (town)", jcttoparticle = "the ", nameprefix = "Town of " },						village = { linksuffix = " (village)", jcttoparticle = "the ", nameprefix = "Village of " },						community = { linksuffix = " (community)", jcttoparticle = "the ", nameprefix = "Community of " },						CDP = { linksuffix = " (CDP)", jcttoparticle = "the ", nameprefix = "Community of " },						hamlet = { linksuffix = " (hamlet)", jcttoparticle = "the ", nameprefix = "Hamlet of " },						["unorganized territory"] = { linksuffix = " (unorganized territory)", jcttoparticle = "the ", nameprefix = "Unorganized Territory of " },						township = { linksuffix = " Township", namesuffix = " Township", }					}					sub2areaSpec = sub2areas[sub2area] end if sub2areaSpec then article = sub2areaSpec[modulearticle] or sub2areaSpec.article or "" link = format("%s%s%s",						sub2areaSpec.linkprefix or "", link, sub2areaSpec.linksuffix or "") name = format("%s%s%s",						group and "" or sub2areaSpec[modulenameprefix] or sub2areaSpec.nameprefix or "",						name,						group and "" or sub2areaSpec[modulenamesuffix] or sub2areaSpec.namesuffix or "") else -- TODO report error local errMsg = util.err(format("Undefined sub2area: %s", sub2area)) name = format("%s%s", name, errMsg) end end if locations.sub2 then local spec = locations.sub2 -- Prepend and append text from spec. link = format("%s%s%s",					spec.linkprefix or "", link, spec.linksuffix or "") name = format("%s%s%s",					spec[modulenameprefix] or spec.nameprefix or "",					name,					spec[modulenamesuffix] or spec.namesuffix or "") else -- TODO transition warnings.sub2 = "Spec for sub2 parameter undefined in road data module" -- Some second-level subdivisions are not unique in a given region. -- `sub1dab` is the first-level subdivision to be used for disambiguation. local sub1dab = args.sub1dab if sub1dab then sub1dab = sub1name and trim(format("%s %s", sub1dab, sub1name)) or sub1dab link = format("%s, %s", link, sub1dab) end link = format("%s, %s", link, region) -- Add region to wikilink end sub2Text = format("%s%s", article or "", link, name) end end return {region = regionText, sub1 = sub1Text, sub2 = sub2Text, warnings = warnings} end

return p