Guidelines:Modules: Difference between revisions

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search
No edit summary
Line 86: Line 86:
</pre>
</pre>


This invokes [[Module:Figurine]], which looks be something like:
This invokes [[Module:Figurine]], which could look something like:


<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">

Revision as of 00:51, 19 March 2020

Overview

Modules are Lua scripts for making templates using a full-fledged programming language. You can ask questions about Zelda Wiki modules in the #modules [Discord Discord] channel.

Templates

Main article: Help:Templates

Modules exist as a means to create more complex templates. In order to contribute to modules, one must first understand how templates are used. One should also know to create a basic template without modules to get a sense when Lua scripts are needed and why.

Getting Started

In order to contribute to modules, you'll need to learn basic programming in Lua. You'll also need to know the specifics of Lua scripting on MediaWiki.

Lua

Beginners

There are many excellent online resources designed to teach programming fundamentals... But none of them are in Lua. Fortunately, Lua is a small and simple language. Jump in and try to learn through the Exercises.

If you find you need more guided lessons, try Codeacademy's Python 2 course, or Khan Academy's Intro to JavaScript. Python is closer to Lua than JavaScript, but Khan Academy's course has the benefit of video talk-throughs. In any case the fundamental language concepts are the same.

Developers

If you already have some programming experience in other languages, read Learn Lua in 15 minutes.

Note the following in particular:

Particularities Further reading
tables are the only built-in object type. Any use of the term "array" actually refers to a table with integer keys.
Generally arrays are 1-indexed as opposed to 0-indexed.
local tbl = {"foo"}
tbl[1]
> "foo"
  • The ipairs iterator is for integer key-value pairs in a table.
  • The pairs iterator is for all key-value pairs, integer or otherwise.
The length operator for a table is #.

# replaces the table.getn seen in the online version of Programming in Lua, which is for Lua 5.0. getn won't work here, as the wiki is on Lua 5.1 at the time of writing.

table.setn was also deprecated and is unusable in 5.1. Unfortunately, the alternative __len field is only available in Lua 5.2.

Tables, nil, and the # operator don't interact the way similar constructs do in other languages.
#{ nil, nil, "foo" }
> 3

local tbl = {}
tbl[3] = "foo"
#tbl
> 0

tbl[1] = "bar"
#tbl
> 1

tbl[2] = "baz"
#tbl
> 3

Generally, "array" functions only look at the consecutive indices starting from 1.

Scribunto

For a broader and more in-depth guide, see Gamepedia Help Wiki.
See also the full Scribunto/Lua reference manual.

Scribunto is the name of the extension that enables Lua modules. The Lua on you see on MediaWiki is almost the same as standard Lua except for some changes to library functions and packages. The main difference is in how Lua scripts are invoked.

#invoke

The #invoke parser function is what bridges the gap between templates and module scripts. For example, the transcluded content of Template:Figurine is as follows:

{{#invoke:Figurine|Main}}

This invokes Module:Figurine, which could look something like:

local p = {}
local h = {}
local utilsGame = require("Module:UtilsGame")
...

function p.Main(frame)
    local args = frame:getParent().args
    local result = h.getFigurine(args[1], args[2])
    return result
end

function h.getFigurine(game, name)
...
end

return p

A page can invoke any function that is a field on the module's export table. The export table is the table object returned by the module, which is always named p by convention. In this case, Main is the only function in the export table.

args

Functions called via #invoke are passed a Frame object. frame.args is a table of the arguments to #invoke—there are none in the above example. However, frame:getParent().args represents the template arguments. For example, if a page has {{Figurine|TMC|Minish Ezlo}}, then frame:getParent().args[1] evaluates to the string TMC in that invocation.

The # operator and most other table functions don't work on frame.args.

require

A module can import another module and use its exported functions. This is done with the require function, as shown in the above example with Module:UtilsGame.

mw

Scribunto adds several libraries that are pre-loaded on all modules as the mw object. The following libraries are of note, in addition to the base functions:

Library Usage example
mw.text Module:List
mw.title Module:Subpage List
mw.html Module:Infobox

Testing and Debugging

You should never be in a situation where you're blindly submitting code and hoping that it works. The Geting Started exercise covers how to preview changes to a module. The following exercise after covers logging. The Gamepedia Help Wiki covers the other two debugging tools: the debug console and and script errors.

A good page to preview is the module's documentation page when it has use cases (as Module:UtilsMarkup/Documentation does, for example), or the corresponding template documentation when it has usage examples (e.g. Template:Term/Documentation). An effective way to develop modules is to make the documentation examples before the module itself, then using them as a preview target while making the actual module. (This is in fact a form of test-driven development.)

Utility Modules

A utility module is a library-type module meant to be used by other modules, rather than being invoked by a template. Most template-facing modules use at least one of these:

A list of utility modules is available at Category:Utility Modules. Leverage utility modules as much as possible so that the wiki's codebase stays DRY.

Higher-Order Functions

In utility modules such as Module:UtilsTable, you'll often see functions like these:

utilsTable.isEqual({})({})
> true

You might've expected the usual function syntax utilsTable.isEqual({}, {}) instead. The above is an example of a higher-order function—a function that returns a function. The function call above is shorthand for:

local isEmpty = utilsTable.isEqual({})
isEmpty({})
> true

Functions are written this way for increased reusability. They are often composed with the other type of higher-order function—one that accepts a function as an argument:

local isEmpty = utilsTable.isEqual({})
local notEmpty = utilsFunction.negate(isEmpty)
local magicWords = utilsTable.filter(notEmpty)({ {}, {}, {"Kooloo"}, {"Limpah"} })
utilsTable.flatten(magicWords)
> { "Kooloo", "Limpah" }

Exercises

Try the available exercises to test and develop your understanding of Lua, Scribunto, and Zelda Wiki's utilities.

Style

Once you are able to produce working code, make sure it adheres to Zelda Wiki's coding standards.

It is particularly important to use a consistent naming pattern, as plain text searching is the only way to observe function usage.

Importing

Use lower camelCase for imported utility modules
TFH Red Link desperate.png
local UtilsGame = require("Module:UtilsGame")
TFH Green Link ok.png
local utilsGame = require("Module:UtilsGame")
Avoid importing submodules unless absolutely necessary
TFH Red Link desperate.png
local utilsMarkupLink = require("Module:UtilsMarkup/Link")
TFH Green Link ok.png
local utilsMarkup = require("Module:UtilsMarkup")
Don't index the result of the require expression.
TFH Red Link desperate.png
local list = require("Module:UtilsMarkup").list
TFH Green Link ok.png If you must assign the function separately:
local utilsMarkup = require("Module:UtilsMarkup")
...
local list = utilsMarkup.list

Strings

Default to double quotes
TFH Green Link ok.png
local foo = "foo"
local bar = '<span class="baz">bar</span>'
TFH Red Link desperate.png
local foo = 'foo'