Wiktionary:Scribunto/Converting templates

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Many editors have some understanding of templates, and have built up a repertoire of "coding idioms". But as Scribunto is only new, we don't yet have a good set of established practices for Lua. This page is an attempt to gather up our experiences with converting templates to Lua, by explaining how to conceptually "translate" certain common practices.

Using libraries and modules from within Lua[edit]

If your code needs to use a function or table from another module or a built-in library, you can import it with the require function. This function returns the value that the imported module or library itself returns (that is, the table of functions), which you must then store in a variable to use it later. Libraries that are part of Scribunto can be imported directly (except for the "mw" library, which is always available), but to import a module that is in the Module: namespace on Wiktionary, you have to include the Module: prefix in the require call. For example:

local languages = require("Module:languages")

local p = {}

function p.test(frame)
    return languages["en"].names[1] -- returns "English"
end

return p

Expanding wiki code[edit]

Lua scripts are much faster to run than template code. They are also more flexible and have more possibilities than templates. This means that it's best to try to delegate as much of the work to the module, and leave the template to do as little as possible. The template, ideally, would act as nothing more than "glue" between the wiki and Scribunto. Nevertheless, it may sometimes be desirable to transclude a template or a magic word. Scribunto provides two functions that allow you to access "wikispace" from within a module: frame:preprocess and frame:expandTemplate.

The main use of this is to allow you to convert templates to Scribunto without having to convert all the templates it uses first. However, expanding wiki code is slow; much slower than just writing Lua code. So this should not be done if it's at all possible. If you find that a Scribunto module needs to use a template, then you could consider passing the result of that template to the invocation as a parameter. Or better yet, convert that template to a Scribunto module itself!

Page names and other magic words[edit]

The Scribunto library includes a collection of functions that are geared specifically towards wiki usage, and are intended to replace magic words such as {{PAGENAME}}. Here is an overview of some of the more important ones that are available, with their magic word equivalents:

Wikicode Lua equivalent
{{PAGENAME}} mw.title.getCurrentTitle().text
{{FULLPAGENAME}} mw.title.getCurrentTitle().prefixedText
{{SUBPAGENAME}} mw.title.getCurrentTitle().subpageText
{{BASEPAGENAME}} mw.title.getCurrentTitle().baseText
{{NAMESPACE}} mw.title.getCurrentTitle().nsText

Keep in mind that if you need to access mw.title.getCurrentTitle() multiple times, it's probably a good idea to store it in a variable the first time, and then re-use that variable for any subsequent uses. When you need to check whether a page is in a particular namespace, you can use if mw.title.getCurrentTitle():inNamespace("name") then ... instead, which is probably faster.

Ifexist[edit]

Among the parser functions that are available in templates is {{#ifexist:}}, which allows you to check whether a page exists or not. Lua has an equivalent that is a bit different, but also more flexible: mw.title.new(title).exists.

Just like {{#ifexist:}}, the function makeTitle is considered an "expensive function". This means that there is a limit to how many times it and other expensive functions can be used altogether on a page; this limit is 500 as of February 2013. Like its template-code counterpart, it should therefore be used sparingly.

Empty parameters[edit]

In templates, you can use {{{param|def}}} to indicate that you want to use the parameter named param if it was given in the template call, or the string "def" if it was not. In Lua you use the following equivalent (assuming you stored the arguments in the variable args):

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

This will set the variable param to whatever was passed to the template, but if the parameter was not passed at all it is set to "def" by default. Of course, if you want to use an empty string as the default, just use "" instead.

Often, you want to use the same behavior if the parameter was missing as if it was blank; for example, in wikitext you might write {{#if:{{{param|}}}|...}}. In Lua, you would write:

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

if param ~= "" then
    ...
end

As a special case, if you want your default to be used whenever a parameter is missing or blank, you can write:

local param = args["param"] or "" ; if param == "" then param = "def" end

which sets param to the equivalent of {{#if:{{{param|}}}|{{{param}}}|def}}.

Conditionals, switches and the ternary operator[edit]

Template parser functions are functional and value-based, in that they evaluate expressions which result in a value (text) to return as a result, and do not store anything. Lua, however, is imperative: it is based on statements (commands) that are executed one at a time, and it allows you to store values in variables. Lua also has expressions, but while in wikitext everything is an expression (has a value), in Lua not everything is.

Unfortunately, Lua has no exact equivalent to a wikitext expression like {{#if:condition|then_value|else_value}}. In Lua, the only true if-then-else construct is a statement, not an expression, so it does not return a value and cannot be embedded in a larger expression. However, there are a number of expression-level constructs, detailed in the "TernaryOperator" article on the lua-users wiki, that can approximate the effect of an if-then-else construct. One of the most popular is this notation:

condition and then_value or else_value  -- Syntactically, this is: (condition and then_value) or else_value

This works because if the condition is true, it will (because of the and) then evaluate the then_value, and if not, it will evaluate else_value. Unlike in templates and in Lua if-statements, the else_value is required, because if it's left out, when the condition is false, the expression as a whole evaluates literally to false, which will trigger an error if you then use that result within a larger expression (because you can't concatenate false to a string, for example). This method code will also break down if then_value is either false or nil, because condition and false and condition and nil both evaluate to false, so the else_value will be used. You can avoid that by changing the condition around so that the then and else values are swapped (else_value can be false or nil).

For example, consider {{l}}, where the second parameter specifies the entry to link to, and the third specifies the word to be displayed — but where the third parameter is optional, and if it's missing or blank, we want to display the name of the entry (i.e., the second parameter). In wikitext, we might write {{#if:{{{3|}}}|{{{3}}}|{{{2}}}}}. In Lua, that could be expressed this way:

local entry = args[2] or error("Second parameter missing!")
local display = args[3] or ""
return "[[" .. entry .. "|" .. (display ~= "" and display or entry) .. "]]"  -- you can't use "display or entry" here because the empty string evaluates to true

However, it might be clearer to take advantage of the fact that Lua variables, unlike template parameters, are mutable; so, we can write this instead:

local entry = args[2] or error("Second parameter missing!")
local display = args[3] or "" ; if display == "" then display = entry end
return "[[" .. entry .. "|" .. display .. "]]"

You can also mimic a {{#switch:}} expression in this way, although there may be better ways to do that.