Hilfe:Lua/Modul im Wiki
{{Wikipedia:Lua/Linkbox}} Diese Hilfeseite stellt für Lua-Programmierer die Wirkung eines Lua-Moduls im Kontext von Wiki-Seiten dar.
Quelltext und Seiten
- Alle Seiten im Namensraum
Modul:
(828) sind grundsätzlich der Quellcode zu je einem Lua-Modul. Dies wird nicht als Wikitext interpretiert. Das Content Model istScribunto
. - Nur Seiten, deren Name das Schema für Dokumentationen erfüllt, in der deutschsprachigen Wikipedia also auf
/Doku
endet, können Wikitext enthalten. - Darüber hinaus gibt es die Möglichkeit, in bestimmten Unterseiten der Benutzer-Spielwiese im Rahmen der Vorlagenspielwiese Lua-Module wirksam werden zu lassen.
- Seiten anderer Namensräume können mit
#invoke
schon deshalb nicht genutzt werden, weil der Namensraum dieser Anweisung intern vorangestellt wird. Auch einrequire()
ist nur auf Seiten möglich, deren Inhalt als Lua-Quellcode gespeichert ist („Content Model“). - Sonstige Unterseiten im Namensraum
Modul:
sollten nur nicht-selbstständigen Lua-Code enthalten:- Keine Vorlage oder ein anderes Modul sollte unaufgefordert derartige Seiten verwenden; ausgenommen durch
mw.loadData()
genutzte Datenmengen oder wenn eine solche Schnittstelle dokumentiert ist. - In der Regel ist es aus dem selbstständigen Modul ausgegliederter Code, wofür es unterschiedliche gute Gründe geben kann.
- Es könnte auch ein unter dem Namen
/test
ausgegliederter unit test sein.
- Keine Vorlage oder ein anderes Modul sollte unaufgefordert derartige Seiten verwenden; ausgenommen durch
Kapselung
Die Module sind bewusst so gestaltet, dass keinerlei Zugriff von einer Einbindung mit #invoke
auf eine andere oder den Inhalt der umgebenden Seite möglich ist. Einige trickreiche Versuche, dies zu unterlaufen, wurden nach kurzer Zeit unterbunden.[1]
Ein Modul kann selbst keinerlei Veränderung an der Welt hervorrufen (sieht man davon ab, dass bei den „Links auf diese Seite“ durch Einbindung anderer Seiten ein Eintrag hinzukommen kann; oder dass es die Kategorie der Seiten mit Skriptfehlern bereichert). Es kann sich ausschließlich durch seinen Rückgabewert äußern, der letztlich in irgendeiner Weise auf eine Vorlagen-Einbindung oder sonstigen Seitentext wirken müsste.
Das Modul kann die Parameter der Vorlagen-Einbindung (oder einer sonstigen Seite, in der das #invoke
vorkommt) auslesen. Ihm ist außerdem bekannt, wie die Gesamt-Seite heißt, die dem Benutzer dargestellt wird. Mehr kann das Modul über seine Umgebung (Elternumgebung) nicht herausfinden. Wie die Vorlage heißt, in der #invoke
steht, lässt sich mit
frame:getParent():getTitle()
auch noch ermitteln (siehe unten). seinen eigenen Namen kann das Modul seit Anfang 2014 ermitteln.
Die globale Variable _G
_G ist eine table, die standardmäßig die nachstehend aufgelisteten Komponenten enthält:
- _G _VERSION assert debug error getmetatable ipairs math mw next os package pairs pcall rawequal rawget rawset require select setmetatable string table tonumber tostring trim type unpack xpcall
Im Handbuch auf Mediawiki heißt es:
- Note that every required module is loaded in its own sandboxed environment, so it cannot export global variables as is sometimes done in Lua 5.1. Instead, everything that the module wishes to export should be included in the table returned by the module.
Tatsächlich sind jedoch im Mai 2013 in _G
aus allen mittels require()
eingebundenen Modulen diejenigen Variablen versammelt, die dort nicht explizit als local deklariert wurden. Es ist aber davon auszugehen, dass dies nicht mehr lange der Fall sein wird; besser ist gemäß Export-table eine saubere Rückgabe.
Rückgabewert des Moduls
Die letzte Zeile eines Moduls muss eine return
-Anweisung mit einem Rückgabewert sein. Fehlt dies, wäre der Wert immer nil
.
Der Datentyp dieses Rückgabewerts ist im Prinzip beliebig. Verbreitet wäre table, aber auch eine function wäre vorstellbar; letztlich auch eine zwangsläufig kaum variierbare Zeichenkette oder Zahl.
Der Rückgabewert ist in drei Fällen auszuwerten:
- Als Wert des Aufrufs von
require()
. - Als Wert von
mw.loadData()
. - Wenn es eine table ist, macht sie für
#invoke
die verfügbaren Funktionen zugreifbar.
Schnittstelle zur Vorlagenprogrammierung
In die einbindende Seite wird mittels der Parserfunktion {{#invoke:
M|
fun|
params}}
eingebettet.
fun ist der Name einer Funktion, die Komponente in dem als table deklarierten Rückgabewert des Moduls M ist. Sie erhält als Parameter das Objekt frame des #invoke
.
Die Parameterliste entspricht derjenigen bei Vorlagen. Verirrte Gleichheitszeichen im Wert eines unbenannten Parameters können Syntaxfehler auslösen. Bei von außen kommenden Werten sollte immer mittels vorangestellter laufender Nummer eine sichere Zuweisung erfolgen: |1={{{Text|}}}
Auswertung in der Funktion
In der Funktion steht die Parameterliste im Objekt frame als table frame.args
zur Verfügung.
Im Unterschied zu Vorlagen ist auch die leere Zuweisung |x= |
auswertbar; das heißt: Es lässt sich ohne besondere Konstrukte feststellen, dass x in der Parameterliste genannt wird, jedoch der leere Wert zugewiesen wurde.
Der Wert einer Komponente von frame.args
ist ein string, wenn der Parameter im #invoke
vorkommt, und nil
ansonsten.
Wie auch bei Vorlagen gilt: Benannte Parameter werden getrimmt (Leerzeichen und Zeilenumbruch vor und nach dem Wert werden entfernt); unbenannte Parameter nicht.
Als Beispiel siehe das Modul:Hello.
Die Zuordnung lässt sich am besten aus folgendem Beispiel für ein Modul M mit Funktion f ablesen:
{{#invoke:M|f|1=ABC|FGH| LMN |benanntA=PQR|benanntB= TUV | HÖHE ÜBER NN= 32 | Leergut= }}
liefert
frame.args[ 1 ]
|
»ABC «
|
frame.args[ 2 ]
|
»FGH «
|
frame.args[ 3 ]
|
» LMN «
|
frame.args.benanntA
|
»PQR «
|
frame.args.benanntB
|
»TUV «
|
frame.args[ "HÖHE ÜBER NN" ]
|
»32 «
|
frame.args.Leergut
|
»« |
frame.args.Unbekannt
|
nil
|
Rückgabewert einer Funktion für Vorlagen
Der Rückgabewert der in #invoke
genannten Funktion ersetzt als Zeichenkette den Bereich {{#invoke:}}
.
Als Rückgabewert sollte man immer explizit eine Zeichenkette schaffen. Wenn der Rückgabewert von anderem Typ ist, wird dies durch Standard-Operationen in eine Zeichenkette umgewandelt. Dadurch kann in der Seite das Wort false
oder nil
lesbar sein oder die Programmierung zu falschen Schlüssen führen. Letzteres lässt sich vermeiden durch den Ausdruck:
return r or ""
Wenn r „etwas“ ist, wird r zurückgegeben, sonst ""
.
Mehrere Rückgabewerte oder ein aggregierter Datentyp würden für #invoke
automatisch durch Verwendung der Funktion tostring()
in Strings umgewandelt und dann ohne Trennzeichen aneinandergefügt. Dies sollte vermieden werden und kontrolliert eine Zeichenkette gebildet werden, die sich dann auch im Artikel sehen lassen kann.
Objekt frame
- → Scribunto-Dokumentation (englisch)
Das Objekt frame gibt die Umgebung wieder. Grundsätzlich gibt es zwei Instanzen:
- Aufruf des
#invoke
(Normalfall; standardmäßig immer mitframe
bezeichnet) - Aufruf der einbindenden Seite (meist Vorlage).
- Erhältlich mittels
frame:getParent()
- Einziger nutzbarer Unterschied sind die Argumente
frame.args
der Vorlageneinbindung; alle Funktionen führen zum gleichen Ergebnis; erneutesframe:getParent()
hilft nicht.
- Erhältlich mittels
Wenn über #invoke
eine Funktion für die Vorlage genutzt wird, ist der Funktion gemäß Schnittstelle zur Vorlagenprogrammierung auch das Objekt frame dazu bekannt. Falls dies (als Bibliotheksfunktion) nicht übergeben wurde oder in tieferen Ebenen nicht durchgereicht wurde, kann es überall gebildet werden mit:
frame = mw.getCurrentFrame()
Komponenten
Unmittelbar nutzbar sind:
- frame.args
- Angegebene Parameter als table.
- In der Regel Parameter des
#invoke
– nach frame:getParent() Vorlagenparameter der einbindenden Seite.
- In der Regel Parameter des
- Aus Performance-Gründen wird das frame-Objekt als metatable organisiert. Das bedeutet, dass insbesondere die
frame.args
nicht manipuliert werden können und auch die Anzahl nicht per length-Operator#
festgestellt werden kann. - Der Datentyp des Bezeichners ist:
number
bei einem unbenannnten Vorlagenparameter; bzw. numerischer Angabestring
sonst
- frame:callParserFunction()
- Ausführung einer Parser-Funktion.
r = frame:callParserFunction( name, args )
- Verschiedene Formen sind für die Argumentenliste möglich.
- Die Argumente werden unverarbeitet an die Parser-Funktion übergeben.
- Der Doppelpunkt gehört nicht zum Funktionsnamen.
- Wo es möglich ist, sollten Lua-interne Funktionen bevorzugt werden.
- frame:expandTemplate()
- Einbinden einer anderen Seite.
r = frame:expandTemplate{ title = s, args = args }
- Es ist nur dieses Format mit benannten Parametern und geschweiften Klammern möglich.
title=
wird in der Regel eine Zeichenkette mit dem Vorlagentitel sein; inzwischen ist aber auch ein Objektmw.title
möglich.- frame:extensionTag()
- Analog zu frame:callParserFunction() mit dem von Tags umschlossenen Inhalt.
r = frame:extensionTag( name, content, args )
- Verschiedene Formen sind für die Argumentenliste möglich.
- frame:getParent()
- Objekt frame für die einbindende Seite (Elternumgebung).
frameT = frame:getParent()
- frame:getTitle()
- Gibt den Seitentitel zum frame-Objekt zurück.
- Für den mittels
#invoke
generierten frame ist das der Name des eingebundenen Moduls selbst (einschließlichModul:
zu Beginn). - Das Modul kennt dadurch seinen eigenen Namen (Pfad).
- Das kann eingesetzt werden, um die Produktivversion von einer Testkopie im BNR zu unterscheiden.
- Maximal die einbindende Wikitext-Seite, in der Regel also die Vorlage mit dem
#invoke
, lässt sich so ermitteln:frame:getParent():getTitle()
- Von der einbindenden Wikitext-Seite (Vorlage) aus ist keine Steigerung mehr im Sinne eines Aufruf-Stack möglich; die übergeordneten Aufrufe liefern
nil
zurück. - frame:preprocess()
- Expandieren eines komplexen Wikitexts.
- Vorlagen, Parserfunktionen und Parameter wie
{{{1}}}
werden ausgewertet. - Eine einzelne Vorlage sollte statt dessen mit frame:expandTemplate() expandiert werden.
frame:preprocess( string )
Nicht zwingend benötigt werden die nachstehenden drei Funktionen. Sie liefern jeweils ein object (table) zurück. Das object hat eine einzige Methode object:expand()
– dies liefert den expandierten Wikitext in der jeweiligen Situation.
- frame:getArgument( argument )
- frame:getArgument{ name = argument }
- Lässt
argument
expandieren. - frame:newParserValue( text )
- frame:newParserValue{ text = text }
- Zugriff auf
frame:preprocess( text )
. - frame:newTemplateParserValue{ title = title, args = table }
- Benannte Argumente erforderlich.
- Zugriff auf
frame:expandTemplate()
mit den genannten Argumenten.
Substituierung
Mittels der Funktion mw.isSubsting()
lässt sich herausfinden (boolean), ob die momentane Aktivität darauf hinauslaufen wird, dass das Ergebnis substituiert wird (mit {{subst:
eingebunden wurde).
Anfang 2014 wird jedoch nur erkannt, ob die Parserfunktion #invoke
unmittelbar Gegenstand einer Substituierung {{subst:#invoke:m|f}}
ist; nicht aber, ob etwa eine Vorlage, in der dieses #invoke
vorkäme, gerade substituiert wird.
Dadurch lassen sich Wartungskategorien in das Ergebnis einfügen, die bei einer unerwünschten Substituierung protestieren; oder umgekehrt bei fehlender Substituierung eine Einbindung bemängeln.
Ein frame
-Objekt wird bei der Substituierung von #invoke
nicht übergeben; über mw.getCurrentFrame() lässt es sich aber jederzeit beschaffen.
Mehrere Module
Einbindung
Die Funktion require()
bindet ein anderes Modul ein.
- Parameter ist eine Zeichenkette mit dem Namen des Moduls.
- Der Namensraum
Modul:
ist voranzustellen.
- Der Namensraum
- Es ergeben sich folgende Werte:
- table oder string, wenn dies Rückgabewert war.
true
wenn der Seitenname sonst existiert und als Lua-Quellcode verwaltet wird.- Skriptfehler und Abbruch, wenn der Seitenname nicht existiert oder der Inhalt nicht als Lua-Quellcode gilt oder ein globaler Syntaxfehler in der Seite vorliegt (kein gültiger content).
Beispiel für eine sichere Einbindung:
local lucky, WillHaben = pcall( require, "Modul:WillHaben" )
if type( WillHaben ) == "table" then
r = WillHaben.WillHaben()
else
r = "<span class=\"error\">" .. WillHaben .. "</span>"
-- auto: "package.lua:80: module 'Modul:WillHaben' not found"
-- "Fehler * Modul:WillHaben nicht gefunden"
end
pcall()
ist absturzsicher; der zweite Rückgabewert ist im Erfolgsfall der Rückgabewert des Moduls, sonst die Fehlermeldung von require()
.
Hinweis: Die Parserfunktion #iferror:
greift nicht, wenn der Klassenname in Apostroph '
statt "
eingeschlossen ist.
Weiterleitung
Ein Modul kann aus einer einzigen Zeile bestehen
return require( "Modul:NeuerName" )
und wirkt dann wie eine Weiterleitung; etwa nach einer Umbenennung.
mw.loadData()
Diese Funktion entspricht grundsätzlich dem require()
mit zwei Besonderheiten:
- Die Elemente des Rückgabewerts können nur Basis-Datentypen enthalten (vor allem Zeichenketten und Zahlen); nicht aber Funktionen.
- Die erfolgte Einbindung wird vorgemerkt, so dass mehrfache
#invoke
innerhalb einer Gesamt-Seite beschleunigt werden.
Vorgesehen ist dies für große Datenmengen, die dann auch separat von den Funktionen im Modul gepflegt werden können.
Export für andere Module
Die Standard-Aufgabe ist es, eine Vorlage mittels #invoke
zu unterstützen. Konzeptionell gibt es die Möglichkeit, zusätzlich oder ausschließlich andere Lua-Module durch require()
zu unterstützen. Dabei gibt es zwei Fälle:
- Sowohl Vorlagen als auch Module werden durch jeweils angepasste Funktionen gleicher Zielsetzung unterstützt.
- Es bietet sich an, die Funktionen mit gleichem Namen in beiden Zugriffsarten anzubieten.
- Die generische Funktion ist dann die Version für Lua-Aufruf.
- Die Variante für die Vorlagenprogrammierung setzt nur noch die Eingabeparameter um, ruft die generische Funktion auf und stellt das für Vorlagen geeignete Resultat sicher.
- Bei der Vorlagenprogrammierung sind ausschließlich Zeichenketten als Parameter möglich, während die generische Funktion auch andere Typen wie numerische Werte oder table zulässt. Die beim
#invoke
beigegebenen Parameterwerte sind dann entsprechend zu interpretieren (parsen).
- Beispiel: siehe unten
- Es bietet sich an, die Funktionen mit gleichem Namen in beiden Zugriffsarten anzubieten.
- Es werden ausschließlich Funktionen für Lua-Module angeboten; die Aufgabenstellung passt nicht zu Vorlagen.
- Beispiele:
Exportierte Funktionen werden mitsamt der table ihres definierenden Moduls angewendet. Diese heißt immer so wie das Modul selbst. Da die Namen der Module im Namensraum eindeutig sind, kann es nicht zu Namenskonflikten kommen. Damit ist auch sofort klar, woher eine Funktion stammt und wo die Dokumentation zu finden ist, wenn plötzlich im Quelltext ein unbekannter Funktionsaufruf angetroffen wird.
Einfaches Modul für Vorlagen
Das Modul:Hello gibt einen ersten Einstieg:
local p = {}
function p.hello(frame)
local name = frame.args[1]
if not name then
name = 'Welt'
end
return 'Hallo, ' .. name .. '! Dies ist Lua!'
end
return p
Der Aufruf würde erfolgen mittels
{{#invoke:Hello|hello}}
(ergibtHallo, Welt! Dies ist Lua!
)
oder
{{#invoke:Hello|hello|hier bin ich}}
(ergibtHallo, hier bin ich! Dies ist Lua!
)
Strukturierung eines komplexeren Moduls
Ein Modul mit zwei oder drei Funktionen nur für Vorlagen ist eher trivial.
Bei komplexeren Aufgaben ist etwas Strukturierung und Übersicht erforderlich.
- An Beginn sollte auch ein Datum vermerkt sein.
- Weil Versionen des Quellcodes nicht ausschließlich über die produktive Seite generiert werden, ist die normale Versionsgeschichte einer Seite nicht hinreichend für die Verfolgung der Varianten. Vielmehr werden Versionen auch aus einem Testwiki oder von einer gesonderten Erprobungsseite oder aus einem fremden Wiki-Projekt mit der Mutterversion einkopiert; und wieder dorthin zurück.
- Es empfiehlt sich eine Ultrakurzbeschreibung der Funktionalität.
- Am Schluss stehen die Funktionen, die für den Export bestimmt sind.
p
ist der Rückgabewert des gesamten Moduls. Dieser Name hat sich eingebürgert, und er stellt eine table dar.
Funktionen nur für Vorlagen, sowie innere Hilfsfunktionen
--[=[ DiesesBeispiel 2013-05-07
Dieser und jener Zweck
* service
]=]
-- Module globals
local messagePrefix = "lua-module-DiesesBeispiel-"
-- ...
-- ...
local function x( a )
-- ...
return r
end -- x()
-- ...
-- ...
-- Provide template access
local p = {}
p.service( frame )
-- ...
return x( u ) or ""
end -- .service()
return p
Die Funktion service
wird zur Nutzung in einer Vorlage exportiert.
Funktionen für Vorlagen und zusätzlich Lua-Zugriff
--[=[ DiesesBeispiel 2013-05-07
Dieser und jener Zweck
* service
* DiesesBeispiel()
]=]
-- Module globals
local messagePrefix = "lua-module-DiesesBeispiel-"
local DiesesBeispiel = { }
-- ...
-- ...
local function x( a )
-- ...
return r
end -- x()
DiesesBeispiel.service = function ( a )
-- ...
return x( u )
end -- DiesesBeispiel.service()
-- ...
-- ...
-- Provide external access
local p = {}
p.service( frame )
-- ...
return DiesesBeispiel.service( u ) or ""
end -- .service()
function p.DiesesBeispiel()
-- Retrieve function access for modules
-- Postcondition:
-- Return table with functions
return DiesesBeispiel
end -- .DiesesBeispiel()
return p
Wie vor, aber es wird zusätzlich die table DiesesBeispiel
exportiert über die Funktion DiesesBeispiel()
.
- Die Funktion
DiesesBeispiel.service()
steht danach auch anderen Modulen zur Verfügung. - Die eigentliche Implementierung der Funktionalität erfolgt in
DiesesBeispiel.service()
und die Funktionservice
für Vorlagen ruft diese Implementierung auf. Dabei sind die Parameter der Vorlageneinbindung (Zeichenketten inframe.args
) geeignet zu interpretieren und ihr Typ ggf. geeignet zu konvertieren. Während der Rückgabewert vonDiesesBeispiel.service()
auchfalse
odernil
sein mag, wird durchor ""
die für Vorlagen geeignetere leere Zeichenkette zurückgegeben.
Funktionen ausschließlich für Lua-Zugriff, nicht für Vorlagen
--[=[ DiesesBeispiel 2013-05-07
Dieser und jener Zweck
* DiesesBeispiel.service()
]=]
-- Module globals
local messagePrefix = "lua-module-DiesesBeispiel-"
local DiesesBeispiel = { }
-- ...
-- ...
local function x( a )
-- ...
return r
end -- x()
DiesesBeispiel.service = function ( a )
-- ...
return x( u )
end -- DiesesBeispiel.service()
-- ...
-- ...
-- Provide external access
return DiesesBeispiel
Der Export von p
mit der Tabelle für Vorlagen entfällt; es wird nur die table DiesesBeispiel
direkt exportiert.
Diese Situation tritt beispielsweise bei ausgelagerten Unter-Modulen sowie Datentabellen bei mw.loadData() auf.
Elternumgebung
Versuche, die programmtechnisch im Prinzip vorhandene Elternumgebung zu ergründen, werden von Scribunto mit dem Ziel der Kapselung systematisch unterbunden. Darin unterscheidet sich „Lua im Wiki“ von allgemeinen Lua-Anwendungen.
Der einzig sinnvoll mögliche Zugriff ist auf die Parameter der unmittelbar einbindenden Seite möglich:frame:getParent().args
Innerhalb der unmittelbar umgebenden Seite (Vorlage) erfolgen die einzelnen Aufrufe von #invoke
sequentiell und unter Verwendung der gleichen Umgebung.
- Verschiedene Einbindungen der Vorlage selbst (z. B. in einem Artikel) werden dagegen parallel abgearbeitet und teilen auch keine Elternumgebung.
math.random()
Vor dem ersten Aufruf der Funktion math.random() ist der Zufallszahlengenerator stets mit dem Wert 0 initialisiert (zur Initialisierung siehe math.randomseed()). Ohne eigene Initialisierung wird also stets die gleiche Folge von Zufallszahlen generiert. Bei sequentieller Verarbeitung der Aufrufe von #invoke
innerhalb der gleichen Vorlage wird der Zufallszahlengenerator allerdings zwischendurch nicht zurückgesetzt, so dass die Folge dort kontinuierlich fortgesetzt wird.