Utilisateur:Hexasoft/Lua

Une page de Wikipédia, l'encyclopédie libre.

Voir Projet:Scribunto/Guide qui remplace cette page. Son contenu est le même (en un peu plus complet) mais il est structuré en sections/sous-sections et en sous-pages, pour un résultat − je pense − plus digeste et lisible. Hexasoft (discuter) 3 février 2013 à 01:05 (CET)



Cette page correspond à mon expérience actuelle. Il est probable que certains éléments soient faux ou incomplets. N'hésitez-pas à compléter !

Lua est un langage de programmation. L'extension Scribunto permet d'exécuter du code Lua dans wikipédia.

Pour cela un nouvel espace existe : Module:. Tout code Lua doit être écrit dans l'espace Module: pour pouvoir être utilisé.
Il semble qu'on ne puisse appeler du code Lua que depuis un modèle.

Pourquoi un langage de programmation[modifier | modifier le code]

Les modèles – en tout cas certains modèles – sont complexes et utilisent de nombreux parser-functions pour adapter leur action aux paramètres (tous les {{#<mot-clé>:).
Les parser-functions rendent des services mais ont des défauts :

  • ils sont relativement limités dans leurs actions : on ne peut pas tout faire, et certaines choses sont mal faites
  • il semble qu'ils posent des problèmes de performance, en particulier à cause des appels multiples que cela engendre
  • ils rendent la lecture des gros modèles très difficile par la densité des {} et l'aspect "tassé" du code correspondant
  • ils rendent par conséquent délicat la compréhension des modèles et leur modification
  • il y a enfin des subtilités difficiles à gérer comme la répercution (ou pas) des espaces et sauts de lignes ou encore la prise en compte (ou pas) des | (séparateurs de paramètres, qui peuvent être interprétés à différents niveaux quand il s'agit d'un modèle qui appelle un modèle qui...).

Ces raisons semblent avoir conduit à l'intégration d'un vrai langage de programmation. L'intérêt annoncé est multiple :

  • un vrai langage, ne dépendant pas d'un jeu réduit de fonctions de contrôle (les parser-functions)
  • une séparation nette de la logique de contrôle (le code) et du résultat (ce que génère le code)
  • un gain en lisibilité : structuration logique du code, absence de contrainte sur la mise en page, présence de commentaires...
  • un gain en performance

Bonne pratiques générales de la programmation, dans le contexte de wikipédia[modifier | modifier le code]

Voici quelques bonnes pratiques pour l'écriture de code. Ces bonnes pratiques ne sont pas spécifiques à Lua.

Entête de code, commentaires[modifier | modifier le code]

Note : il n'existe pas à l'heure actuelle de mécanisme similaire au <noinclude> des modèles, permettant – typiquement – d'inclure de la documentation visible pour qui lit la page du module. Les commentaires doivent servir à expliquer le code mais ne peuvent remplacer une documentation utilisateur. J'ai souligné ce manque auprès des développeurs, j'attends une réponse de leur part.

Faites commencer vos codes par des commentaires décrivant le rôle du module, ce qu'il permet de faire...
En Lua un commentaire commence par deux "-" et continue jusqu'à la fin de la ligne. Il est d'ailleurs possible de mettre un commentaire après du code.

-- ceci est un commentaire en Lua
    if (foo == "bar" ) then   -- ceci est aussi un commentaire
        foo = "not bar"
    end

En dehors de l'entête mettez des commentaires dans votre code. Un commentaire doit être pertinent : n'expliquez pas ce qu'est le code, mais ce qu'il fait :

  • À éviter. Ceci n'apporte rien à qui sait lire du Lua :
    -- on teste si foo = nil 
    if (nil == foo) then
        return "foo is not defined."  -- on retourne ce texte
    end
  • Bonne pratique. Expliquer à quoi ça sert est plus important :
    -- si foo n'est pas défini alors on quitte
    if (nil == foo) then
        return "foo is not defined."  -- on retourne un message d'erreur
    end

Espacement du code[modifier | modifier le code]

Sautez des espaces entre les mots-clés. Sautez des lignes entre les parties de codes et les différentes fonctions. Ceci améliore la lisibilité du code et accélère sa compréhension et sa modification. Exemple :

  • À éviter. Ceci est pénible à lire :
    if(nil==foo or ""==foo)then
      return "foo vaut"..foo.."\n"
    end
  • Bonne pratique. Ceci est bien plus facile à lire :
    if (nil == foo or "" == foo) then
      return "foo vaut" .. foo .. "\n"
    end

Nommer les variables et les fonctions[modifier | modifier le code]

Donnez des noms explicites et utiles à vos variables et à vos fonctions. Il faut bien sûr rester raisonnable, une variable ayant pour nom VariableContenantLaPositionDansLaTableFoo est excessif Émoticône sourire et sera surtout pénible à écrire. Mais ne tombez pas dans l'excès inverse : des variables "a, b, c, d, e, f" sont moins explicites que "resultat, titre, taille, position".

Attention : il n'est pas possible d'utiliser des caractères accentués dans les noms de fonctions et de variables.

Faites des fonctions[modifier | modifier le code]

Ne dupliquez pas de code. Si une même portion de code se répète au moins une fois il faut en faire une fonction séparée. Les fonctions ont de nombreux avantages :

  • quand on sait ce que fait une fonction on a seulement à connaître son nom, pas le contenu
  • de la même façon le code est plus lisible : il n'est pas nécessaire de comprendre un bout de code, il suffit de connaître la fonction
  • cela simplifie les modifications futures : si vous changez un code vous risquez d'oublier d'autres occurrences, ou de faire un copier/coller invalide (nom de variables différents)... ce qui entraine plus d'éditions et des risques accrus de bugs
  • une fonction est plus simple à écrire, comprendre et tester. Elle ne fait qu'une seule chose, elle peut se manipuler en dehors du reste du programme, elle utilise des variables locales.
  • cela simplifie aussi le code principal : moins de code (donc plus de facilité à suivre la structure logique), moins de variables en tous genres servant très localement.

Faites des sous-modules[modifier | modifier le code]

Il est tout à fait possible de créer des sous-modules. Ceux-ci permettent de regrouper par thème des fonctions, et cela allège d'autant le module principal (un module avec plein de code et de fonctions sera plus pénible à éditer et plus long à lire). Il est possible de créer des sous-modules en tant que sous-page du module principal. Exemple :

Module:MonModulePrincipal
-- un module de test

-- la table du module
local z = {}

-- on "importe" dans ce code les fonctions (la table) du sous-module
-- il n'y a dans ce sous-module qu'une fonction : MaSousFonction
local sousmodule = require "Module:MonModulePrincipal/SousModule"

-- la fonction principale
function z.FonctionPrincipale(frame)
  -- un texte généré dans le module principal
  local result = "Ceci est dans le module principal.<br/>"
  -- on ajoute ce que retourne la fonction du sous-module
  -- on l'appelle en utilisant la table du sous-module
  result = result .. sousmodule.MaSousFonction("un texte")

  -- on retourne le résultat
  return result
end
-- obligatoire : un module doit retourner la table

Voici le code correspondant au sous-module :

Module:MonModulePrincipal/SousModule

-- sous-module de MonModulePrincipal

-- la table du module. Vous pouvez l'appeler 'z' aussi, ce sont des modules distincts
local z = {}

-- la fonction du sous-module
local z.MaSousFonction(texte)
  -- on retourne juste un truc quelconque
  return "Un texte généré dans le sous-module + paramètre = " .. texte .. ".<br/>"
end
return z  -- obligatoire

Attention : à l'heure actuelle il semble que le logiciel de wikipédia ne soit pas en mesure de détecter les changements qui interviennent dans des modules qui ne sont pas directement appelés par des modèles.
Dans l'exemple ci-dessus si vous modifiez le module principal vous verrez les changements directement, en rechargeant la page où vous utilisez le modèle qui appelle le module.
Par contre si vous modifiez uniquement le sous-module recharger la page où le modèle est utilisé ne fera rien (ou alors - peut-être - qu'il faut attendre, mais longtemps...).
Vous pouvez forcer la prise en compte des changements en modifiant le module principal, ce qui oblige à faire un dummy edit, ce qui est dommage.

Addentum : en fait il semble que ce problème de cache n'existe que pour les modules se trouvant en sous-page d'un module existant. Il semblerait donc conseillé de n'utiliser que des sous-modules dans l'espace module principal.
J'ai évoqué la question avec l'un des développeurs de cette extension, j'espère avoir des précisions rapidement.

Structuration de base d'un module, fonctionnement[modifier | modifier le code]

Un module est appelé par un modèle. Il est important de comprendre que dans le modèle l'appel au module est remplacé par ce que le module retourne.

Ceci implique donc qu'un module doit retourner du texte wiki. Ce texte peut contenir tous les éléments habituels : texte simple, structuration (puces, paragraphes, ...), liens internes ou externes, ...

Attention : le code retourné par un module est considéré comme étant déjà "traité". Ce qui veut dire que le logiciel ne traitera que le code wiki de base, pas les éventuelles inclusions de modèles.
Par exemple si je retourne ceci dans mon module :

(...)
  return "Ici j'appelle un modèle : {{MonModele}}"
end

je n'obtiendrai pas l'affichage du texte "Ici j'appelle un modèle :" suivi de ce que fait le modèle "MonModele", mais j'obtiendrai réellement le texte "Ici j'appelle un modèle : {{MonModele}}".

S'il est nécessaire d'évaluer un modèle (c'est en général le cas) il faut faire appel à une fonction particulière du paramètre frame (le paramètre que reçoit la fonction appelée par un modèle) : preprocess. Cette fonction permet de "résoudre" les inclusions. Exemple :

(...)
  return frame:preprocess("Ici j'appelle un modèle : {{MonModele}}")
end

Notez que vous pouvez n'appeler cette fonction que sur les parties qui en ont besoin :

(...)
  return "Ici j'appelle un modèle : " .. frame:preprocess("{{MonModele}}")
end

Lorsqu'on génère une sortie longue et complexe c'est plus économique (en ressources sur les serveurs) de ne faire le preprocess que sur les éléments en ayant réellement besoin. Évitez d'utiliser cette fonction quand elle n'est pas nécessaire.

Note : attention, c'est bien frame:preprocess() (avec comme séparateur les deux-points et non un simple point comme pour les appels classiques de fonctions). J'ai lu l'explication du pourquoi mais je ne m'en rappelle plus.


Le module le plus simple qu'on puisse écrire est le suivant :

-- mon module très simple

-- création de la "table" (vide au départ, d'où le {}) correspondant au module
local z = {} -- vous pouvez l'appeler comme vous voulez.

-- définition de la fonction du module. C'est le nom qui sera passé lors de l'appel
--  au module : {{#invoke|le-nom-de-mon-module|le_nom_de_la_fonction}}
function z.ma_fonction(frame)  -- toujours le paramètre frame
  -- on se contente de retourner un texte
  return "Ceci est la sortie de mon module."
end -- et c'est tout
-- obligatoire : on retourne la table du module (qui contient les fonctions)
return z

Les paramètres passés au module[modifier | modifier le code]

Un module, comme un modèle, a besoin de paramètres qu'on lui passe lorsqu'on l'appelle. Quelques modèles très simples n'ont pas de paramètres (exemple : {{Section vide ou incomplète}}) mais c'est très rare et c'est à travers les paramètres que les modèles prennent tout leur sens, à savoir pouvoir s'appliquer à de nombreuses situations différentes.

Dans le cas d'un module on retrouve la gestion des paramètres. Il y a toutefois une différence importante : il existe deux jeux de paramètres reçus par un module, ces deux jeux étant indépendants.
Le premier correspond aux paramètres passés au modèle lorsqu'on l'utilise (dans un article typiquement). Cela correspond aux paramètres tels qu'on les connait dans les modèles.
Le second correspond aux paramètres que le modèle lui-même passe au module (en fait à la fonction du module).

Exemple : dans un article j'ai le code suivant :

(...)
{{MonModèle|mo-param1|mo-param2|mo1=toto|mo2=titi}}
(...)

Le modèle MonModèle lui contient l'appel à la fonction MaFonction du module MonModule comme suit :

{{#invoke|MonModule|MaFonction|p1=riri|p2=fifi|mu-p1|mu-p2}}

La fonction MaFonction du module MonModule sera appelée avec deux séries de paramètres :

  • ceux en provenance de l'appel du modèle : mo-param1 (paramètre non nommé), mo-param2 (paramètre non nommé), mo1 (paramètre nommé valant "toto"), mo2 (paramètre nommé valant "titi")
  • ceux en provenance de l'appel depuis le modèle : p1 (paramètre nommé valant "riri"), p2 (paramètre nommé valant "fifi"), mu-p1 (paramètre non nommé), mu-p2 (paramètre non nommé)

La fonction appelée depuis un modèle reçoit en paramètre un objet spécifique : frame. Cet objet contient (entre autre) les éléments suivants :

  • frame.args : une table contenant les paramètres passés à la fonction du module par le modèle.
  • frame:getParent() : une méthode permettant d'accéder au "parent" du module, c'est-à-dire au modèle lui-même. En pratique ça ne sert à rien (enfin, pas ici) mais cela sert toutefois à accéder aux paramètres passés au modèle. Voir les 2 lignes suivantes.
  • local pframe = frame:getParent() puis local args = pframe.args : on accède ainsi à une table contenant les paramètres passés au modèle (et non passés par le modèle).


Appartée : structuration des tables de paramètres.
Les tables de paramètres peuvent être accédés de deux façons différentes, pour les paramètres nommés et ceux non nommés. Ici on considère qu'on a une variable args qui est une table de paramètres.

  • args.foo : désigne le paramètre nommé 'foo'. On peut ainsi écrire : local valeur = args.foo
  • args["foo"] : fait exactement la même chose que ci-dessus. Cette notation est toutefois nécessaire si le paramètre contient des caractères non reconnus dans les mots-clés de Lua (les accents par exemple). Ainsi si un paramètre nommé s'appelle "légende" je ne peux pas écrire "valeur = args.légende", je suis obligé d'utiliser "valeur = args["légende"]".
  • args[1] : désigne le premier paramètre non nommé (quelque soit sa position réelle : les paramètres non nommés sont numérotés à partir de 1 dans l'ordre où ils sont passés). De la même façon on a args[2], args[3]...

Note : si par erreur on passe un paramètre nommé dont le nom est un nombre cela pose problème (exemple : {{foo|1=bar}}). Si vous en voyez, corrigez-les.

Important :

  • en Lua une entrée qui n'existe pas vaut la valeur spéciale 'nil'. Ainsi "args.foo" fonctionne toujours, et vaudra soit le texte du paramètre soit la valeur nil.
  • un paramètre nommé est "nettoyé" de ses espaces au début et à la fin. Un paramètre non nommé est passé tel quel, avec les (éventuels) espaces avant et après.
  • sans rapport direct avec les paramètres, mais utile : la valeur 'nil' n'est pas acceptée par la plupart des fonctions traitant des textes, fonctions souvent utilisées - par exemple pour nettoyer les espaces avant et après. Pensez à tester la présence de nil avant de passer le paramètre à une autre fonction.

Exemple de début de fonction principale[modifier | modifier le code]

Il peut y avoir plusieurs fonctions principales, appelables par des modèles, dans un module.
Voici un exemple de début de fonction principale définissant des variables pour les paramètres :

function z.FonctionPrincipale(frame)
    local pframe = frame:getParent() -- on recupère le parent (le modèle)
    local args = pframe.args -- les paramètres passés au modèle
    local config = frame.args -- les paramètres passés au module, par le modèle

Gestion du 'nil', traitement des paramètres[modifier | modifier le code]

Principalement dans le cas des paramètres on est souvent confronté à la valeur nil, qui indique l'absence de valeur (l'absence du paramètre). Comme cette valeur n'est pas acceptée par de nombreuses fonctions il faut y prendre garde. Les moyens de le gérer sont divers.

Test lors de l'utilisation :

  local param = args["foo"] -- on récupère le paramètre nommé 'foo'
  -- si le paramètre n'est pas vide on l'utilise
  if (nil ~= param) then
    result = z.traite_parametre(param)
  end

Ici on vérifie que le paramètre est non nul. L'opérateur de comparaison est "==" pour l'égalité et "~=" pour la différence. On peut écrire "nil ~= param" ou "param ~= nil", c'est identique. Dans cet exemple si le paramètre est nil on évite donc de le passer à traite_parametre() qui pourrait avoir des problèmes sinon.

Si traite_parametre() est appelé souvent il est plus simple en terme de code de mettre le test dans la fonction elle-même plutôt que de tester avant chaque appel la valeur du paramètre.

Test lors de la récupération : on peut aussi tester à la récupération du paramètre. En effet une variable (ou une constante) "vaut" sa valeur, et la valeur nil est fausse. Il est donc possible de tester "à la volée" une valeur et de la remplacer par une autre. Exemple :

  local param = args["foo"] or ""

Concentrons-nous sur la partie à droite du "=" : Lua évalue args["foo"] (ou args.foo, c'est pareil). S'il vaut nil, il est faux. Le mot-clé "or" correspond au OU. Si la partie à gauche du ou est vrai (différente de nil) alors l'expression "a or b" vaut "a", sinon elle vaut "b". Donc ici la variable "param" faudra "args.foo" si celui-ci est différent de nil et la chaîne vide (un texte de longueur 0) sinon.
Ainsi on n'a jamais par la suite à se préoccuper du cas où l'un des paramètres contient nil.
Notez toutefois que ceci ne permet plus de distinguer ensuite le cas où le paramètre était absent (absent → nil → "") et le cas où le paramètre était vide via un appel du type {{MonModele|foo=|bar=toto}} (vide → ""), ce qui peut être un problème si un paramètre vide est différent dans la sémantique du modèle.

Structuration du code - bis[modifier | modifier le code]

Le code sert à structurer les tests et actions à faire (en fonction des paramètres généralement), dans le but de générer du code wiki, que ce soit du texte simple ou de la structuration d'information (catégories, box, liens...).

Lorsque le code génère du simple texte ce n'est pas très important, mais lorsqu'on génère du code wiki structurant il est important de regrouper ce code dans des fonctions (voire, s'il y en a assez, dans un module distinct). L'intérêt est double :

  • comme pour tout code structuré cela permet de réutiliser les codes - en évitant les duplications - et ça simplifie à la fois la lecture et la modification du code
  • cela permet surtout de regrouper tout ce qui touche aux données et/ou à la mise en forme, et facilite l'adaptation éventuelle du code en cas de changement de mise en forme

Sur ce dernier point il est d'ailleurs possible/probable que si les modules Lua se généralisent les briques de base servant à générer des choses finissent par devenir des modules. Cette structuration facilite(ra) alors l'extraction du code ou sa gestion par d'autres personnes.
Un exemple dans les infobox pourrait être la création de modules fournissant les fonctions qui correspondent aux briques de base des infobox (infobox début, infobox image, infobox titre, …). Chacun pourra inclure le module gérant ces briques de base et les utiliser sans (quasiment) modifier son code.

Le code Lua, en profondeur[modifier | modifier le code]

Documents décrivant le langage Lua :

Cette section n'a pas pour vocation de ré-écrire tout ce qui est décrit dans les documents indiqués plus haut. Il s'agit plutôt de préciser des éléments spécifiques (qui peuvent a priori servir souvent dans des modules WP).

Les variables, local/pas local[modifier | modifier le code]

En Lua une variable n'a pas de type, ou du moins elle a le type de ce qu'elle contient. Ainsi une même variable peut contenir du texte, une table, un nombre, ...

Toute variable devrait être déclarée en local, c'est-à-dire utilisable uniquement localement à la "zone" où elle est déclarée (une fonction par exemple). Une variable se déclare en indiquant le mot-clé local suivi du nom de la variable et éventuellement de la valeur à lui donner. Exemples :

local i = 5
local x = 3.14159
local y  -- variable créée mais ne contenant rien, donc 'nil'
local titre = "Salut les copains"
local z = {}

On peut déclarer une variable à n'importe quel moment dans le code, et elle est utilisable à partir du moment de sa déclaration.
On peut affecter à une variable qu'on crée des valeurs issues de fonctions ou de calculs :

local j = i + 3
local y = z.calcule_valeur(i, j)
local texte = "toto" .. titre .. z.epilogue()

Attention : une variable déclarée dans un bloc n'est utilisable qu'à l'intérieur de ce bloc (exemple : entre le then et le end d'un if, entre le début et la fin d'une fonction, …).

Les tables[modifier | modifier le code]

En Lua une table correspond à un tableau ou un hash. On peut y ranger des valeurs quelconques. Si on ne précise pas les valeurs sont rangées dans l'ordre, indexées à partir de 1. Il est aussi possible d'indexer des éléments avec des valeurs quelconques plutôt que des nombres. En Lua on construit une table en mettant les éléments entre accolades. Exemples :

local liste = {5, 3, 2, 78, 27} -- une table de 5 éléments
liste[1] -> vaut 5, le premier élément
liste[5] -> vaut 27
liste[6] -> vaut nil

local niveaux = {}  -- une table
niveaux["clade"] = { "[[Clade]]", 0 }  -- l'élément "clade" vaut la table {"[[Clade]]", 0}
niveaux["type"] = { "[[Classification des virus|Type]]", 0 }
niveaux["groupe"] = { "[[Classification des virus#Classification par type de génome|Groupe]]", 0 }

On peut construire des tables à la volée, en combinant des éléments entre eux :

local texte = "Bonjour"
local pi = 3.14159
local a = 0

local doublet = {a, pi}
local tout = {texte, doublet}
→ tout correspond à la table : {"Bonjour", {0, 3.14159}}

Voir divers exemples de tables et leur utilisation dans : Module:Taxobox-data. Ces tables servent à retrouver de l'information ou à mémoriser des correspondances entre des paramètres et des textes (par exemple), et évitent de devoir faire de multiples tests successifs dans les programmes (et aussi simplifient la gestion en séparant les données du programme).


Note : voir dans les exemples à la fin le cas particulier des tables contenant les paramètres, qui sont en réalité des meta-tables et sur lesquelles certaines opérations ne fonctionnent pas (ou pas de le même façon).

Le mot-clé return[modifier | modifier le code]

Le mot-clé return sert en deux occasions :

  • à la fin d'un module, pour retourner la table qui constitue le corps du module lui-même (en pratique c'est une table contenant toutes les fonctions utilisables)
  • pour qu'une fonction retourne le résultat de son travail à celui qui l'a appelé

Dans ce dernier cas il y a un cas particulier : une fonction destinée à être appelée depuis un modèle doit retourner du texte, qui sera le contenu remplaçant l'appel du modèle (du texte, des liens internes ou externes, des catégories, ...).

Dans tous les autres cas une fonction peut retourner diverses choses, du texte, des nombres, des tables... Ce qui est retourné est une convention de celui qui écrit la fonction, à charge de celui qui l'utilise de savoir de quoi il retourne (c'est le cas de le dire Émoticône sourire).

Le return ne peut être utilisé qu'à la fin d'un bloc. Un bloc est l'ensemble de code qui va de l'élément qui l'ouvre jusqu'au end final. Ainsi une fonction est un bloc (jusqu'à son end final, mais un if en est un aussi jusqu'à son end, ou un while, for...).

Lorsqu'un return est rencontré la fonction dans laquelle on se trouve se termine immédiatement et l'exécution du code revient directement à l'endroit où l'appel à la fonction a eu lieu.
Un return peut être présent dans un if par exemple. Il n'est alors exécuté que si le if est vrai. Ceci permet - par exemple - de quitter une fonction à tout moment si on détecte une situation qui le nécessite, comme la détection d'une erreur.

La gestion des erreurs[modifier | modifier le code]

Il n'est normalement pas possible d'avoir une erreur de syntaxe dans un module : lors de la sauvegarde d'une modification le code est évalué et la sauvegarde ne se produit pas si le code est incorrect (avec affichage de la ligne concernée et du type d'erreur).

Toutefois ceci ne protège pas des erreurs d'exécution. La plus fréquente consiste à appliquer une opération sur un paramètre qui n'a pas le type adéquat (exemple : manipulation de table sur une variable qui n'est pas une table, calcul arithmétique sur une variable qui n'est pas un nombre, manipulation sur une variable qui est nil par erreur…).
Ceci déclenche lors de l'utilisation effective du module dans un article une erreur dans le module et le résultat du module est alors remplacé par un message indiquant une erreur (en clickant sur "script error" on voit apparaître un popup décrivant le type d'erreur et où il s'est produit.


Il est possible de "récupérer" les erreurs au niveau du programme. Ceci passe par la fonction pcall() : pcall( fonction-à-exécuter, liste-des-paramètres-à-passer-à-la-fonction). Exemple :

z.mafonction(a, b, "texte")  -- appel sans protection d'erreur
pcall(mafonction, a, b, "texte") -- appel avec protection d'erreur

pcall appelle la fonction indiquée mais contrairement à un appel direct une erreur dans cette fonction ne fait pas sortir du module (avec affichage du message d'erreur).
Si l'appel génère une erreur pcall retourne { false, "message d'erreur" }. Il est donc possible de détecter et de prendre en compte une erreur, ne serait-ce qu'en générant son propre message d'erreur (probablement plus explicite pour le rédacteur car à même de fournir le contexte de l'erreur).
Si l'appel ne génère pas d'erreur pcall retourne { true, … } où "…" correspond à l'ensemble (éventuel) des informations retournées par la fonction appelée.


Notez toutefois que la meilleur façon de gérer les erreurs reste de faire en sorte qu'elles ne se produisent pas. Par exemple en validant dans les fonctions ou au moment de leur appel que les différents paramètres correspondent à ce qu'on attend (exemple : de nombreuses fonctions retournent un résultat ou nil lorsqu'il n'y a aucun résultat à retourner. On risque alors si on ne teste pas le retour précédent de passer nil à une fonction qui attend un texte, un nombre, une table…).


Pour l'affichage des erreurs il faut se rappeler qu'on peut quitter le module à tout moment en invoquant return dans la fonction principale. Ce return peut alors servir à retourner un message d'erreur mis en forme (box d'erreur, message encadré, insertion dans une catégorie d'erreurs afin de repérer les articles concernés…).
Il peut être utile de se faire une fonction génerant un message à un format pré-déterminé.
Exemple utilisé dans taxobox-fr pour générer une box d'erreur :

function z.t_erreur(texte, cattitle)
    local box
    -- boîte flottante avec le style "error" (couleur rouge)
    box = '<div class="infobox_v3 large taxobox_v3 bordered error" style="width: 20em">\n'
    -- titre explication + lien vers la documentation du module/modèle
    box = box .. '<p class="entete">' .. "Erreur d'utilisation du modèle/module taxobox.<br/>Voir [[Template:Taxobox-fr/doc|la documentation]] associée." .. '</p>\n'
    if ( nil == texte or "" == texte ) then
        texte = "Aucune description d'erreur fournie."
    end
    -- texte explicatif de l'erreur rencontrée
    box = box .. '<p class="bloc">' .. texte .. '</p>\n'
    box = box .. '</div>\n'
    -- insertion de la catégorie "en erreur"
    if (nil ~= cattitle and "" ~= cattitle) then
        box = box .. "[[Category:Taxobox à corriger|" .. cattitle .. "]]\n"
    else
        box = box .. "[[Category:Taxobox à corriger]]\n"
    end
    -- on retourne cette box. À charge à celui qui nous a appelé de retourner ceci
    -- en tant que résultat du module.
    return box
end

Quelques exemples pratiques[modifier | modifier le code]

Quelques exemples, pas forcément totalement fonctionnels, réalisant certaines actions simples.

Mise en italique de la partie non homonyme d'un titre[modifier | modifier le code]

Certains projets testent ou ont choisi de mettre le titre de certains articles en italiques (pour respecter des conventions typographiques). Le problème actuel avec les modèles est qu'il n'est pas possible de détecter la présence d'une mention d'homonymie (titre avec un éléments final entre parenthèses, donc) et qu'il ne faut pas mettre cette partie en italique - qui n'est pas concernée. Exemple : l'article Princesse Mononoké est présenté en italique : Princesse Mononoké, ce qui peut se régler automatiquement. Si l'article s'était appelé Princesse Mononoké (film) (s'il existait un autre article de même nom) l'affichage serait : Princesse Mononoké (film). Ceci ne peut se faire directement (par exemple depuis l'infobox qui est en mesure de comparer le titre du film avec le titre de l'article) et doit donc passer par une commande spécifique du genre : {{Titre mis en forme|''Princesse Mononoké'' (film)}}.
Lua permet de gérer cela, voici une façon de le faire :

-- module Italiques sur titre
local z = {}

-- Note : utilisation de preprocess car DISPLAYTITLE doit être interprété

-- fonction principale (et unique). Paramètre à l'appel : aucun
function z.titre_mis_en_italique(frame)

  -- récupération des paramètres -> on n'a pas de paramètre
  
  -- on récupère le titre de l'article
  local titre = frame:preprocess("{{PAGENAME}}")

  -- on cherche la présence d'une "(" dans le titre
   -- retourne la position dans le texte du premier "(" trouvé
  local pos = string.find (titre, "(", 1, true)
  -- si 'nil' il n'y a pas de ( -> tout le titre en italique
  if (nil == pos) then
    return frame:preprocess("{{DISPLAYTITLE:<i>" .. titre .. "</i>}}")
  end

  -- on récupère la partie homonymie (à partir de 'pos', donc)
  local p2 = string.sub (titre, pos, -1)
  -- on récupère le début (de 1 jusqu'à pos-1)
  local p1 = string.sub (titre, 1, pos-1)
  -- il ne faut pas mettre les italiques sur l'espace terminal (avant la "(")
  -- donc on enlève tout ce qui est "séparateur" de la fin de la chaîne
  p1 = string.gsub (p1, "[%s]*$", "")

  -- on retourne la première partie en italique, la deuxième normal, dans DISPLAYTITLE
  return frame:preprocess("{{DISPLAYTITLE:<i>" .. p1 .. "</i> " .. p2 .. "}}")
end

return z

Exemples :

Mise en italique de la partie non homonyme d'un titre (bis)[modifier | modifier le code]

Maintenant qu'on a une fonction qui récupère le titre et qui découpe la partie homonymie, on pourrait faire d'autres choses : mettre en gras (au lieu des italiques) ou ne pas afficher la partie homonyme (non pas que ça ait d'intérêt éditorial, il s'agit d'exemples...).

Plutôt que de faire d'autres fonctions qui vont faire quasiment la même chose on va plutôt étendre les fonctionnalité de celle-ci. La fonction recevra un paramètre du modèle (et non depuis l'article, ce n'est pas à l'utilisateur de choisir ça) qui permettra de choisir l'action à appliquer.
Ce sera d'ailleurs plusieurs paramètres :

  • italique=oui → la partie première sera en italique
  • gras=oui → la partie première sera en gras
  • seul=oui → la partie homonymie sera enlevée : il semble qu'on n'ait pas le droit d'enlever la partie homonymie du titre !

Par défaut si on ne passe aucun paramètre la fonction affichera un message d'erreur.

-- module Italiques/gras sur titre + enlève homonymie
local z = {}

-- Note : utilisation de preprocess car DISPLAYTITLE doit être interprété


-- fonction qui prend deux paramètres : la première et la deuxième partie du titre
-- on ajoute aussi les paramètres italique, gras et seul.
-- fin peut valoir nil s'il n'y en a pas
function z.genere(debut, fin, italique, gras)
  result = "{{DISPLAYTITLE:"
  -- si italique on ajoute
  if (italique ~= nil) then
    result = result .. "<i>"  -- ajout de l'italique
  end
  if (gras ~= nil) then
    result = result .. "<b>"  -- ajout du gras
  end
  -- ajout du titre
  result = result .. debut
  -- symétriquement on ferme les éléments
  if (gras ~= nil) then
    result = result .. "</b>"  -- fermeture du gras
  end
  if (italique ~= nil) then
    result = result .. "</i>"  -- fermeture de l'italique
  end

  -- si on a un 'fin' on l'ajoute
  if (nil ~= fin) then
    result = result .. " " .. fin
  end

  -- on ferme le DISPLAYTITLE
  return result .. "}}"
end

-- fonction principale (et unique). Paramètre à l'appel : aucun
function z.titre_mis_en_italique2(frame)

  -- récupération des paramètres -> seulement ceux venant du modèle
  local args = frame.args

  -- les paramètres qui nous intéresse
  local italique = args.italique
  local gras = args.gras

  -- si aucun paramètre, on n'a rien à faire
  if (nil == italique and nil == gras) then
    return "'''''Attention, erreur d'utilisation : appel du module Blablabla sans précision de paramètre.'''''"  -- rien, message d'erreur
  end

  -- on récupère le titre de l'article
  local titre = frame:preprocess("{{PAGENAME}}")

  -- on cherche la présence d'une "(" dans le titre
   -- retourne la position dans le texte du premier "(" trouvé
  local pos = string.find (titre, "(", 1, true)

  -- si pos différent de nil on recupère les deux parties
  local p1
  local p2
  if (pos ~= nil) then
    -- on récupère la partie homonymie (à partir de 'pos', donc)
    p2 = string.sub (titre, pos, -1)
    -- on récupère le début (de 1 jusqu'à pos-1)
    p1 = string.sub (titre, 1, pos-1)
    -- il ne faut pas mettre les italiques sur l'espace terminal (avant la "(")
    -- donc on enlève tout ce qui est "séparateur" de la fin de la chaîne
    p1 = string.gsub (p1, "[%s]*$", "")
  else
    -- sinon p1=titre, et p2=nil (rien)
    p1 = titre
    p2 = nil
  end
  -- on retourne le résultat
  return frame:preprocess( z.genere (p1, p2, italique, gras)
end

return z

Modèle de découpage de dates[modifier | modifier le code]

Il existe des articles pour différents éléments de date. Par exemple le 12 septembre 2012 peut pointer sur : 12 septembre, sur Septembre 2012 et sur 2012.
Le propos du modèle/module est de recevoir en paramètre (d'appel) une date comme "12 septembre 2012" et de générer la forme suivante : [[12 septembre|12]] [[septembre 2012|septembre]] [[2012]].
Le code ci-dessous ne fait aucune vérification (orthographe du mois, existence du numéro du jour) si ce n'est qu'on donne bien trois éléments.

À titre d'exercice vous pouvez ensuite ajouter :

  • une option permettant de choisir la forme à créer (par exemple ne pas faire de lien vers l'année, vers le jour...)
  • effectuer une vérification (au moins sommaire) de la validité du jour (compris entre 1 et 31) et du mois (correspondant à un mois existant) ou encore de prendre la précaution de mettre les lettres du mois en minuscule.
local z = {}


z.datifie(frame)
  local pframe = frame:getParent() -- on recupère le parent (le modèle)
  local args = pframe.args -- les paramètres passés au modèle
  local result

  -- le jour (note : il faudrait valider qu'on a bien un paramètre)
  local jour = string.gsub(args[1], "(%w)%s*(%w)%s*(%w)", "%1")
  -- le mois
  local mois = string.gsub(args[1], "(%w)%s*(%w)%s*(%w)", "%2")
  -- l'année
  local année = string.gsub(args[1], "(%w)%s*(%w)%s*(%w)", "%3")
  -- mettre ici les vérifications sur les paramètres

  -- ici, si on ajoute des paramètres, on peut contrôler ce qu'on gère ou pas
  result "[[" .. jour .. " " .. mois .. "|" .. jour .. "]] [["
  result = result .. mois .. " " .. annee .. "|" .. mois .. "]] [["
  result = result .. annee .. "]]"

  return result
end

return z

Parcourir les paramètres reçus[modifier | modifier le code]

Pour récupérer la valeur d'un paramètre passé au modèle il suffit (voir plus haut) d'accéder à args["nom-paramètre"].
Ça veut dire en pratique qu'on n'accède qu'aux paramètres qu'on cherche à utiliser. Il peut toutefois être intéressant de parcourir tous les paramètres, par exemple pour détecter l'utilisation de paramètres inconnus − qui peuvent correspondre à des fautes de frappe typiquement.
Mais les tables de paramètres (dans frame) sont en fait des meta-tables, et il n'est pas possible de parcourir leur contenu comme on peut le faire avec les tables « normales » (voir par exemple les fonction pairs() ou ipairs()).
Toutefois l'objet frame donne une méthode pour y accéder : agrumentPairs().

Voici un exemple effectuant un parcours de tous les paramètres reçus stockés dans l'objet pframe (c'est-à-dire les paramètres reçus par le modèle, mais cela fonctionne pareil pour les paramètres envoyés par le modèle) :

-- k et v reçoivent resp. le nom et le contenu de chaque paramètre
for k,v in pframe:argumentPairs() do
   if (type (k) == "string") then -- le nom est un texte → c'est un paramètre nommé
      -- ici on traite éventuellement ce paramètre nommé
   else  -- normalement dans les paramètres c'est 'string' ou 'number'
      -- ici on traite éventuellement ce paramètre non nommé
   end
end

Notez l'utilisation de la fonction type() qui retourne une chaîne décrivant la nature du contenu du paramètre (string, number, table, nil…).