Documentation[voir] [modifier] [historique] [purger]

Ce module a pour objectif d’aider à traiter les paramètres de modèles dans les modules LUA.

process(args, defined_parameters)

Paramètres

args de type table
Les arguments du modèle, généralement obtenus en utilisant frame.args ou frame:getParent().args.
defined_parameters de type table
La liste des paramètres acceptés par le module.
Chaque paramètre est une entrée de la table defined_parameters dont la clé est le nom du paramètre et la valeur est une table qui peut contenir les entrées suivantes :
  • required (booléen, défaut : false) : indique que le paramètre est obligatoire.
  • alias_of (chaîne/entier) : le nom du paramètre dont ce paramètre est un alias.
  • default (chaîne/nombre/booléen) : la valeur par défaut, utilisée si l’argument n’est pas présent dans la table args.
  • type (chaîne) : le type du paramètre ; number pour nombre, boolean pour booléen, omit pour chaîne. Les valeurs autorisées pour le type booléen sont 1, oui et vrai pour true et 0, non et faux pour false.
  • allow_empty (booléen, défaut : false) : indique si un paramètre obligatoire peut être laissé vide.
  • checker : précondition, fonction permettant de tester la valeur de l’argument ; elle doit posséder un seul paramètre et retourner un booléen. Ne peut pas être présent sur un alias.
  • enum : liste des valeurs autorisées pour le paramètre.
Cas particuliers
Si alias_of est présent, la valeur required n’est pas prise en compte.
Si required est true ou alias_of n’est pas nil, la valeur default ne sera pas prise en compte.
Si required n’est pas présent ou est false, la valeur allow_empty ne sera pas prise en compte.
Si alias_of pointe vers un paramètre qui est lui-même un alias, n’existe pas ou vers lui-même, une erreur est lancée.
Les erreurs suivantes ne sont pas masquées par le mode silencieux :
  • Type invalide
  • Alias vers lui-même
  • Alias vers un autre alias
  • Alias vers un paramètre non défini
  • Attributs checker et enum renseignés en même temps
Le message d’erreur est alors préfixé de "Erreur interne :"

Valeur retournée

La fonction retourne les arguments avec leurs valeurs converties dans le type spécifié pour chaque paramètre.

Exemples

Exemple complet

On veut définir un modèle avec les paramètres pron et lang en tant qu’alias des paramètres 1 et 2 obligatoires.

local args = m_params.process(frame.args, { -- ou frame:getParent().args
  [1] = { required = true, allow_empty = true }, -- On autorise à laisser le paramètre vide.
  [2] = { required = true },
  ["pron"] = { alias_of = 1 },
  ["lang"] = { alias_of = 2 },
})

La variable args est une table qui contient alors deux valeurs, aux indices 1 et 2.

Exemple complet avec erreurs silencieuses

Même exemple qu’au-dessus sauf que cette fois-ci on veut passer sous silence les erreurs éventuelles levées par la fonction process pour les traiter ultérieurement.

local args, success = m_params.process(frame.args, { -- ou frame:getParent().args
  [1] = { required = true, allow_empty = true }, -- On autorise à laisser le paramètre vide.
  [2] = { required = true },
  ["pron"] = { alias_of = 1 },
  ["lang"] = { alias_of = 2 },
}, true)

Si la fonction process n’a pas rencontré d’erreurs, la variable args est une table qui contient alors deux valeurs, aux indices 1 et 2 et la variable success contient une valeur booléenne égale à true.

Si la fonction process a rencontré une erreur, args est une table de la forme { "valeur du paramètre erroné", "type de l’erreur", "message d’erreur" } et success vaut false.

Définition d’un paramètre obligatoire
{ [1] = { required = true } }                  -- paramètre positionnel
{ ["nom du paramètre"] = { required = true } } -- paramètre nommé
Définition d’un alias
{
   [1] = {},
   ["a"] = { alias_of = 1 }
}
Définition d’une valeur par défaut
{ [1] = { default = "valeur par défaut" } }
Spécification d’un type
{ [1] = { type = "number" } }  -- nombre
{ [1] = { type = "boolean" } } -- booléen
{ [1] = { } }                  -- chaîne
Spécification d’une précondition

{ [1] = { checker = function(s) return s == "a" or s == "b" end } }

Spécification d’une énumération

{ [1] = { enum = { "v1", "v2" } } }

Exemple pour le module prononciation
{
  [1] = { required = true, allow_empty = true },
  [2] = { required = true },
  ["pron"] = { alias_of = 1 },
  ["lang"] = { alias_of = 2 }
}

local m_table = require("Module:table")

local p = {}

-- Types
p.NUMBER = "number"
p.INT = "int"
p.FLOAT = "float"
p.BOOLEAN = "boolean"

-- Constantes d’erreur
p.UNKNOWN_PARAM = "unknown parameter"
p.MISSING_PARAM = "missing required parameter"
p.EMPTY_PARAM = "empty required parameter"
p.INVALID_VALUE = "invalid value"
p.VALUE_NOT_IN_ENUM = "value not in enum"
p.INVALID_TYPE = "invalid type"
p.ALIAS_TO_UNKNOWN = "alias to undefined parameter"
p.ALIAS_TO_ALIAS = "alias to alias parameter"
p.ALIAS_TO_ITSELF = "alias to itself"
p.ENUM_WITH_CHECKER = "enum with checker"
p.ENUM_INVALID_VALUE = "invalid enum values"

--- Liste des erreurs non masquées par le mode silencieux.
local UNCATCHABLE_ERRORS = {
  p.INVALID_TYPE,
  p.ALIAS_TO_UNKNOWN,
  p.ALIAS_TO_ALIAS,
  p.ALIAS_TO_ITSELF,
  p.ENUM_WITH_CHECKER,
  p.ENUM_INVALID_VALUE,
}

--- Liste des templates de messages d’erreur.
local ERROR_MESSAGES = {
  [p.UNKNOWN_PARAM] = "Paramètre « %s » inconnu",
  [p.MISSING_PARAM] = "Paramètre requis « %s » absent",
  [p.EMPTY_PARAM] = "Paramètre requis « %s » vide",
  [p.INVALID_VALUE] = 'Valeur invalide pour le paramètre « %s » ("%s") de type %s',
  [p.VALUE_NOT_IN_ENUM] = 'Valeur invalide pour le paramètre « %s » ("%s")',
  [p.INVALID_TYPE] = 'Type inconnu pour le paramètre « %s » ("%s")',
  [p.ALIAS_TO_UNKNOWN] = 'Paramètre « %s », alias vers un paramètre non défini « %s »',
  [p.ALIAS_TO_ALIAS] = 'Paramètre « %s », alias vers un autre alias (« %s »)',
  [p.ALIAS_TO_ITSELF] = 'Paramètre « %s », alias vers lui-même',
  [p.ENUM_WITH_CHECKER] = "Le paramètre « %s » est une énumération avec une précondition",
  [p.ENUM_INVALID_VALUE] = 'Valeur énumérée invalide pour le paramètre « %s » ("%s") de type %s',
}

--- Construit l’objet d’erreur.
--- @param errorType string Le type de l’erreur.
--- @vararg any Les données pour formater le message d’erreur.
--- @return table Un objet contenant le type d’erreur à l’indice "error_type" et les autres données à l’indice "error_data".
local function buildErrorMessage(errorType, ...)
  return {
    errorType = errorType,
    errorData = { ... }
  }
end

--- Convertit une chaîne en booléen.
--- true = "1", "oui" ou "vrai"
--- false = "0", "non" ou "faux"
--- @param argValue string La valeur à convertir.
--- @param argName string Le nom de l’argument correspondant à la valeur.
--- @param frTypeName string Le type attendu en français.
--- @return boolean La valeur booléenne du premier argument.
local function toBoolean(argValue, argName, frTypeName, errorType)
  if m_table.contains({ "1", "oui", "vrai" }, argValue) then
    return true
  elseif m_table.contains({ "0", "non", "faux" }, argValue) then
    return false
  else
    error(buildErrorMessage(errorType, argName, argValue, frTypeName))
  end
end

--- Convertit une chaîne en nombre.
--- @param argValue string La valeur à convertir.
--- @param argName string Le nom de l’argument correspondant à la valeur.
--- @param toInt boolean Indique si le nombre doit être un entier.
--- @param frTypeName string Le type attendu en français.
--- @return number La valeur numérique du premier argument.
local function toNumber(argValue, argName, toInt, frTypeName, errorType)
  local val = tonumber(argValue)

  if val ~= nil then
    if not toInt or toInt and val == math.floor(val) then
      return val
    end
  end

  error(buildErrorMessage(errorType, argName, argValue, frTypeName))
end

local function getValue(expectedType, rawValue, argName, errorType)
  local value, frTypeName

  -- Vérification des types des arguments.
  if expectedType == nil then
    frTypeName = "chaîne"
    if type(rawValue) ~= "string" then
      error(buildErrorMessage(errorType, argName, rawValue, frTypeName))
    end
    value = rawValue
  elseif expectedType == p.NUMBER then
    frTypeName = "nombre"
    value = toNumber(rawValue, argName, false, frTypeName, errorType)
  elseif expectedType == p.INT then
    frTypeName = "entier"
    value = toNumber(rawValue, argName, true, frTypeName, errorType)
  elseif expectedType == p.FLOAT then
    frTypeName = "flottant"
    value = toNumber(rawValue, argName, false, frTypeName, errorType)
  elseif expectedType == p.BOOLEAN then
    frTypeName = "booléen"
    value = toBoolean(rawValue, argName, frTypeName, errorType)
  end

  return value, frTypeName
end

--- Vérifie la validité des définitions des paramètres.
--- @param definedParameters table La définition des paramètres.
local function checkParametersDefinitions(definedParameters)
  local validTypes = { p.BOOLEAN, p.NUMBER, p.INT, p.FLOAT }

  for paramName, paramValue in pairs(definedParameters) do
    if paramValue.type and not m_table.contains(validTypes, paramValue.type) then
      error(buildErrorMessage(p.INVALID_TYPE, paramName, paramValue.type))
    end
    if paramValue.enum then
      if paramValue.checker then
        error(buildErrorMessage(p.ENUM_WITH_CHECKER, paramName))
      else
        for _, enumValue in ipairs(paramValue.enum) do
          -- Vérification du type de la valeur.
          getValue(paramValue.type, enumValue, paramName, p.ENUM_INVALID_VALUE)
        end
      end
    end

    if paramValue.alias_of then
      local alias = paramValue.alias_of

      if not definedParameters[alias] then
        error(buildErrorMessage(p.ALIAS_TO_UNKNOWN, paramName, paramValue.alias_of))
      elseif alias == paramName then
        error(buildErrorMessage(p.ALIAS_TO_ITSELF, paramName))
      elseif definedParameters[alias].alias_of then
        error(buildErrorMessage(p.ALIAS_TO_ALIAS, paramName, paramValue.alias_of))
      end
    end
  end
end

--- Extrait la clé de l’argument donné après avoir résolu l’alias éventuel.
--- @param param table La définition du paramètre.
--- @param processedArgs table Les paramètres en cours de traitement.
--- @param argName string Le nom du paramètre.
--- @param processedArgs table La définition des paramètres.
--- @return string|nil,table|nil Le nom du paramètre à utiliser et le paramètre réel ou nil si le paramètre
---                              est un alias dont l’argument de base a déjà une valeur.
local function extractArgumentKey(param, processedArgs, argName, definedParameters)
  local key
  local outParam

  if param.alias_of then
    -- L’alias n’écrase pas la valeur du paramètre de base.
    if not processedArgs[param.alias_of] then
      key = param.alias_of
      outParam = definedParameters[param.alias_of]
    end
  else
    key = argName
    outParam = param
  end

  return key, outParam
end

--- Extrait la valeur de l’argument donné.
--- @param param table La définition du paramètre.
--- @param key string Le nom de l’argument après résolution de l’alias.
--- @param argName string Le nom de l’argument de base.
--- @param processedArgs table Les arguments en cours de traitement.
local function extractArgumentValue(param, key, argName, argValue, processedArgs)
  if argValue then
    argValue = mw.text.trim(argValue)
  end

  if argValue == "" then
    -- Un paramètre requis vide doit avoir la propriété allow_empty à true
    -- pour ne pas lancer d’erreur.
    if param.required and param.allow_empty or not param.required then
      argValue = nil
      processedArgs[key] = nil
    else
      error(buildErrorMessage(p.EMPTY_PARAM, argName))
    end
  end

  if argValue then
    -- Récupération de la valeur après transtypage éventuel.
    local value, frTypeName = getValue(param.type, argValue, argName, p.INVALID_VALUE)
    -- Vérification des contraintes d’énumération ou de la précondition.
    if type(param.enum) == "table" and not m_table.contains(param.enum, value) then
      error(buildErrorMessage(p.VALUE_NOT_IN_ENUM, argName, value))
    elseif type(param.checker) == "function" and not param.checker(value) then
      error(buildErrorMessage(p.INVALID_VALUE, argName, value, frTypeName))
    end
    processedArgs[key] = value
  end
end

--- Traite les arguments.
--- @param args table Les arguments à traiter.
--- @param definedParameters table La définition des paramètres.
--- @return table Les arguments traités.
local function parseArguments(args, definedParameters)
  local processedArgs = {}

  for argName, argValue in pairs(args) do
    local param = definedParameters[argName]

    if param then
      local key, actualParam = extractArgumentKey(param, processedArgs, argName, definedParameters)
      if key then
        extractArgumentValue(actualParam, key, argName, argValue, processedArgs)
      end
    else
      -- Les paramètres non définis lancent une erreur.
      error(buildErrorMessage(p.UNKNOWN_PARAM, argName))
    end
  end

  return processedArgs
end

--- Vérifie que les paramètre requis sont effectivement renseignés.
--- @param processedArgs table Les arguments retournés par la fonction parse_args.
--- @param definedParameters table La définition des paramètres.
local function checkRequiredParameters(processedArgs, definedParameters)
  for paramName, paramValue in pairs(definedParameters) do
    if processedArgs[paramName] == nil and paramValue.alias_of == nil then
      if paramValue.required and not paramValue.allow_empty then
        error(buildErrorMessage(p.MISSING_PARAM, paramName))
      elseif paramValue.default ~= nil then
        processedArgs[paramName] = paramValue.default
      end
    end
  end
end

--- Fonction permettant de traiter les arguments du module appelant.
--- Pour plus de détails, voir la documentation du module.
--- @param args table Les arguments du module appelant.
--- @param definedParameters table Les paramètres définis.
--- @param silentErrors boolean Si true, les paramètres problématiques sont retournés au lieu de lancer une erreur ; ne devrait être utilisé que dans le cas où un comportement précis est nécessaire.
--- @return table|string|number,boolean Une table contenant les paramètres traités ou le nom du paramètre ayant déclanché une erreur et un booléen indiquant le statut.
function p.process(args, definedParameters, silentErrors)
  local success, result = pcall(function()
    checkParametersDefinitions(definedParameters)
    local processedArgs = parseArguments(args, definedParameters)
    checkRequiredParameters(processedArgs, definedParameters)
    return processedArgs
  end)

  if not success then
    local errorType = result.errorType
    local errorData = result.errorData
    local errorMessage = mw.ustring.format(ERROR_MESSAGES[errorType], unpack(errorData))

    if silentErrors and not m_table.contains(UNCATCHABLE_ERRORS, errorType) then
      local argName = errorData[1]
      local argValue = type(argName) == "number" and tonumber(argName) or argName

      return { argValue, errorType, errorMessage }, false

      -- Ajout d’une mention pour les erreurs internes.
    elseif m_table.contains(UNCATCHABLE_ERRORS, errorType) then
      errorMessage = "Erreur interne : " .. errorMessage
    end
    -- Suppression de la trace de l’erreur, on garde juste le message.
    error(errorMessage, 0)
  end

  return result, true
end

function p.checkParametersFromTemplate(frame)
	local templateName = mw.ustring.sub(frame:getParent():getTitle(), 8)
	local expectedArgs = {}
	for _, arg in ipairs(frame.args) do expectedArgs[arg] = true end
	local templateArgs = frame:getParent().args
	local cats = ''
	for templateArg, _ in pairs(templateArgs) do
		if not expectedArgs[tostring(templateArg)] then
			cats = cats .. '[[Catégorie:Appel du modèle ' .. templateName .. ' avec le paramètre inconnu « ' .. templateArg .. ' »]]'
			if not mw.title.new('Catégorie:Appel du modèle ' .. templateName .. ' avec le paramètre inconnu « ' .. templateArg .. ' »').exists then
				cats = cats .. '[[Catégorie:Appel du modèle ' .. templateName .. ' avec un paramètre inconnu|' .. templateArg .. ']]'
			end
		end
	end
	return cats
end

return p