Fandom Developers Wiki
Advertisement

Collected here are tips for converting Wikitext-based templates to Lua.

Switch statements (#switch)

One of the most common cases of a slow template is due to a large {{#switch:}} statement, which are often called multiple times on the same page. By making use of mw.loadData and a simple table of data, we can make these templates significantly faster. By using a simple Lua table of data, we move to using a roughly O(1) hash table lookup, and by using mw.loadData(), the data will only be loaded once in a page load no matter how many times Module:FooBar is invoked on a page. For a switch statement with 150 items, converting it to Lua in this way resulted in an approximately 50% improvement in parse time.

For example, if you have a template that uses a switch statement like so:

{{#switch:{{{1|}}}
| case 1 = Foo
| case 2 = Bar
| case 3
| case 4 = FooBar
| #default = {{{1|}}}
}}

To convert it to Lua, create two module pages.

Module:FooBar:

local p = {}

local fooBarData = mw.loadData( 'Module:FooBar/data' )

function p.fooBar( frame )
    local fooBarText = frame.args[1]

    return fooBarData[fooBarText] or fooBarText or ""
end

return p

Module:FooBar/data:

return {
    ["case 1"] = "Foo",
    ["case 2"] = "Bar",
    ["case 3"] = "FooBar",
    ["case 4"] = "FooBar",
    --["default"] = ""
}

Usage:

{{#invoke:fooBar|fooBar|case 1}}

Output:

Foo

Conditional statements (#if)

Many templates make use of "#if" parser functions resulting in a lot of nested functions, which may make the code very complex and hard to understand. This can be converted and made more efficient using Lua.

For example, to create template that checks if a template argument contains "text", one would write something like:

{{#if:{{{1|}}}|You entered text|No text}}

This can be converted to:

Module:If:

local p = {}

function p.main( frame )
    local tArgs = frame:getParent()

    if not(tArgs.args[1]) then
        tArgs = frame
    end

    local sText = tArgs.args[1]
    local sTrueAction = tArgs.args[2] or tArgs.args["true"]
    local sFalseAction = tArgs.args[3] or tArgs.args["false"]

    if sText == nil or sText == "" then
        sText = mw.getCurrentFrame():preprocess(sFalseAction)
    else
        sText = mw.getCurrentFrame():preprocess(sTrueAction)
    end
    return sText
end
return p

Template:If:

{{#invoke:if|main}}

Usage:

{{if|statement|if_true|if_false}}

Output:

if_true

Check if article exists (#ifexists)

Create Module:Ifexists:

local p = {}

function p.exists( frame )
    local tArgs = frame:getParent()
    if not(tArgs.args[1]) then
        tArgs = frame
    end

    local sText = tArgs.args[1]
    local sTrueAction = tArgs.args[2] or tArgs.args["true"]
    local sFalseAction = tArgs.args[3] or tArgs.args["false"]

    if sText and sText ~= "" then
        if  mw.title.new( sText ).exists then
            return sTrueAction
        else 
            return sFalseAction
        end
    end
end
return p

Then simply use {{#invoke:ifexists|exists|pagename|yep|nop}}.

Note: This sadly still remains an expensive function.

One can remove the "expensive" nature of the above function by using the hack below. However, this hack can actually be worse because it transcludes a whole page.

local p = {}
function p.exists(frame)
    local sPage = frame.args[1]
    if sPage then 
        local num = frame:preprocess('{{:'..sPage..'}}')
        if string.sub(num,1,3) ~="[[:"  then
            return frame.args[2] or frame.args["true"]
        end
    end
    return frame.args[3] or frame.args["false"]
end

return p

This method is not recommended.

However, yet another approach can be used to inexpensively check if a module exists:

local p = {}
function p.exists(frame)
    local sPage = frame.args[1]

    if sPage then 
        local _, val = pcall(package.loaders[2], sPage)

        if type(val) == "function" or type(val) == "string" then
           return frame.args[2] or frame.args["true"]
        end
    end
    return frame.args[3] or frame.args["false"]
end

return p

Loop Statements (#for)

One way to reduce the template size, and to make it load faster and more efficiently is to reduce repeated text. This can typically be achieved by using loops or using Lua.

A template that repeats some text can be created using the loop functions extension by using:

Template:Foo:

{{#for:{{{n}}} | {{{list$n$}}} ♣ }}

Usage:

{{ foo: |n=4 | list1=a | list2=b | list3=c | list4=d }}

Output:

a♣b♣c♣d♣

This can be converted to Module:Loop:

local p = {}

function p.appendloop( frame )
    local tArgs = frame:getParent()

    if not(tArgs.args[1]) then
       tArgs = frame
    end

    local sOutputText = ""

    for iKey,sValue in pairs(tArgs.args) do 
       sOutputText = sOutputText  .. sValue  .. "♣"
    end

    return sOutputText 
end
return p

Template:Foo:

{{#invoke:loop|appendloop}}

Usage:

{{foo|a|b|c|d}}

Output:

a♣b♣c♣d♣

Time function (#time)

Time functions can be converted with the following Module:Time:

local p = {}

function p.getdate( frame )
    local sText = frame.args[1]
    local sTimestamp = frame.args[2]
    local sLangCode = frame.args[3]
    local lang = mw.language.new(sLangCode or "en")

    if sText then 
        return lang:formatDate(sText, sTimestamp)
    end
end

return p

Usage:

{{#invoke:time|getdate|Y-m-d|2013-10-04|fr}}

This shows the date, e.g. 2013-10-04. The special characters used to get this date can be found on mediawiki.org.

Text manipulation functions

The table below lists some common text manipulation functions (StringFunctions) commonly used in wikis. Some of these are available in the Global Lua Modules/String.

Function Wikitext
Output Lua
Length (#len)
{{#len:Icecream}}
8
local text = "Icecream"
local results = string.len(text)

print(results)
Position (#pos)
{{#pos:Žmržlina|lina}}
4
local text = "Žmržlina"
local results, e = string.find(text, "lina")

print(#text + 1 - results)
Substring (#sub)
{{#sub:Icecream|0|3}}
Ice
local text = "Icecream"
local results = string.sub(text, 1, 3)

print(results)
Repeat (#pad)
{{#pad:Ice|5|x|left}}
xxIce
local text = "Ice"
local results = string.rep("x", 5 - #text) .. text

print(results)
Replace (#replace)
{{#replace:Žmržlina|ž|z}}
Žmrzlina
local text = "Žmržlina"
local results = string.gsub(text ,"ž","z")

print(results)
URL encode (#urlencode)
{{urlencode:x:y/z á é}}
x%3Ay%2Fz+%C3%A1+%C3%A9
local results = mw.uri.encode(("x:y/z á é")

print(results)
URL decode (#urldecode)
{{urldecode:x%3Ay%2Fz+%C3%A1+%C3%A9}}
x:y/z á é
local results = mw.uri.decode("x%3Ay%2Fz+%C3%A1+%C3%A9","QUERY")

print(results)

Explode (#explode)

The "Explode" function can divide a piece of text and show the part needed. This is a parser function that was a subject of much argument and debate over close to a decade or more and one of the reasons for existence of Scribunto.[1] This was primarily due to its useful ability to extract sections or parts of a text.

Its syntax is something like:

{{#explode:And if you tolerate this| |2}} -> you

Scribunto is hundreds of times more efficient, and can be written as:

local text  = "And if you tolerate this"
local tab = mw.text.split( text, " ")

print(tab[3])

Output:

you

Extracting text

One can even do more complex things such as extracting arbitrary text using Lua patterns:

local text  = "Jameson, J. (2013). E-Leadership in higher education"
local surname,initials, year = string.match( text, "(%a*)%s*,%s*(%S*)%s*(%b())")

print( year, initials, surname)
(2013)  J. Jameson

Number formatting

There are a number of parser functions (see this) to format numbers such as Formatnum and Extension:Parser functions. Despite having the exact same name, they have different syntax and are largely incompatible.

Wikitext Lua
{{formatnum:1231231}}
local number = 1231231
local noCommas = false
local lang = mw.language.new("en")

local result = lang:formatNum(number, {noCommafy=noCommas})
mw.log(result)

Output:

1,231,231

See also

References

Text above can be found here (edit)
Advertisement