Introduction
You’ve probably seen something like the following:
{{MatchRecapS8 |gamename=Game 1 /* snip */
|blue1={{MatchRecapS8/Player|id= |champion= /* snip */}}
|blue2={{MatchRecapS8/Player|id= |champion= /* snip */}}
|blue3={{MatchRecapS8/Player|id= |champion= /* snip */}}
|blue4={{MatchRecapS8/Player|id= |champion= /* snip */}}
Each {{MatchRecapS8/Player}}
template will do some formatting like print a table row and maybe update some global variables along its way, and {{MatchRecapS8}}
will insert those table rows in the correct place in the table, do some other stuff with its “direct” args, and print the overall result.
In wikitext, this is a very standard “design pattern” of sorts, and there’s no real reason to disrupt it - even if you were capable of sending each child template parameter individually to the parent, wikitext’s support for loops and arrays isn’t solid enough for your resulting awkward syntax to be worth it.
All this changes when you start using Lua, however - once your code is in Lua, you absolutely want all of your child arguments available to you directly for arbitrary computations, and a system like the above would be crippling compared to what the language supports. So how can we send individual child template arguments to the body of our code?
In other words, what we have right now is siloed individual templates that each has its own |id=
, |champion=
, etc, and are unable to communicate with each other. These individual parameters of |id=
etc are also unavailable to the parent template. What we want, and what becomes practical to arrive at in Lua, is a setup in which there is only a single context in which we have a list of id
s and a list of champion
s, etc, so that we can do any computations and displays needed.
In this article I’ll go through a couple different possible approaches to the issue of making these parameters available to the “parent” (I use this term very informally), including code examples from Leaguepedia.
(Note: All code in this post was originally written by me for Leaguepedia and is licensed under CC BY-SA 3.0.)
A mediocre solution - numbered arguments
One option is to eliminate the child templates completely and use a system like the following:
{{TeamRoster|team=Team Liquid|footnoteteamn=3
|player1= Quas|flag1= ve |role1=top
|player2= IWDominate|flag2= us |role2=jungle
|player3= FeniX|flag3= kr |role3=mid
|player4= KEITH|flag4= us |role4=ad
|player5= Piglet|flag5= kr |role5=ad
|player6= Xpecial|flag6= us |role6=support
|player7=Peter |link7= Peter (Peter Zhang)|flag7=cn |role7=coach
}}
In Lua, you can then do something like the following to retrieve args:
|
|
There’s a pretty big UX problem with this, though - what if we wanted to insert a second mid laner after FeniX? Then we need to move KEITH, Piglet, Xpecial, and Peter down - which means re-numbering at least twelve args by hand - not a pleasant experience without automated tooling, which is unlikely to exist for typical editors making typical edits to typical pages.
So while our code is pretty happy about the current state of affairs, our users most certainly are not. Let’s do better.
Serializing our child templates
The solution is to go back to child templates, serialize our inputs as strings, and then deserialize to tables in Lua. Going back to our {{MatchRecapS8/Player}}
example above, here’s the code at that template (ignore {{Pentakills/CargoAttach}}
):
<includeonly>{{{champion|}}};;;{{{link|}}};;;{{{name|}}};;;{{{summonerspell1|}}},{{{summonerspell2|}}};;;{{{item1|}}},{{{item2|}}},{{{item3|}}},{{{item4|}}},{{{item5|}}},{{{item6|}}};;;{{{trinket|}}};;;{{{kills|}}};;;{{{deaths|}}};;;{{{assists|}}};;;{{{gold|}}};;;{{{cs|}}};;;{{{keystone|}}};;;{{{secondary|}}};;;{{{pentakills|}}};;;{{{pentakillvod|}}};;;{{{nocargo|}}};;;{{{skillletter|}}};;;{{{skillimage|}}}{{Pentakills/CargoAttach}}</includeonly><noinclude>{{documentation|cargodec=
{{attach|ScoreboardPlayers}}
}}[[Category:Scoreboard Templates]]</noinclude>
Now we have a utility function called util_args.splitArgs
:
|
|
And in Module:ScoreboardAbstract
we have:
|
|
You can see the similarity in approach to the Module:TeamRoster
example from the introduction - we have an array of args that we want to extract, in the order that we expect them in, and we’re going to generate a row in an array of child args. But whereas before we relied on individually numbering every single child arg, now we only have to number the entire child arg row one time. A huge improvement!
The called wikicode will look identical to that in the first example, once again:
{{MatchRecapS8 |gamename=Game 1 /* snip */
|blue1={{MatchRecapS8/Player|id= |champion= /* snip */}}
|blue2={{MatchRecapS8/Player|id= |champion= /* snip */}}
|blue3={{MatchRecapS8/Player|id= |champion= /* snip */}}
|blue4={{MatchRecapS8/Player|id= |champion= /* snip */}}
Note that my choice of ;;;
as the splitter is pretty arbitrary. You can use anything you want, as long as you’re really, really confident it’ll never come up in any actual string that you’re processing. Also, in the interest of performance, it probably shoudln’t contain any unicode characters, so that you can avoid using the ustring
library for your split.
Going one step further - no numbered args at all
In some situations we may consider it appropriate to forego naming or numbering of child templates altogether.
For example, consider this input:
|pre={{RCPlayer|player=Johnsun |role=bot |status= |trainee=yes}}
{{RCPlayer|player=Grig |role=j |status= |move_type=to_main}}
{{RCPlayer|player=Fanatiik |role=j |status= |move_type=confirm}}
|post={{RCPlayer|player=Spica |role=j |status= |move_type=confirm}}
{{RCPlayer|player=Johnsun |role=bot |status= |sub=yes}}
Here we have a list of three players as pre
and two players as post
, but they arent really player1
, player2
, and player3
, they’re just, three players.
In this case, the <includeonly>
portion of my {{RCPlayer}}
template looks like this:
{{{player|}}};;;{{{role|}}};;;{{{status|}}};;;{{{loaned_from|}}};;;{{{loaned_to|}}};;;{{{move_type|}}};;;{{{custom|}}};;;{{{contract_until|}}};;;{{{assistance|}}};;;{{{event|}}};;;{{{replacing|}}};;;{{{reason|}}};;;{{{phase|}}};;;{{{sub|}}};;;{{{trainee|}}};;;{{{rejoin|}}};;;{{{order|}}};;;{{{sentence_group|}}};;;{{{leave_date|}}};;;{{{nolink|}}};;;{{{remain_for|}}};;;{{{remain_for_link|}}};;;{{{already_joined|}}};;;{{#or:{{{team_priority|}}}|1}};;;{{{sister_team|}}};;;{{{reserve|}}};;;{{{changed_on_team_rename|}}}:::
Note the :::
at the end.
And I have a different Lua function for it:
|
|
Here, h.splitArgs
references the same function as it does above; actually, now I should confess that the function I showed in the section above is actually private (as you might have guessed from its inclusion in the table h
rather than p
). The public version is:
|
|
The reason for this wrapper has to do with how empty cases are handled; it was causing a problem when a public splitArgsArray
was calling a public splitArgs
, though to be honest I can’t remember precisely what the problem was, so now the public dependency is in the other direction.
For completeness, here’s the entire section of code from Module:ArgsUtil
, though I have previously shown all three of these functions:
|
|
And here’s util_map.split
:
|
|
util_text.split
is a more performant version of the built-in mw.text.split
with a bit of extra handling for cases of empty strings etc.
Here’s how util_args.splitArgsArray
is called:
|
|
(OD
stands for OrderedDictionary
and is my implementation of an Ordered Dictionary data type in Lua.)
Once again, my choice of :::
to divide individual players / instances of the child template from each other is pretty arbitrary; just pick something that will be globally unique. But if you want to follow my conventions, then ;;;
divides individual arguments, and :::
divides instances of multi-instance child templates.
Conclusion
A common design pattern when working with Lua is to deliver multi-instance child-template data from MediaWiki markup to Lua by first serializing the user input and then deserializing in Lua. To do this, I use ;;;
as my parameter separator, and, if applicable, I will separate instances of templates with :::
.
Links to example templates and sample code (though remember all of this is subject to change):
- Template:RCPlayer, used by Module:NewsUtil
- Template:Scoreboard/Player, used by Module:ScoreboardAbstract
- Template:TeamRoster/Line, used by Module:TeamRoster
- Module:ArgsUtil, containing processing functions