This page looks best with JavaScript enabled

A Lua module to print all args

 ·  ☕ 7 min read

I was recently asked to debug a non-functioning Cargo query on the Gamepedia Undermine wiki. It was entirely in wikitext, using |format=template, and I have to admit that it took me a rather embarrassingly long time to figure out what was going on, given how simple the bug ended up being in the end. As part of my debugging process, I wrote a tiny but extremely useful Lua module that everyone should have available to them specifically for the purpose of debugging |format=template Cargo queries, so I’m presenting this module here.

(Surprise, I didn’t write this code for Leaguepedia! I wrote it for the Undermine wiki, which is licensed under CC BY-NC-SA 3.0.)

Motivation

One thing that a MediaWiki template cannot do, but a Lua module can, is to discover and examine its entire set of arguments. The question, “wtf did the user/process/function/Cargo query provide me with?” is really important to be able to answer, when your code is doing unexpected things (and also when writing tests to verify that your code is doing expected things), and MediaWiki templates are incapable of providing the answer. Fortunately, not only is it possible to do in Lua, but it’s very easy! So we can just fallback to a Lua module to get our answer any time we need to ask this question.

Lua code

Save the following at Module:PrintAllArgs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
local p = {}

function p.main(frame)
  local args = frame:getParent().args
  local ret = ''
  for k, v in pairs(args) do
    ret = ret .. ',' .. k .. ',' .. v
  end
  return ret
end

return p

River, don’t you write your code with tabs?? Yes, yes I do. (Unless it’s Python.) I wrote this in my browser textarea, not Sublimetext, because this wiki wasn’t configured in my preferences. So, two spaces for indentation.

Explanation

Let’s go through line by line.

1
  local args = frame:getParent().args

In order to be super portable and completely dependency-free, I don’t bother doing anything with a special args-getter module; instead I just pull the parent’s frame’s args. This means that you will ALWAYS have to put this module inside of a template before calling it, and you can’t instead just write {{#invoke:PrintAllArgs|main}}. But this is not a problem, since we’re working with format=template Cargo queries anyway, so we’d always wrap our invoke inside of a template call.

If you don’t understand this, don’t worry. I’ll explain later how to call the module.

1
  local ret = ''

ret stands for return. I like this convention a lot, and it’s pretty standard in Lua.

1
  for k, v in pairs(args) do

Start a for loop. The choice of k and v as the variables is pretty generic: k stands for “key,” and v stands for “value.” Similar to ret meaning return, this is also pretty standard in Lua. Sometimes you would want to have more specific variable names, but here they really are extremely generic things, and this for loop is really short, and there’s not that much to be confused about, so k and v are just fine.

1
    ret = ret .. ',' .. k .. ',' .. v

I’m being EXTREMELY lazy here. String concatenation is extremely inefficient. As that StackOverflow answer notes, BY FAR the best way to perform repeated string concatenation is to put your individual strings into a table, and then concatenate once at the end.

Why did I do it this way if it sucks so much?

  • Laziness when initially writing this code - like I said, I wrote it in the browser because the Undermine wiki wasn’t configured in Sublimetext, and I didn’t want to write the couple extra characters to do the table concatenation
  • Simplicity now when presenting this code; I want to make it as understandable as possible
  • Illustration that performance doesn’t matter when you are writing diagnostic testing code
  end
  return ret

Technically two lines here, but, we end the loop and then return the string.

Alternative code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
local p = {}

function p.main(frame)
  local args = frame:getParent().args
  local tbl = {}
  for k, v in pairs(args) do
    tbl[#tbl+1] = k
    tbl[#tbl+1] = v
  end
  return table.concat(tbl, ', ')
end

return p

Explanation

This just optimizes the string concatenation issue mentioned earlier.

You could also structure the internals of the loop however you like. Maybe you want something like, to more easily distinguish which is the key and which is the value in each of the key-value pairs:

1
    tbl[#tbl+1] = ('%s: %s'):format(k, v)

The original quick thing I threw together was totally sufficient for my needs at the time, but sometimes you need a bit more structure. If you’re just starting out with Lua, playing around with a bit of string formatting in a module like this is a great way to learn a bit more!

How to invoke it

Make a template called Template:PrintAllArgs. It should have the following source:

<includeonly>{{#invoke:PrintAllArgs|main}}</includeonly><noinclude>{{documentation}}</noinclude>

If you like, you can also just write the documentation directly on this template page, since it won’t be transcluded much (so editing it won’t cause a high jobqueue), and it’s a very simple template. Here’s my version:

<includeonly>{{#invoke:PrintAllArgs|main}}</includeonly><noinclude>Use this module as the <code>format=template</code> for a Cargo query to debug.</noinclude>

To use the debugger you have just built, simply make PrintAllArgs the template in your format=template query:

{{#cargo_query:table=Item_IDs
|fields=name,item_guid,potion_guid,effect_guid,upgrade_guid
|where=name="absolution"
|format=template
|template=PrintAllArgs
|named args=yes
}}

PrintAllArgs used on the Undermine Gamepedia wiki

Transcription of the output:

,upgrade guid,,name,absolution,effect guid,,potion guid,a07b25919d62486c9455501a06488ff9,item guid,28737e052b414273a67aca33267fc2e7 

And…aha! The issue was that the original template was using UNDERSCORES in its parameters instead of spaces! A quick, easy fix, now that I have some visibility into what’s going on!

Bonus: How would I write this on Leaguepedia?

It’s no secret that I have a TON of utility library modules on Leaguepedia. If I wanted to write a similar module on Leaguepedia, here’s what I’d do:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
local util_args = require('Module:ArgsUtil')
local util_vars = require('Module:VarsUtil')
local p = {}

function p.main(frame)
  local args = util_args.merge()
  util_vars.log(args)
end

return p

Then when I called it I’d print {{#var:log}} in wikitext after the Cargo query to expose the contents of this variable.

Is this technically writing more code because of the imports? Well…if I create a new “empty” module in Sublimetext, here’s what I get for free:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local util_args = require('Module:ArgsUtil')
local util_cargo = require("Module:CargoUtil")
local util_html = require("Module:HtmlUtil")
local util_map = require('Module:MapUtil')
local util_table = require("Module:TableUtil")
local util_text = require("Module:TextUtil")
local util_vars = require("Module:VarsUtil")
local i18n = require("Module:I18nUtil")
local lang = mw.getLanguage('en')
local h = {}
local p = {}
function p.main(frame)
	local args = util_args.merge()
	i18n.init('PrintAllArgs')
end
return p

So I’d have to write one line of code: util_vars.log(args), delete the i18n that I’m not creating or using, and then optionally delete unnecessary imports and I’m done.

Of course, I’m logging rather than printing, so I also need to type the {{#var:log}} after my query, but really that’s more in line with my workflow regardless, so no problem there really.

Conclusion

Debugging MediaWiki code is hard for a lot of reasons, not least of which is the lack of access to any kind of code profiling tool or debugger or ability to set breakpoints or step through code and check values, etc. But we can sometimes build some tools here and there that can approximate some of these things, and this module gives us a glimpse at what some values are when they are fed to our template in format=template Cargo queries. If you work with such queries, I definitely recommend copying this code to your wiki and adapting it to your needs, and as a bonus you’ll get a bit of Lua code to play with!

Share on

river
WRITTEN BY
River
River (RheingoldRiver) is a MediaWiki developer and the manager of Leaguepedia. She likes cats.


What's on this Page