User:Gechy/lua-scripting
Overview
Lua is a programming language implemented on Wikipedia with some substantial restrictions via Scribunto. Its purpose is to allow you to process the data which is available on Wikipedia content pages to allow various sorts of customized display of information. Lua is supported as a scripting language in all Wikimedia Foundation sites (since March 2013), via the Scribunto extension. for example. See also [tutorial].
The Scribunto (Latin: "they shall write/let them write (in the future)") extension allows for embedding scripting languages in MediaWiki.
Currently the only supported scripting language is Lua.
Why Lua Scripting
Templates and ParserFunctions were introduced to allow end-users of MediaWiki to replicate content easily and build tools using basic logic, effectively turning wikitext into a limited programming language.
This project aims to make it possible for MediaWiki end-users to use a proper scripting language that will be more powerful and efficient than ad-hoc ParserFunctions-based logic.
Initially MediaWiki templates were pieces of wikitext that were substituted into pages instead of copy-pasting. By 2005, any other use was rare and, to some extent, controversial. In 2006, ParserFunctions were enabled, allowing users to use constructions such as {{#if}}
and {{#switch}}
, essentially turning wikitext into a purely functional programming language (i.e., a language that has no concept of state at any level and one part of the code may not affect any other part, it can only change its own output). This eventually caused several problems, including performance (some pages are overloaded with templates and require 40 seconds or more to parse/render) and readability (just take a look at this).
However, complex templates have caused performance issues and bottlenecks.
Installation and configuration
Scribunto comes bundled with Lua binary distributions for Linux (x86 and x86-64), Mac OS X Lion, and Windows (32- and 64-bit). For detailed explanation see mw:Extension:Scribunto.
Check out the Lua.org demo if you don't want to download Scribunto just yet.
Deep dive on Lua(Scribunto) Scripting
Setting up
The software used by Wikipedia, called MediaWiki, has an extension that provides a version of Lua that can be used within Wikipedia pages. The extension is called Scribunto.
On a MediaWiki wiki with Scribunto enabled, create a page with a title starting with "Module:", for example "Module:Bananas". Into this new page, copy the following text:
local p = {} -- p stands for package</translate>
function p.hello( frame )
return "Hello, world!"
end
return p
Save that, then on another(non-module) page, type:
{{#invoke:Bananas|hello}} -- Replace "Bananas" with the name of your module.
The "hello" function is exported from the module, and the result of the function is returned.
It's generally a good idea to invoke Lua code from the context of a template. This means that from the perspective of a calling page, the syntax is independent of whether the template logic is implemented in Lua or in wikitext.
For practical example on setting-up see Setting up really fast.
Working with modules
The module itself must return a Lua table containing the functions that may be called by{{#invoke:}}
. Generally, as shown above, a local variable is declared holding a table, functions are added to this table, and the table is returned at the end of the module code.
Any functions that are not added to this table, whether local or global, will not be accessible by {{#invoke:}}
, but globals might be accessible from other modules loaded using require()
. It is generally good style for the module to declare all functions and variables local.
Wrap up this session by creating your own lua module
Accessing parameters from wiki
Functions called by {{#invoke:}}
will be passed a single parameter, that being a frame object. To access the parameters passed to the {{#invoke:}}
, code will typically use the args
table of that frame object. It's also possible to access the parameters passed to the template containing the {{#invoke:}}
by using frame:getParent() and accessing that frame's args
.
local p = {}
function p.hello(frame)
return 'Hello, my ' .. frame.args[1] .. ' is ' .. frame.args[2]
end
return p
Such a function can access the frame
object to get access to the parameters that the template was invoked with.
For detailed examples on passing information to your lua module
Loops and tables
Lua libraries
MediaWiki libraries
All Scribunto libraries are located in the table mw
.
Base functions
mw.allToString (mw.allToString( ... )
)
Calls tostring()
on all arguments, then concatenates them with tabs as separators.
mw.clearLogBuffer(mw.clearLogBuffer()
)
Removes all data logged with mw.log()
.
mw.clone (mw.clone( value )
)
Creates a deep copy of a value. All tables (and their metatables) are reconstructed from scratch. Functions are still shared, however.
mw.executeFunction(mw.executeFunction( func )
)
This creates a new copy of the frame object, calls the function with that as its parameter, then calls tostring()
on all results and concatenates them (no separator) and returns the resulting string.
Note this will not work correctly from the debug console, as there is no frame object to copy.
mw.executeModule(mw.executeModule( func )
)
Executes the function in a sandboxed environment; the function cannot affect anything in the current environment, with the exception of side effects of calling any existing closures.
The name "executeModule" is because this is the function used when a module is loaded from the Module: namespace.
Note this will not work correctly from the debug console, as there is no frame object to copy.
mw.getCurrentFrame(mw.getCurrentFrame()
)
Note this will not work correctly from the debug console, as there is no frame object to copy.
Returns the current frame object.
mw.getLogBuffer(mw.getLogBuffer()
)
Returns the data logged by mw.log()
, as a string.
mw.incrementExpensiveFunctionCount (mw.incrementExpensiveFunctionCount()
)
Adds one to the "expensive parser function" count, and throws an exception if it exceeds the limit(see $wgExpensiveParserFunctionLimit).
mw.loadData (mw.loadData( module )
)
Sometimes a module needs large tables of data; for example, a general-purpose module to convert units of measure might need a large table of recognized units and their conversion factors. And sometimes these modules will be used many times on one page. Parsing the large data table for every Script error: You must specify a function to call.
can use a significant amount of time. To avoid this issue, mw.loadData()
is provided.
mw.loadData
works like require()
, with the following differences:
- The loaded module is evaluated only once per page, rather than once per
Script error: You must specify a function to call.
call.
- The loaded module is not recorded in
package.loaded
. - The value returned from the loaded module must be a table. Other data types are not supported.
- The returned table (and all subtables) may contain only booleans, numbers, strings, and other tables. Other data types, particularly functions, are not allowed.
- The returned table (and all subtables) may not have a metatable.
- All table keys must be booleans, numbers, or strings.
- The table actually returned by
mw.loadData()
has metamethods that provide read-only access to the table returned by the module. Since it does not contain the data directly,pairs()
andipairs()
will work but other methods, including #value, next(), and the functions in the Table library, will not work correctly.
The hypothetical unit-conversion module mentioned above might store its code in "Module:Convert" and its data in "Module:Convert/data", and "Module:Convert" would use local data = mw.loadData( 'Module:Convert/data' )
to efficiently load the data.
Global modules
Modules containing tables can also be retrieved from a global repository ( specifically dev.wikia.com). This uses a syntax such as:
local data = mw.loadData( 'Dev:Convert/data' )
The code above will load a table from a module stored in dev.wikia.com/wiki/Module:Convert/data (if it exists). Note: This is case sensitive.