Módulo:Road data/parser

Origem: Wikipédia, a enciclopédia livre.
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 args.province 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