Aide:Module

La bibliothèque libre.
Sauter à la navigation Sauter à la recherche
Nuvola apps kedit.png
Cet article est une ébauche à compléter.
Vous pouvez partager vos connaissances en l’améliorant (comment ?) selon les recommandations de Wikisource.

La communauté de développement des modules Scribunto en language Lua est peu nombreuse (et encore moins nombreuse sur cette édition francophone de Wikisource). Il est recommandé de coopérer à la communauté plus nombreuse et plus expérimentée du Projet:Scribunto sur l'édition francophone de Wikipédia. Nombre des renvois de cette page de présentation y mènent directement. On peut également trouver de l'aide sur d'autres wikis listés en fin de cette page.

Un module est un morceau de code source écrit en Lua. Son utilisation s'apparente à celle des modèles (dont le langage de programmation est différent). Les modules de code Lua (et leur documentation wiki associée dans une sous-page) se trouvent dans l'espace de noms dédié Module: (et non l'espace de nom Modèle: utilisé pour la plupart les modèles wiki).

Un module est invoqué dans les pages ou modèles wiki par le code {{#invoke:Nom du module|nom_fonction|arg1|arg2|...}} (et non simplement par le code {{Nom du modèle|arg1|arg2|...}} en utilisant les modèles wiki).

Cette fonctionnalité est offerte par l'extension Scribunto pour Mediawiki, laquelle prend en charge d'une part le chargement des pages de code depuis l'espace de noms Module: et d'autre celui de l'environnement qui va les analyser, les exécuter et ensuite permettre d'en utiliser les fonctions définies par le module. Scribunto transmettra ensuite à ces fonctions les valeurs des paramètres textuels fournis par les pages wiki, exécutera ces fonctions par le moteur de script, puis transmettra le wikitexte retourné par ces fonctions à MediaWiki qui (comme lors des transclusion de modèles wiki) analysera ce texte retourné (en y détectant et filtrant le balisage non autorisé), puis l'insérera pour le remettre en forme HTML dans les pages affichées. Cette fonctionnalité de Scribunto (actuellement pour le langage Lua uniquement) est disponible depuis le 19 février 2013 sur le wiki francophone de Wikisource (fr.wikisource.org).

Structure d'un module[modifier]

Voir aussi Projet:Scribunto/Guide/Structure Lua.

Un module comporte une table locale qui doit être retournée (return) en fin de code. Toutes les fonctions exportées — c'est-à-dire utilisables à travers un #invoke — doivent être des éléments de cette table. Les fonctions exportées ne peuvent avoir qu'un paramètre déclaré, frame (voir l'objet frame), qui contient entre autres les paramètres passés lors de l'appel. D'autres fonctions (fonctions intermédiaires de traitement) peuvent être locales au module.

Les commentaires permettent d'expliquer le code, et ne sont pas interprétés. Un commentaire commence toujours par deux signes moins consécutifs (--) et se termine alors (par défaut) à la fin de la même ligne. Cependant un commentaire peut s'étendre sur plusieurs lignes sans avoir répéter pour chacune un -- initial : il suffit de débuter le commentaire par --[[ et le commentaire s'étendra alors jusqu'au prochain ]] (après un nombre quelconque de lignes, y compris les lignes blanches) et à défaut jusqu'à la fin de la page de code Lua (attention à ne pas oublier de fermer correctement ces blocs de commentaires multilignes).

On peut également insérer un ou plusieurs signes égal (=) entre les deux crochets initiaux du marqueur de commentaires multiligne, pour indiquer que ce commentaire s'étend jusqu'à la paire de crochets fermants contenant le même nombre de signes égal (voir le premier exemple ci-dessous). Cela permet notamment de mettre facilement en commentaires du code existant comprenant déjà d'autres commentaires, sans avoir à les modifier, ou éventuellement de créer des lignes de séparation en prenant garde toutefois de compter le nombre de signes égal).

Exemple simple 1[modifier]

Le titre de la page d'un module utilisable pour Scribunto et écrit en langage Lua doit toujours commencer par Module: (contrairement aux modèles wiki qui peuvent être définis dans la plupart des autres espaces de noms du wiki mais sont le plus souvent dans l'espace de noms dédié Modèle: s'ils sont assez génériques pour être réutilisés sur différentes pages wiki).

Supposons que dans la page Module:Banane, le code Lua suivant a été écrit :

-- Mon modèle très simple
-- Création d'une "table" correspondant aux symboles exportés par le module.
-- Vous pouvez l'appeler comme vous voulez, pas obligatoirement `p`.
-- Elle est vide au départ, d'où l'absence de contenu entre les accolades `{}`.
local p = {}

-- Définition d'une fonction du module, dont le nom est ajouté à la table `p`.
--[==[
`hello` est le nom qui sera indiqué lors de l'appel au module (via Scribunto)
depuis une page ou un modèle du wiki (avec `{{#invoke:Banane|hello}}`).

Vous auriez pu l'appeler pratiquement comme vous le souhaitez, pas nécessairement `hello`,
mais uniquement avec des caractères ASCII (lettres de base sans accents, chiffres décimaux
ou tiret bas), le premier caractère ne devant pas être un chiffre, et le nom utilisé ne
devant pas être est des rares mot-clés réservés par le langage Lua.

Attention, la casse des lettres est significative dans le nom de fonction choisi !
]==]
function p.hello()
    -- Ici, on se contente de retourner un texte
    -- (donc pas de frame comme dans exemple [[w:Module:Hello|]])
    return 'Hello, world!';
    -- Ce qui est retourné est la valeur de la chaîne, indiquée ici comme une constante
    -- entre les apostrophes simples (') ; une constante chaîne peut également être 
    -- encadrée de guillemets doubles (") ; et il existe également plusieurs syntaxes
    -- permettant de définir des constantes textuelles incluant des sauts de lignes
    -- ou de construire une chaîne en concaténant plusieurs constantes écrites sur
    -- des lignes différentes de code Lua.
end -- Et c'est tout.

-- Obligatoire, on retourne comme "valeur" du module la table contenant la liste des
-- noms et des définitions de fonctions exportées, qui pourront ensuite être appelées
-- sous les noms qui y ont été ajoutés :
return p;
Note : le module précédent est le plus simple qu'il soit. Il ne déclare aucun paramètre comme (frame) pour la fonction qu'il définit et exporte du module.

Dans une autre page du wiki, qui ne doit pas être dans l'espace de noms Module:, par exemple Modèle:Bac à sable, écrivez :

{{#invoke:Banane|hello}}
Note : si le code du module était dans une page appelée Module:Truc, il aurait fallu écrire {{#invoke:Truc|hello}}.

Ceci va appeler la fonction hello exportée par ce module. Le code {{#invoke:Banane|hello}} sera remplacé par le texte que cette fonction Lua retourne (après son exécution dans Scribunto, qui aura au préalable chargé le module indiqué depuis l'espace de noms Module: pour obtenir la table des fonctions exportées, puis y trouver le nom de la fonction invoquée), dans cet exemple Hello, world!.

Noter que Scribunto ne va charger le module Lua indiqué qu'une seule fois : à chaque invocation ultérieure du même module depuis une même page wiki, Scribunto pourra réutiliser et exécuter directement n'importe laquelle des fonctions exportées par ce module, dont la table des exports reste en mémoire jusqu'à la fin du traitement de la page Wiki à afficher ; cela permet notamment à un module de conserver et réutiliser, entre deux invocations de fonctions exportées par le même module, certaines variables définies dans ce module (mais pas nécessairement exportées de façon visible) et accessibles par ces fonctions, ce qui est impossible à réaliser avec les modèles wiki inclus plusieurs fois dans la même page (et difficile à réaliser et contrôler correctement avec nombre d'autres extensions spécialisées de MediaWiki).

C'est généralement une bonne idée d'invoquer un code Lua depuis une page de l'espace de noms Modèle: plutôt que directement dans les pages destinées au grand public (articles, page de discussion, projet, etc.). De cette façon, la syntaxe des pages courantes est constante, que le code soit écrit en Lua ou en code Wiki. Ainsi cela évite d'introduire une nouvelle syntaxe complexe dans ces pages.

Mises en garde concernant le codage et la composition des textes[modifier]

  • Sur tous les wikis de Wikimédia, le texte des pages et des modèles wiki (et utilisant une invocation Scribunto de code Lua) est sensé être encodé avec Unicode en UTF-8 (en principe sous une forme normalisée canonique appelée NFC, mais ce n'est pas une obligation pour les contributeurs même si elle est hautement recommandée). C'est sous cette forme que le texte sera transmis dans les noms et valeurs des paramètres fournis aux fonctions exportées par les modules Lua et c'est sous cette forme qu'est sensé être encodé le texte retourné par ces fonctions. Mais le langage Lua est neutre concernant le codage et ses chaînes de caractères ne comptent que les octets et considèrent que les 128 premières valeurs sont des caractères ASCII, les 128 autres n'ayant pas de signification particulière).
  • Quand on invoque une fonction d'un module Lua en transmettant un paramètre tel que été, le code Lua considère que la chaîne a une longueur de 5 octets et non 3 caractères, car chaque caractère é (non ASCII) est codé en UTF-8 sur deux octets. Seuls les caractères ASCII (les 128 premiers points de code Unicode, insuffisants pour le français) sont codés sur un octet. En codage UTF-8, un unique caractère peut être codé sur 1 à 4 octets (selon la valeur du point de code auquel il a été assigné par le standard Unicode).
  • Pour faciliter les traitements, MediaWiki propose des fonctions dans sa bibliothèque mw.ustring permettant d'effectuer les mêmes opérations qu'avec la bibliothèque Lua standard string (mesure de longueur, extraction de sous-chaînes, recherche, substitution, etc.), mais en comptant non pas chaque octet composant un même caractère, mais chaque caractère Unicode tout entier (s'il est codé en UTF-8 valide). Bien qu'en apparence semblables et utilisant les mêmes noms de fonctions, ces deux bibliothèques ne doivent pas être confondues, à moins que le texte à traiter est assuré ne contenir que de l'ASCII (donc pas du français standard !), au risque sinon de générer un pseudo-texte incorrectement codé et non reconnu par la suite. Cette bibliothèque est de fait indispensable dans toutes les langues (y compris en anglais) pour conserver une orthographe et une ponctuation enrichie (notamment sur Wikisource) ou simplement lisible et compréhensible (pour toutes les langues à écriture non latine qui sont également présentes au sein de textes francophones).
  • La même bibliothèque de MediaWiki fournit aussi quelques autres fonctions nécessaires au traitement minimal mais correct du codage Unicode (dont les fonctions standards Unicode de normalisation, ainsi que les fonctions de changement de casse des lettres qui sont étendues pour fonctionner sur le répertoire Unicode tout entier et non pas seulement sur les 128 premiers points de code). Cette bibliothèque simple ne peut cependant pas suffire seule à tous les traitements linguistiques possibles qui nécessitent des algorithmes plus complexes (par exemple le classement ou le tri).
  • Il faudra donc faire attention à l'usage des fonctions de transformation de texte, notamment les extractions de sous-chaînes, les fonctions de recherche et de substitution, les changements de casse des lettres, l'interprétation de la valeur numérique des nombres représentés dans un texte ou la conversion des valeurs numériques en texte (laquelle dépend aussi des conventions propres à chaque langue, déjà différentes entre le français et l'anglais), la direction inversée (ou changeant selon le contexte) de certaines écritures (notamment dans les écritures arabo-persanes et hébraïques).
  • Il faudra parfois tenir compte des règles orthographiques ou techniques de composition de plusieurs caractères, dans un ordre bien défini qui ne peut pas toujours être inversé sans modifier la signification du texte ou le rendre incompréhensible (notamment dans les écritures sud-asiatiques, ou pour certains signes diacritiques ajoutés aux caractères de base), de la même façon qu'on tient compte en français des règles grammaticales (comme les accords et conjugaisons), orthographiques (comme les élisions), lexicales (comme les pronoms personnels) ou typographiques (comme sa ponctuation, déjà un peu différente de celle de l'anglais, mais très éloignée de celle d'autres langues qui font une interprétation très différente de certains signes qu'on croit à tord comme universels, alors que leur signification dans chaque langue ou système d'écriture peut changer autant que celle des mots transcrits avec la même orthographe dans une écriture donnée et même parfois au sein de la même langue avec la même écriture).

Exemple simple 2[modifier]

Avec l'objet frame et un argument

Dans la page Module:Hello, le texte suivant a été écrit :

-- test Lua
-- utiliser {{#invoke:hello|helloworld}}
local p = {};
function p.helloworld()
   return 'Hello World!';
end
-- utiliser {{#invoke:hello|hello|Monsieur X}}
function p.hello(frame)
    -- remarquez le frame
    return 'Hello ' .. frame.args[1] .. '!';
end
return p;
  • {{#invoke:hello|helloworld}} → Hello World!
  • {{#invoke:hello|hello|Monsieur X}} → Hello Monsieur X!

Exemple simple 3[modifier]

Avec l'objet frame et les arguments

Voir :

Exemple plus complexe[modifier]

Dans la page, Module:ExempleParams, le texte suivant a été écrit :

-- Illustration du passage des paramètres dans les modules Lua
-- utilisation des itérations sur ipairs() et pairs()
local p = {};
function p.imprime_les_parametres( frame )
    local args = frame.args

    -- Uniquement les paramètres non nommés (ou dont la clé est un indice entier séquentiel)
    local resultat = '; Paramètres séquentiels\n';
    for rang, valeur in ipairs( args ) do
        resultat = resultat .. ': args[' .. rang .. '] = "' .. valeur .. '"\n';
    end

    resultat = resultat .. '; Tous les paramètres (dans un ordre arbitraire)\n';
    for clef, valeur in pairs( args ) do
        -- Noter que les deux variables `clef` et `valeur` sont locales à chaque boucle
        -- `for` qui les déclare implicitement : il est donc inutile de les déclarer avant
        -- car elles ne seraient pas utilisées et pas modifiées du tout, et leur valeur
        -- est inaccessible à la boucle suivante ou après la fin des boucles.
        if type( clef ) == 'number' then
            resultat = resultat .. ': args[' .. clef .. '] = "' .. valeur .. '"\n';
        else
            resultat = resultat .. ': args["' .. clef .. '"] = "' .. valeur .. '"\n';
        end
    end

    resultat = resultat .. '; Tous les paramètres (dans l'ordre lexicographique des clefs)\n';
    -- Forme une séquence contenant seulement les clefs dans un ordre quelconque en les
    -- mettant en valeur d'une autre clef, un numéro entier arbitraire mais séquentiel.
    local clefs = {}; -- séquence initialement vide
    for clef, _ in pairs( args ) do
        table.insert( clefs, clef );
    end
    -- Trie cette séquence (lexicographiquement, dans l'ordre ASCII).
    table.sort( clefs,
        function( a, b )
            -- Pour pouvoir comparer les clés de types différents, on les convertit toutes
            -- en chaînes (sinon se produit l'erreur Lua: attempt to compare string with number).
            return tostring(a) < tostring(b);
            -- Noter qu'alors une clé comme '+' sera classée lexicographiquement avant la clé 1, et
            -- la clé 10 sera classée lexicographiquement avant la clé 2.
            -- On pourrait décider de trier que lorsque les deux clés sont de types différents,
            -- on classe toute clé numérique avant celles d'un autre type
            -- if faudrait juste modifier cette fonction ainsi :
            --[[
            if type(a) ~= type(b) then
                return type(a) == 'number';
            else
                return a < b;
            end
            --]]
        end );
    -- Utilise les clés maintenant lues depuis la séquence triée
    for rang = 1, #clefs do
        local clef = clefs[rang];
        local valeur = args[clef];
        if type( clef ) == 'number' then
            resultat = resultat .. ': args[' .. clef .. '] = "' .. valeur .. '”\n';
        else
            resultat = resultat .. ': args["' .. clef .. '"] = "' .. valeur .. '”\n';
        end
    end

    return resultat;
end
return p;

Voir les explications dans Module:ExempleParams.

Remarques :

  • la table d'exportation de fonctions (ici p) contient comme clé les noms déclarés auxquels on a associé en valeur les fonctions définies par le module ; les fonctions exportées peuvent être ensuite appelées sous la forme p.nomFonction() (cette même syntaxe peut être utilisée dans le module lui-même à condition que les fonctions soient définies avec la syntaxe comportant aussi le nom de la table et le point, et que la table d'exportation p soit déjà définie dans le module, et pas modifiée ensuite avant de définir la fonction à exporter) ;
  • le passage des paramètres aux fonctions exportées et leur récupération (frame.args, frame, pairs, ipairs) ;

Quand utiliser un module plutôt qu'un modèle[modifier]

Les motivations ayant conduit à l'intégration de Lua dans MediaWiki (le logiciel qui fait tourner Wikipédia) sont :

  • élargir les possibilités de traitements, limitées dans les modèles par les parser-functions disponibles ;
  • avoir un vrai langage de programmation qui sépare contrôle et données (au contraire des modèles), avec comme avantages :
    • une capacité d'expression plus grande,
    • des structures de contrôle permettant des traitements plus simples et génériques (les boucles en particulier),
    • la possibilité de stocker de l'information durant le traitement, réduisant la répétition de code,
    • la structuration en modules et fonctions, permettant la réutilisation du code,
    • une mise en page libre (espaces, sauts de lignes, commentaires), favorisant la lecture et la compréhension du code ;
  • améliorer les performances, car dans les modèles :
    • chaque appel à un autre modèle ou parser-function génère une charge significative,
    • le code est souvent dupliqué en l'absence de capacité de mémorisation,
    • l'absence de structures de contrôle oblige à « étaler » le code (par exemple pour traiter un, ou deux, ou trois, ou plus de paramètres).

Un module devrait être utilisé lorsque le coût d'exécution d'un modèle est élevé. Le coût d'exécution d'un modèle est difficile à évaluer mais est proportionnel au nombre d'appels à des sous-modèles et parser-functions.

Un module devrait être également utilisé lorsque la complexité d'un modèle rend délicates sa compréhension et sa modification.

(Cette documentation est encore incomplète et à compléter)

Comment créer un module[modifier]

Se référer aux documents suivants :

Comment documenter un module[modifier]

La documentation du module Nom du module se place dans la sous-page Module:Nom du module/Documentation.

Lorsqu'on lit une page de module ne possédant pas de documentation, un texte en début de page l'indique et fournit un lien permettant de créer directement la page de documentation. L'utilisation de ce lien pré-charge un modèle de documentation.

Suivez le modèle pré-chargé de documentation. Il faut décrire chaque fonction exportée : son nom, son rôle, les paramètres (nommés et non nommés) attendus. Il est aussi conseillé de documenter les dépendances (autres modules utilisés, bibliothèques…) ainsi que les fonctions internes (rôle, paramètres…).

La documentation d'un module est une documentation d'utilisation, destinée aux personnes voulant utiliser le module, mais c'est aussi une documentation technique, destinée aux personnes voulant faire de la maintenance. Le ou les modèles qui utilisent ce module ont, eux, leur propre documentation d'utilisation, destinée aux personnes voulant utiliser ce(s) modèle(s).

Note : Il ne faut pas insérer le modèle {{Documentation}}. En effet l'insertion des éléments de documentation s'effectue directement par MediaWiki lors de la visualisation des pages.

Comment utiliser un module[modifier]

On peut utiliser un module directement par l'appel à #invoke :

{{#invoke:nom-du-module|nom_de_la_fonction|paramètres…}}

Sans paramètre, cela donne :

{{#invoke:nom-du-module|nom_de_la_fonction}}

On peut aussi utiliser un module à travers un modèle. Dans ce cas le rédacteur fait appel à un modèle comme il a l'habitude de le faire, mais le modèle fait appel à une ou plusieurs fonctions dans un ou plusieurs modules avec la syntaxe #invoke.

Par exemple, dans un modèle : {{#invoke:nom-du-module|nom_de_la_fonction|{{{1}}}|{{{2}}}|paramètre 1={{{3|valeur par défaut}}}|paramètre 2=fr|…}}

La seconde approche doit être préférée pour plusieurs raisons :

  • Cela limite les modifications dans les articles en cas de changements dans le module (seul le modèle est à modifier). Il a de plus été évoqué par les techniciens d'interdire l'utilisation de #invoke depuis l'espace encyclopédique.
  • Un module gère différemment les paramètres venant d'un appel de modèle et les paramètres venant d'un appel direct (#invoke). Gérer les deux cas complique la programmation des modules et peut conduire à des effets indésirables.
  • Une fonction générique d'un module peut avoir de nombreuses options, à charge du rédacteur de s'y retrouver. Passer par un modèle permet de simplifier la rédaction tout en permettant de construire plusieurs modèles différents exploitant des variantes de fonctionnement d'un même module (par exemple le choix de la langue décidé par le modèle et non par l'utilisateur du modèle, pour une fonction multi-langues).

Notes et références[modifier]


Voir aussi[modifier]