Difference between revisions of "Module:Road data/parser"
Jump to navigation
Jump to search
blackwiki>Imzadi1979 |
blackwiki>Fredddie (sync to main) |
||
| Line 1: | Line 1: | ||
| − | -- | + | local p = {} -- Package to be exported |
| − | local | + | |
| + | -- Change to "" upon deployment. | ||
| + | local moduleSuffix = "" | ||
| + | |||
| + | 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 upper = mw.ustring.upper | local upper = mw.ustring.upper | ||
| − | -- | + | --- |
| − | local prepattern = "%[(%w+)%|( | + | -- Substitution pattern based on passed arguments |
| − | local pattern = "%%(%w+)%%" | + | -- 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+)%%" | ||
| − | local function | + | --- |
| − | + | -- Perform substitutions. | |
| − | + | -- @param #string formatStr The string the be substituted | |
| − | if | + | -- @param #table args The arguments passed to this module |
| − | + | local function subst(formatStr, args) | |
| − | if | + | --- |
| − | + | -- 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 | else | ||
| − | + | return arg ~= '' and ifmatch or ifmismatch | |
end | end | ||
| − | |||
end | end | ||
| − | local | + | -- 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 | end | ||
| + | return true | ||
end | 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 | end | ||
| − | + | -- default result for mismatch | |
| − | + | local defaultValue = translate(entry.default, args, key) | |
| − | + | if defaultValue and entry.ifexists then | |
| − | local | + | -- Check existence. |
| − | + | if ifexists(defaultValue, key) then return defaultValue end | |
| − | if | + | -- 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 | end | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
else | 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 | ||
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 | end | ||
| + | return true, typeTable[key] or defaultTable[key] or nil | ||
| + | else | ||
| + | return true, nil | ||
end | end | ||
| − | + | end | |
| − | local stateCountries = {USA = true, CAN = true} | + | |
| + | --- | ||
| + | -- 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 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 |
| + | -- 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 | ||
| − | |||
| − | |||
if stateCountries[country] and state ~= '' then | if stateCountries[country] and state ~= '' then | ||
| − | + | -- Submodule within the country exists. | |
| − | + | return format("Module:Road data/strings/%s/%s", country, state) | |
| − | |||
end | end | ||
| − | return | + | return format("Module:Road data/strings/%s", country) |
end | 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 | |
| − | local formatStr = | + | -- @param #string key The key for the entry within the type table |
| − | if not | + | -- @param #string type (optional) The key for the type table within the fetched |
| − | return | + | -- 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 | end | ||
return p | return p | ||
Revision as of 02:57, 7 August 2019
Documentation for this module may be created at Module:Road data/parser/doc
local p = {} -- Package to be exported
-- Change to "" upon deployment.
local moduleSuffix = ""
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