 <?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mwstake.org/w/index.php?action=history&amp;feed=atom&amp;title=Lua_example</id>
	<title>Lua example - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mwstake.org/w/index.php?action=history&amp;feed=atom&amp;title=Lua_example"/>
	<link rel="alternate" type="text/html" href="https://mwstake.org/w/index.php?title=Lua_example&amp;action=history"/>
	<updated>2026-05-27T17:43:59Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.6</generator>
	<entry>
		<id>https://mwstake.org/w/index.php?title=Lua_example&amp;diff=2063&amp;oldid=prev</id>
		<title>Bryandamon: Created page with &quot; == Intro == This is not a guide to Lua syntax or behavior, and it's also not documentation. What it does instead is talk about a simple example of a common use case in MediaW...&quot;</title>
		<link rel="alternate" type="text/html" href="https://mwstake.org/w/index.php?title=Lua_example&amp;diff=2063&amp;oldid=prev"/>
		<updated>2020-04-03T18:17:36Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot; == Intro == This is not a guide to Lua syntax or behavior, and it&amp;#039;s also not documentation. What it does instead is talk about a simple example of a common use case in MediaW...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;
== Intro ==&lt;br /&gt;
This is not a guide to Lua syntax or behavior, and it's also not documentation. What it does instead is talk about a simple example of a common use case in MediaWiki. I'd suggest reading it as maybe a 2nd or 3rd source when trying to start coding in Lua for a wiki. My example uses Cargo, but you can do very similar things with SMW (or DPL?), or with no databasing extension at all. I also recommend the book ''Clean Code'' by Robert C. Martin. The examples in that book are in Java, but even if you don't know any Java you can still take away a lot of useful things from it.&lt;br /&gt;
&lt;br /&gt;
A couple things you should keep in mind about my coding style:&lt;br /&gt;
* &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; is always a table queried from Cargo&lt;br /&gt;
* &amp;lt;code&amp;gt;processed&amp;lt;/code&amp;gt; is a table created by processing that data&lt;br /&gt;
* &amp;lt;code&amp;gt;make&amp;lt;/code&amp;gt; as the first word of a function name means &amp;quot;create and return an mw.HTML object&amp;quot;&lt;br /&gt;
* &amp;lt;code&amp;gt;print&amp;lt;/code&amp;gt; means &amp;quot;add sections to the mw.html object that is the function's first argument&amp;quot;&lt;br /&gt;
* &amp;lt;code&amp;gt;tag&amp;lt;/code&amp;gt; means &amp;quot;add a self-contained unit to an mw.html object&amp;quot; (e.g. a footnote)&lt;br /&gt;
&lt;br /&gt;
There are more, but the point is a lot of these arbitrary-seeming names are in fact very deliberate, and globally consistent through my code base (or at least since I decided to start doing things like this). You should try to have internally-consistent conventions as much as possible so that someone reading your code knows what to expect. Have a reason for doing everything!&lt;br /&gt;
&lt;br /&gt;
== How to Invoke ==&lt;br /&gt;
For simplicity, on this page I use the &amp;lt;code&amp;gt;invoke&amp;lt;/code&amp;gt; parser function directly, e.g. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{#invoke:CatsAdoptedTable|main}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;. But usually you will want to put your invokes inside of templates so that users don't have to worry about the specific syntax - and also to add an extra layer between your call of the code and the definition of where the code is, so if you decide to reorganize things later and your function is no longer called &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;, it's just a single replacement to change it later. Making potential future changes easy is a constant theme in coding.&lt;br /&gt;
== Goal ==&lt;br /&gt;
Let's adopt some cats! Check out the data at [[Help:List of Adopted Cats]]. We want to build a table based on that data that looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| rowspan = 3 | Sunday || Luna&lt;br /&gt;
|-&lt;br /&gt;
|Chloe&lt;br /&gt;
|-&lt;br /&gt;
|Bella&lt;br /&gt;
|-&lt;br /&gt;
| Monday || Lucy&lt;br /&gt;
|}&amp;lt;/pre&amp;gt;&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| rowspan = 3 | Sunday || Luna&lt;br /&gt;
|-&lt;br /&gt;
|Chloe&lt;br /&gt;
|-&lt;br /&gt;
|Bella&lt;br /&gt;
|-&lt;br /&gt;
| Monday || Lucy&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As you can see, this is pretty easy to do but it requires that we know ahead of time how many cats were adopted each day so that we can set the correct rowspan. So what if we want to automate this data? Let's use Lua. You can check the final source at {{mod|CatsAdoptedTable}}.&lt;br /&gt;
&lt;br /&gt;
== Writing Our First Lua Module ==&lt;br /&gt;
Check out {{mod|HelloWorld}}. It contains the following code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local util_args = require('Module:ArgsUtil')&lt;br /&gt;
&lt;br /&gt;
local h = {}&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = util_args.merge(true)&lt;br /&gt;
	return args.kittens&lt;br /&gt;
end&lt;br /&gt;
return p&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
When we run this in the main wiki, we get the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;{{#invoke:HelloWorld|main|kittens=cute}}&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{#invoke:HelloWorld|main|kittens=cute}}&lt;br /&gt;
&lt;br /&gt;
Don't worry what &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;util_args.merge(true)&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; does. In fact, all of this is pretty much just boilerplate code other than the line &amp;lt;code&amp;gt;return args.kittens&amp;lt;/code&amp;gt;. You can use the rest as a copy-paste basis for everything (assuming you have a module like {{mod|ArgsUtil}} that grabs the template arguments for you on your wiki).&lt;br /&gt;
== Writing Our First Lua Module That Does Something ==&lt;br /&gt;
=== Cargo ===&lt;br /&gt;
First of all, just trust me that the following code will return us a Cargo table of the following form, where &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;day&amp;lt;/code&amp;gt; are the data values from the table we stored at [[Help:List of Adopted Cats]]:&lt;br /&gt;
&amp;lt;pre&amp;gt;{&lt;br /&gt;
	{ CatName = cat, Day = day },&lt;br /&gt;
	{ CatName = cat, Day = day },&lt;br /&gt;
	{ CatName = cat, Day = day },&lt;br /&gt;
	{ CatName = cat, Day = day },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function h.doQuery()&lt;br /&gt;
	local query = {&lt;br /&gt;
		tables = 'CatsAdopted',&lt;br /&gt;
		fields = {&lt;br /&gt;
			'Day',&lt;br /&gt;
			'CatName'&lt;br /&gt;
		},&lt;br /&gt;
		limit = 9999&lt;br /&gt;
	}&lt;br /&gt;
	return util_cargo.queryAndCast(query)&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can look at the Cargo documentation for how &amp;lt;code&amp;gt;mw.ext.cargo.query&amp;lt;/code&amp;gt; works, and {{mod|CargoUtil}} for how &amp;lt;code&amp;gt;util_cargo.queryAndCast&amp;lt;/code&amp;gt; works, but this page is about Lua not Cargo, so don't worry about it if you don't want to; this is just a convenient way to get some data.&lt;br /&gt;
=== Main Function ===&lt;br /&gt;
So let's write our main function first. Generally we want to separate code into 3 parts:&lt;br /&gt;
# Get the data (in this case, do the Cargo query)&lt;br /&gt;
# Manipulate the data&lt;br /&gt;
# Print the data&lt;br /&gt;
&lt;br /&gt;
These sections should be TOTALLY SEPARATE from each other: we don't want to get some data, then format it, then get more data, then format it, etc; nor do we want to construct our output object at the same time as we're manipulating the data. Sometimes there will be case-by-case situations where these steps blur together, but in instances of that happening, it's generally better to ''increase'' the number of distinct steps (maybe we manipulate then format then print, or maybe we get data then calculate totals then do the rest of the processing, etc) rather than decrease them.&lt;br /&gt;
&lt;br /&gt;
So here's a nice clean main function:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function p.main(frame)&lt;br /&gt;
	local args = mw.getCurrentFrame().args&lt;br /&gt;
	local data = h.doQuery(args)&lt;br /&gt;
	local processed = h.processData(data)&lt;br /&gt;
	return h.makeOutput(processed)&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Calling the main function is the only way we should ever interact with this module from elsewhere, so in fact let's go one step further and put this function as the only thing in our export table:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local p = {}&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = util_args.merge(true)&lt;br /&gt;
	local data = h.doQuery(args)&lt;br /&gt;
	local processed = h.processData(data)&lt;br /&gt;
	return h.makeOutput(processed)&lt;br /&gt;
end&lt;br /&gt;
return p&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All of the supporting functions we write will be &amp;quot;helper&amp;quot; functions or &amp;quot;private&amp;quot; functions that are only able to be accessed from within this single module, so we'll put them in a separate table called &amp;lt;code&amp;gt;h&amp;lt;/code&amp;gt; (h for &amp;quot;helper&amp;quot;) that won't be returned at the end.&lt;br /&gt;
&lt;br /&gt;
=== Code Part 1 ===&lt;br /&gt;
You can see this at {{mod|CatsAdoptedTable/Part 1}}.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local util_args = require('Module:ArgsUtil')&lt;br /&gt;
local util_cargo = require('Module:CargoUtil')&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local h = {}&lt;br /&gt;
&lt;br /&gt;
function h.doQuery()&lt;br /&gt;
	local query = {&lt;br /&gt;
		tables = 'CatsAdopted',&lt;br /&gt;
		fields = {&lt;br /&gt;
			'Day',&lt;br /&gt;
			'CatName'&lt;br /&gt;
		},&lt;br /&gt;
		limit = 9999&lt;br /&gt;
	}&lt;br /&gt;
	return util_cargo.queryAndCast(query)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.processData(data)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.makeOutput(processed)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = util_args.merge(true)&lt;br /&gt;
	local data = h.doQuery()&lt;br /&gt;
	local processed = h.processData(data)&lt;br /&gt;
	return h.makeOutput(processed)&lt;br /&gt;
end&lt;br /&gt;
return p&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can run this and it will run! It just won't return anything interesting yet.&lt;br /&gt;
&amp;lt;pre&amp;gt;{{#invoke:CatsAdoptedTable/Part 1|main}}&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{#invoke:CatsAdoptedTable/Part 1|main}}&amp;lt;br&amp;gt;&lt;br /&gt;
(The line above this contains the output. Yes, it's empty. But it's not throwing any error!)&lt;br /&gt;
=== Designing A Data Structure ===&lt;br /&gt;
When you're using MediaWiki parser functions to do stuff, you have arrays that you can manipulate a bit, but it's super awkward to do that much with them. Imagine the pain of working with an array where each element itself is an array! And if you wanted to have keyed data instead of ordered data, well...you can't, so you'd need a separate object for each sub-array, with the keys stored in their own array, and.......yeah no. In Lua, though, it's super easy to set up nice data structures. I'll talk about one I like in particular:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;{&lt;br /&gt;
	'Sunday', 'Monday', 'Tuesday',&lt;br /&gt;
	Sunday = {},&lt;br /&gt;
	Monday = {},&lt;br /&gt;
	Tuesday = {},&lt;br /&gt;
	etc.&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we have an ordered list of keys Sunday, Monday, etc, and their data are also in the same table. This is nice because we can iterate over the integer keys with &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt; would iterate over both integer and non-integer keys). Now we can sort the integer keys using &amp;lt;code&amp;gt;table.sort&amp;lt;/code&amp;gt;, and then print stuff in the order we want.&lt;br /&gt;
&lt;br /&gt;
This kind of &amp;quot;ordered dictionary&amp;quot; turns out to be a useful structure to return to frequently, particularly when working with Cargo data that gets grouped together into &amp;quot;containers,&amp;quot; like this cat-adoption data.&lt;br /&gt;
&lt;br /&gt;
=== Processing Our Data ===&lt;br /&gt;
We need to take our one-cat-per-row table and turn it into a one-day-per-row table, with a sub-table that contains a list of cats.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function h.processData(data)&lt;br /&gt;
	local processed = {}&lt;br /&gt;
	for _, row in ipairs(data) do&lt;br /&gt;
		local day = row.Day&lt;br /&gt;
		local cat = row.CatName&lt;br /&gt;
		local dayTable = processed[day] -- dayTable is now an alias for processed[day]&lt;br /&gt;
		if dayTable then&lt;br /&gt;
			dayTable[#dayTable+1] = cat -- Lua doesn't have a method like .append, instead you literally just say &amp;quot;make the value at 1 more than the current length this&amp;quot;&lt;br /&gt;
		else&lt;br /&gt;
			processed[#processed+1] = day&lt;br /&gt;
			processed[day] = { cat }&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	table.sort(processed, h.sortByWeekOrder)&lt;br /&gt;
	return processed&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So what we do here is to go through each row in our data, extract the day and cat name, and then do one of two things:&lt;br /&gt;
* If this is the first cat on a new day, then add that day to our table, and add the cat as the first cat in that day, or&lt;br /&gt;
* If not then just add the cat to the existing day-list-of-cats.&lt;br /&gt;
&lt;br /&gt;
After we're done with that we just sort the table.&lt;br /&gt;
&lt;br /&gt;
=== Sorting The Table ===&lt;br /&gt;
We just hard code the list of days of the week at the start of the module. Depending on your wiki, you may want to have some &amp;quot;libraries&amp;quot; that just contain ordered lists to order by, like day of the week or month of the year etc. You could also in this case import some time library and use a function for that, but we'll just do it ourselves.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local DAY_ORDER = {&lt;br /&gt;
	'Sunday',&lt;br /&gt;
	'Monday',&lt;br /&gt;
	'Tuesday',&lt;br /&gt;
	'Wednesday',&lt;br /&gt;
	'Thursday',&lt;br /&gt;
	'Friday',&lt;br /&gt;
	'Saturday'&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local DAY_ORDER_BETTER = {&lt;br /&gt;
	Sunday = 1,&lt;br /&gt;
	Monday = 2,&lt;br /&gt;
	Tuesday = 3,&lt;br /&gt;
	Wednesday = 4,&lt;br /&gt;
	Thursday = 5,&lt;br /&gt;
	Friday = 6,&lt;br /&gt;
	Saturday = 7&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function h.sortByWeekOrder(day1, day2)&lt;br /&gt;
	return DAY_ORDER_BETTER[day1] &amp;lt; DAY_ORDER_BETTER[day2]&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you might notice I have two different ordering tables here. The second is just easier to work with, and in fact I have a generic library function which takes a table like the first, transforms it to a table like the second, and then sorts your array by it. But for this example I wrote out the steps here instead of using a &amp;quot;built-in&amp;quot; function.&lt;br /&gt;
&lt;br /&gt;
=== Printing Output ===&lt;br /&gt;
So at this point in time, we haven't had to say a single thing about how we're presenting the data. We could be making a table, an ordered list, an unordered list, even a calendar - everything up until this point would still be exactly the same. This is really nice because it means if our requirements on data presentation change in the future, we can just change one small block of code instead of the entire module. Yay!&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function h.makeOutput(processed)&lt;br /&gt;
	local tbl = mw.html.create('table')&lt;br /&gt;
		:addClass('wikitable')&lt;br /&gt;
	for _, day in ipairs(processed) do&lt;br /&gt;
		h.printDay(tbl, day, processed[day])&lt;br /&gt;
	end&lt;br /&gt;
	return tbl&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We aren't doing much so far but that's okay, we'll put more specific details in &amp;lt;code&amp;gt;h.printDay&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The next thing I wrote was actually not &amp;lt;code&amp;gt;h.printDay&amp;lt;/code&amp;gt; but rather &amp;lt;code&amp;gt;h.printCat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function h.printCat(tr, cat)&lt;br /&gt;
	tr:tag('td'):wikitext(cat)&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The reason I wrote this first was that there are two separate cases in &amp;lt;code&amp;gt;h.printDay&amp;lt;/code&amp;gt;: 1) the first cat in the day; and 2) subsequent cats in the day. The latter case will require creating a new row, whereas the former just adds a cell to an already-existing row.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function h.printDay(tbl, day, catList)&lt;br /&gt;
	local tr = tbl:tag('tr')&lt;br /&gt;
	h.printDayName(tr, day, #catList)&lt;br /&gt;
	local cat = table.remove(catList,1)&lt;br /&gt;
	h.printCat(tr, cat)&lt;br /&gt;
	for _, thiscat in ipairs(catList) do&lt;br /&gt;
		local trSub = tbl:tag('tr')&lt;br /&gt;
		h.printCat(trSub, thiscat)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.printDayName(tr, day, rowspan)&lt;br /&gt;
	tr:tag('td')&lt;br /&gt;
		:attr('rowspan', rowspan)&lt;br /&gt;
		:wikitext(day)&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So here's the actual job of printing out the entire table. We add a row, print the day cell with the rowspan (which is of course the total number of cats), and then print the first cat. Because we don't need to reuse this table ever, it's convenient to actually remove that cat from the table and then we can just use &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt; over the entire remainder of the table, but it's not necessary to do it that way.&lt;br /&gt;
&lt;br /&gt;
Here's an alternative way to write the same function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function h.printDay(tbl, day, catList)&lt;br /&gt;
	for i, cat in ipairs(catList) do&lt;br /&gt;
		local tr = tbl:tag('tr')&lt;br /&gt;
		if i == 1 then&lt;br /&gt;
			h.printDayName(tr, day, #catList)&lt;br /&gt;
		end&lt;br /&gt;
		h.printCat(tr, cat)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function h.printDayName(tr, day, rowspan)&lt;br /&gt;
	tr:tag('td')&lt;br /&gt;
		:attr('rowspan', rowspan)&lt;br /&gt;
		:wikitext(day)&lt;br /&gt;
end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can see that we still do the same thing of treating index 1 as a special case, and the function to print the day name is exactly the same, but now we make the loop more inclusive. Convince yourself these do exactly the same.&lt;br /&gt;
=== Final Result ===&lt;br /&gt;
And that's it! Check {{mod|CatsAdoptedTable}} for code. There are some blocks commented out based on what we talked about above.&lt;br /&gt;
&lt;br /&gt;
Here's the final table:&lt;br /&gt;
&lt;br /&gt;
{{#invoke:CatsAdoptedTable|main}}&lt;br /&gt;
== Challenge ==&lt;br /&gt;
See if you can adjust the output functions to print a list instead of a table. You can feel free to create modules on this wiki, but they might be deleted in cleanups on occasion. Please use your user space for anything other than modules.&lt;/div&gt;</summary>
		<author><name>Bryandamon</name></author>
	</entry>
</feed>