Module:Road data/parser/sandbox: Difference between revisions
Appearance
Content deleted Content added
test Tag: Reverted |
Restored revision 804710866 by MusikAnimal (talk) |
||
Line 1: | Line 1: | ||
local p = {} -- Package to be exported |
|||
-- Export package |
|||
local p = {} |
|||
-- Change to "" upon deployment. |
|||
local moduleSuffix = "/sandbox" |
|||
local parserHooksModuleName = "Module:Road data/parser/hooks" .. moduleSuffix |
|||
-- Local library aliases |
-- Local library aliases |
||
local format = string.format |
local format = string.format |
||
local gsub = mw.ustring.gsub |
local gsub = mw.ustring.gsub |
||
local trim = mw.text.trim |
|||
local upper = mw.ustring.upper |
local upper = mw.ustring.upper |
||
--- |
|||
-- Regex substitution patterns |
|||
-- Substitution pattern based on passed arguments |
|||
local prepattern = "%[(%w+)%|(.*)%|(.*)%|(.*)%]" -- [arg|equal to (blank for existence)|if true|if false] |
|||
-- Syntax: [param|value|match|mismatch] |
|||
local pattern = "%%(%w+)%%" -- %arg% |
|||
-- where |
|||
-- param is the parameter name to be tested |
|||
-- value is the value to test against argument; if empty, the argument is |
|||
-- tested for existence |
|||
-- match is the string to be substituted if the argument matches value |
|||
-- mismatch is the string to be substituted if the argument does not match |
|||
-- the value |
|||
-- These arguments may not contain "[", "|", or "]". |
|||
local prepattern = "%[(%w+)%|([^%[|%]]*)%|([^%[|%]]*)%|([^%[|%]]*)%]" |
|||
--- |
|||
-- Parameter substitution pattern |
|||
-- Syntax: %param% |
|||
-- where param is the name of the parameter whose value is to be substituted |
|||
-- in place of %param%. |
|||
local pattern = "%%(%w+)%%" |
|||
--- |
|||
local function parser(formatStr, args, form) |
|||
-- Perform substitutions. |
|||
local function ifexists(name) |
|||
-- @param #string formatStr The string the be substituted |
|||
-- Check whether a page or file named name exists |
|||
-- @param #table args The arguments passed to this module |
|||
if name == '' then return false end -- Page doesn't exist if name is blank |
|||
local function subst(formatStr, args) |
|||
local title -- mw.title object |
|||
--- |
|||
if form == 'shield' then |
|||
-- Perform a substitution based on passed argument. |
|||
title = mw.title.new(name, 'Media') -- Shields are in the Media namespace |
|||
-- @param #string param The parameter name to be tested |
|||
-- @param #string value The value to test against argument; if empty, |
|||
-- the argument is tested for existence |
|||
-- @param #string ifmatch The resulting string if the argument matches |
|||
-- `value` |
|||
-- @param #string ifmismatch The resulting string if the argument does not |
|||
-- match `value` |
|||
-- @return #string either `ifmatch` or `ifmismatch`, based on the test |
|||
local function testArgs(param, value, ifmatch, ifmismatch) |
|||
local arg = args[param] or '' |
|||
if value ~= '' then |
|||
return arg == value and ifmatch or ifmismatch |
|||
else |
else |
||
return arg ~= '' and ifmatch or ifmismatch |
|||
title = mw.title.new(name, 0) -- Links are in the mainspace |
|||
end |
end |
||
return title.exists -- A boolean for whether the page exists |
|||
end |
end |
||
-- argument-test substitutions |
|||
local function testArgs(test, equals, ifexists, ifnot) |
|||
local preprocessed = gsub(formatStr, prepattern, testArgs) |
|||
-- Implement the if test |
|||
-- parameter substitutions |
|||
if equals ~= '' then -- Argument equals something |
|||
return (gsub(preprocessed, pattern, args)) |
|||
if args[test] == equals then return ifexists else return ifnot end |
|||
-- gsub returns number of matches as second value. |
|||
else -- Existence of argument |
|||
-- The enclosing parens discards it. |
|||
if args[test] and args[test] ~= '' then return ifexists else return ifnot end |
|||
end |
|||
--- |
|||
-- Determine whether a given title exists on Wikipedia. |
|||
-- @param #string name The title, e.g., article name and file name, |
|||
-- without namespace prefix |
|||
-- @param #string key The name of the entry being translated. |
|||
-- @return #boolean `true` if the title exists, false otherwise |
|||
local function titleExists(name, key) |
|||
if name == '' then return false end |
|||
local namespaceModule = mw.loadData('Module:Road data/parser/namespace') |
|||
-- Retrieve the namespace for `key`. |
|||
local namespace = namespaceModule[key] or 0 |
|||
local title = mw.title.new(name, namespace); |
|||
return title.exists |
|||
end |
|||
--- |
|||
-- Determine whether titles exist on Wikipedia. |
|||
-- @param value A string or a table containing strings of titles to be checked |
|||
-- against |
|||
-- @param #string key The name of the entry being translated. |
|||
-- @return #boolean `true` if all titles exist, false otherwise |
|||
local function ifexists(value, key) |
|||
local valueType = type(value) |
|||
if valueType == "table" then |
|||
-- If `value` is a table, recursively check the existence |
|||
-- for each element within the table. |
|||
for _,entry in pairs(value) do |
|||
if not ifexists(entry, key) then return false end |
|||
end |
end |
||
return true |
|||
end |
end |
||
-- Otherwise, `value` is a string, so check the existence for that string. |
|||
local formatTable = {} -- Table with definitions |
|||
return titleExists(value, key) |
|||
-- Recursively dig into tables that could be parser hooks or argument tables. |
|||
end |
|||
local function formatStrInTable(formatStr) |
|||
if type(formatStr) ~= "table" then return formatStr end -- formatStr is a scalar |
|||
--- |
|||
formatTable = formatStr |
|||
-- Perform a translation on a given entry. |
|||
local hook = formatStr.hook -- Possible hook |
|||
-- @param entry An entry to be translated; may be any non-function type. |
|||
local both = formatStr[2], formatStr[3], formatStr[4] -- Second shield |
|||
-- A table may be a parser hook specification, a switch table, or an |
|||
if both then |
|||
-- ordinary value table. Translations are applied recursively. |
|||
local first = formatStrInTable(formatStr[1]) |
|||
-- @param #table args The arguments passed to this module |
|||
local second = formatStrInTable(formatStr[2]) |
|||
-- @param #string key The name of the entry being translated. |
|||
local third = formatStrInTable(formatStr[3]) |
|||
-- @return The translated entry |
|||
local fourth = formatStrInTable(formatStr[4]) |
|||
local function translate(entry, args, key) |
|||
return {first, second, third, fourth} -- First and second shield |
|||
if type(entry) == "string" then |
|||
elseif hook then |
|||
return subst(entry, args) -- Substitute arguments as necessary. |
|||
local hooksModule = require "Module:Road data/parser/hooks" |
|||
elseif type(entry) ~= "table" then |
|||
local hookFunction = hooksModule[hook] or error("Hook '" .. hook .. "' does not exist", 0) |
|||
return entry |
|||
return formatStrInTable(hookFunction(formatStr, args)) -- Call hook |
|||
elseif entry.hook then |
|||
else -- Switch on an argument |
|||
-- This entry is a parser hook. |
|||
local arg = args[formatStr.arg or "route"] |
|||
-- Requires: Parser hook must have hook field. |
|||
return formatStrInTable(formatStr[arg] or formatStr.default) |
|||
local hook = entry.hook |
|||
local parserHooksModule = require(parserHooksModuleName) |
|||
local hookFunction = parserHooksModule[hook] |
|||
or error("Hook '" .. hook .. "' does not exist", 0) |
|||
return translate(hookFunction(entry, args), args, key) |
|||
elseif entry.arg or entry.undefined or entry.default then |
|||
-- This entry is a switch table. |
|||
-- Requires: Switch table must have |
|||
-- arg, undefined, or default fields |
|||
-- but not hook field. |
|||
local arg = args[entry.arg or "route"] |
|||
if entry[arg] then return translate(entry[arg], args, key) end |
|||
if arg == nil and entry.undefined ~= nil then |
|||
-- result for unspecified argument |
|||
return translate(entry.undefined, args, key) |
|||
end |
end |
||
-- default result for mismatch |
|||
end |
|||
local defaultValue = translate(entry.default, args, key) |
|||
local function parse(formatStr) |
|||
if defaultValue and entry.ifexists then |
|||
local preprocessed = gsub(formatStr, prepattern, testArgs) -- If statements |
|||
-- Check existence. |
|||
local parsedStr = gsub(preprocessed, pattern, args) -- Variable interpolation |
|||
if ifexists(defaultValue, key) then return defaultValue end |
|||
local final = trim(parsedStr) -- Trim extra spaces |
|||
-- Failed existence check results in fallback value (default to nil). |
|||
if formatTable.ifexists then -- Existence test |
|||
return entry.otherwise and translate(entry.otherwise, args, key) or nil |
|||
local exists = ifexists(final) |
|||
else |
|||
if exists then |
|||
return defaultValue |
|||
else |
|||
return parser(formatTable.otherwise, args, form) |
|||
end |
|||
end |
end |
||
return final |
|||
end |
|||
formatStr = formatStrInTable(formatStr) -- Get formatStr |
|||
if not formatStr or formatStr == '' then return '' end -- Return empty string for empty formatStr |
|||
if type(formatStr) == 'table' then -- Dual shields |
|||
local first = parse(formatStr[1]) |
|||
local second = parse(formatStr[2]) |
|||
local third = parse(formatStr[3]) |
|||
local fourth = parse(formatStr[4]) |
|||
return first, second, third, fourth |
|||
else |
else |
||
-- This entry is a value table. |
|||
return parse(formatStr) |
|||
-- Process each table element. |
|||
local result = {} |
|||
for key,elem in pairs(entry) do |
|||
result[key] = translate(elem, args, key) |
|||
end |
|||
return result |
|||
end |
end |
||
end |
end |
||
--- |
|||
local function formatString(args, form) |
|||
-- Retrieve an entry from a data module based on a given type and key. |
|||
-- @param #string module The name of the data module to be fetched |
|||
local function getTypeData(module, type) |
|||
-- @param type The key for the type table within the loaded table |
|||
-- @param key The key for the entry within the type table |
|||
local success, moduleData = pcall(mw.loadData, module) |
|||
-- @return fetchedTable[type][key] if specified, where `fetchedTable` is the |
|||
if not success then return '' end -- Empty string if module cannot be loaded |
|||
-- table fetched from `module`, nil otherwise |
|||
local typeTable = moduleData[type] or moduleData[''] -- Type table or empty string default table |
|||
local function getTypeData(module, type, key) |
|||
local defaultTable = moduleData[''] or {} -- Default table |
|||
-- Attempt to fetch the given data module. |
|||
if typeTable then |
|||
local success, moduleData = pcall(mw.loadData, module) |
|||
local alias = typeTable.alias |
|||
if not success then return false, moduleData end -- Module could not be loaded |
|||
if alias then -- Type is an alias to another module |
|||
-- The type table defaults to empty-key table if undefined. |
|||
local aliasedModule = "Module:Road data/strings/" .. alias.module |
|||
local typeTable = moduleData[type] or moduleData[''] |
|||
-- Fallback table is the empty-key table, with the empty table as default. |
|||
return getTypeData(aliasedModule, aliasedType) |
|||
local defaultTable = moduleData[''] or {} |
|||
end |
|||
if typeTable then |
|||
local alias = typeTable.alias |
|||
else |
|||
if alias and alias.module and alias.type then |
|||
return '' |
|||
-- The type table is an alias table. |
|||
-- Recursively fetch the aliased type data. |
|||
local aliasedModule = "Module:Road data/strings/" .. alias.module |
|||
local aliasedType = alias.type |
|||
return getTypeData(aliasedModule, aliasedType, key) |
|||
end |
end |
||
return true, typeTable[key] or defaultTable[key] or nil |
|||
else |
|||
return true, nil |
|||
end |
end |
||
end |
|||
local stateCountries = {USA = true, CAN = true} -- These countries have state/province modules |
|||
--- |
|||
local state = upper(args.state or args.province or '') |
|||
-- Determine the module name for the lookup by country and state. |
|||
-- @param #table args The arguments passed to this module |
|||
-- @return #string The module name to be fetched |
|||
local function getModuleName(args) |
|||
-- countries with submodules for states or provinces |
|||
local stateCountries = {USA = true, CAN = true} |
|||
local state = upper(args.state or '') |
|||
local country |
local country |
||
if args.country then |
if args.country then |
||
country = upper(args.country) |
country = upper(args.country) |
||
else |
|||
else -- Find country via a mask |
|||
-- Recover the country from the given state or province. |
|||
local countryModule = mw.loadData("Module:Road data/countrymask") |
local countryModule = mw.loadData("Module:Road data/countrymask") |
||
country = countryModule[state] or 'UNK' |
country = countryModule[state] or 'UNK' |
||
end |
end |
||
local typeArg = args.type |
|||
local module |
|||
if stateCountries[country] and state ~= '' then |
if stateCountries[country] and state ~= '' then |
||
-- Submodule within the country exists. |
|||
module = format("Module:Road data/strings/%s/%s", country, state) |
|||
return format("Module:Road data/strings/%s/%s", country, state) |
|||
else |
|||
module = format("Module:Road data/strings/%s", country) |
|||
end |
end |
||
return |
return format("Module:Road data/strings/%s", country) |
||
end |
end |
||
--- |
|||
function p.parser(passedArgs, form) |
|||
-- Fetch the entry from the appropriate module, and return that entry |
|||
local args = {state = passedArgs.state, province = passedArgs.province ,type = passedArgs.type, route = passedArgs.route, |
|||
-- substituted with appropriate values. |
|||
denom = passedArgs.denom, county = passedArgs.county, dab = passedArgs.dab, |
|||
-- @param #table args The arguments to be used for lookup and substitutions |
|||
country = passedArgs.country, township = passedArgs.township} |
|||
-- @param #string key The key for the entry within the type table |
|||
local formatStr = formatString(args, form) |
|||
-- @param #string type (optional) The key for the type table within the fetched |
|||
if not formatStr or formatStr == '' then return nil end |
|||
-- module; defaults to args.type |
|||
return parser(formatStr, args, form) |
|||
-- @param #string moduleName (optional) The name of the module to be fetched; |
|||
-- defaults to the module determined by country and state |
|||
-- @return The substituted entry |
|||
function p.parser(args, key, type, moduleName) |
|||
-- Determine module name, if not given. |
|||
local dataModuleName = moduleName or getModuleName(args) |
|||
-- Fetch the entry from the module. |
|||
local success, formatStr = getTypeData(dataModuleName, type or args.type, key) |
|||
if not success then return false, formatStr end |
|||
-- Translate the entry. |
|||
return translate(formatStr, args, key) |
|||
end |
end |
||
Revision as of 02:53, 26 February 2021
![]() | This is the module sandbox page for Module:Road data/parser (diff). |
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This Lua module is used on approximately 41,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
This module exports the parser
function, which can be used from within Lua to fetch an entry from the appropriate string module. See the comment for the p.parser
function for the function's parameters.
local p = {} -- Package to be exported
-- Change to "" upon deployment.
local moduleSuffix = "/sandbox"
local parserHooksModuleName = "Module:Road data/parser/hooks" .. moduleSuffix
-- Local library aliases
local format = string.format
local gsub = mw.ustring.gsub
local upper = mw.ustring.upper
---
-- Substitution pattern based on passed arguments
-- Syntax: [param|value|match|mismatch]
-- where
-- param is the parameter name to be tested
-- value is the value to test against argument; if empty, the argument is
-- tested for existence
-- match is the string to be substituted if the argument matches value
-- mismatch is the string to be substituted if the argument does not match
-- the value
-- These arguments may not contain "[", "|", or "]".
local prepattern = "%[(%w+)%|([^%[|%]]*)%|([^%[|%]]*)%|([^%[|%]]*)%]"
---
-- Parameter substitution pattern
-- Syntax: %param%
-- where param is the name of the parameter whose value is to be substituted
-- in place of %param%.
local pattern = "%%(%w+)%%"
---
-- Perform substitutions.
-- @param #string formatStr The string the be substituted
-- @param #table args The arguments passed to this module
local function subst(formatStr, args)
---
-- Perform a substitution based on passed argument.
-- @param #string param The parameter name to be tested
-- @param #string value The value to test against argument; if empty,
-- the argument is tested for existence
-- @param #string ifmatch The resulting string if the argument matches
-- `value`
-- @param #string ifmismatch The resulting string if the argument does not
-- match `value`
-- @return #string either `ifmatch` or `ifmismatch`, based on the test
local function testArgs(param, value, ifmatch, ifmismatch)
local arg = args[param] or ''
if value ~= '' then
return arg == value and ifmatch or ifmismatch
else
return arg ~= '' and ifmatch or ifmismatch
end
end
-- argument-test substitutions
local preprocessed = gsub(formatStr, prepattern, testArgs)
-- parameter substitutions
return (gsub(preprocessed, pattern, args))
-- gsub returns number of matches as second value.
-- The enclosing parens discards it.
end
---
-- Determine whether a given title exists on Wikipedia.
-- @param #string name The title, e.g., article name and file name,
-- without namespace prefix
-- @param #string key The name of the entry being translated.
-- @return #boolean `true` if the title exists, false otherwise
local function titleExists(name, key)
if name == '' then return false end
local namespaceModule = mw.loadData('Module:Road data/parser/namespace')
-- Retrieve the namespace for `key`.
local namespace = namespaceModule[key] or 0
local title = mw.title.new(name, namespace);
return title.exists
end
---
-- Determine whether titles exist on Wikipedia.
-- @param value A string or a table containing strings of titles to be checked
-- against
-- @param #string key The name of the entry being translated.
-- @return #boolean `true` if all titles exist, false otherwise
local function ifexists(value, key)
local valueType = type(value)
if valueType == "table" then
-- If `value` is a table, recursively check the existence
-- for each element within the table.
for _,entry in pairs(value) do
if not ifexists(entry, key) then return false end
end
return true
end
-- Otherwise, `value` is a string, so check the existence for that string.
return titleExists(value, key)
end
---
-- Perform a translation on a given entry.
-- @param entry An entry to be translated; may be any non-function type.
-- A table may be a parser hook specification, a switch table, or an
-- ordinary value table. Translations are applied recursively.
-- @param #table args The arguments passed to this module
-- @param #string key The name of the entry being translated.
-- @return The translated entry
local function translate(entry, args, key)
if type(entry) == "string" then
return subst(entry, args) -- Substitute arguments as necessary.
elseif type(entry) ~= "table" then
return entry
elseif entry.hook then
-- This entry is a parser hook.
-- Requires: Parser hook must have hook field.
local hook = entry.hook
local parserHooksModule = require(parserHooksModuleName)
local hookFunction = parserHooksModule[hook]
or error("Hook '" .. hook .. "' does not exist", 0)
return translate(hookFunction(entry, args), args, key)
elseif entry.arg or entry.undefined or entry.default then
-- This entry is a switch table.
-- Requires: Switch table must have
-- arg, undefined, or default fields
-- but not hook field.
local arg = args[entry.arg or "route"]
if entry[arg] then return translate(entry[arg], args, key) end
if arg == nil and entry.undefined ~= nil then
-- result for unspecified argument
return translate(entry.undefined, args, key)
end
-- default result for mismatch
local defaultValue = translate(entry.default, args, key)
if defaultValue and entry.ifexists then
-- Check existence.
if ifexists(defaultValue, key) then return defaultValue end
-- Failed existence check results in fallback value (default to nil).
return entry.otherwise and translate(entry.otherwise, args, key) or nil
else
return defaultValue
end
else
-- This entry is a value table.
-- Process each table element.
local result = {}
for key,elem in pairs(entry) do
result[key] = translate(elem, args, key)
end
return result
end
end
---
-- Retrieve an entry from a data module based on a given type and key.
-- @param #string module The name of the data module to be fetched
-- @param type The key for the type table within the loaded table
-- @param key The key for the entry within the type table
-- @return fetchedTable[type][key] if specified, where `fetchedTable` is the
-- table fetched from `module`, nil otherwise
local function getTypeData(module, type, key)
-- Attempt to fetch the given data module.
local success, moduleData = pcall(mw.loadData, module)
if not success then return false, moduleData end -- Module could not be loaded
-- The type table defaults to empty-key table if undefined.
local typeTable = moduleData[type] or moduleData['']
-- Fallback table is the empty-key table, with the empty table as default.
local defaultTable = moduleData[''] or {}
if typeTable then
local alias = typeTable.alias
if alias and alias.module and alias.type then
-- The type table is an alias table.
-- Recursively fetch the aliased type data.
local aliasedModule = "Module:Road data/strings/" .. alias.module
local aliasedType = alias.type
return getTypeData(aliasedModule, aliasedType, key)
end
return true, typeTable[key] or defaultTable[key] or nil
else
return true, nil
end
end
---
-- Determine the module name for the lookup by country and state.
-- @param #table args The arguments passed to this module
-- @return #string The module name to be fetched
local function getModuleName(args)
-- countries with submodules for states or provinces
local stateCountries = {USA = true, CAN = true}
local state = upper(args.state or '')
local country
if args.country then
country = upper(args.country)
else
-- Recover the country from the given state or province.
local countryModule = mw.loadData("Module:Road data/countrymask")
country = countryModule[state] or 'UNK'
end
if stateCountries[country] and state ~= '' then
-- Submodule within the country exists.
return format("Module:Road data/strings/%s/%s", country, state)
end
return format("Module:Road data/strings/%s", country)
end
---
-- Fetch the entry from the appropriate module, and return that entry
-- substituted with appropriate values.
-- @param #table args The arguments to be used for lookup and substitutions
-- @param #string key The key for the entry within the type table
-- @param #string type (optional) The key for the type table within the fetched
-- module; defaults to args.type
-- @param #string moduleName (optional) The name of the module to be fetched;
-- defaults to the module determined by country and state
-- @return The substituted entry
function p.parser(args, key, type, moduleName)
-- Determine module name, if not given.
local dataModuleName = moduleName or getModuleName(args)
-- Fetch the entry from the module.
local success, formatStr = getTypeData(dataModuleName, type or args.type, key)
if not success then return false, formatStr end
-- Translate the entry.
return translate(formatStr, args, key)
end
return p