Extension:Cargo/Lua example

This module is called Module:ItemRecipes on the Rend wiki, but is commented here extensively. It’s an example of printing a table based on a Cargo query. The table it queries can be found here.

```
local lang = mw.getLanguage('en') -- this is a constant available to every function, and will be used to lowercase a variable below.
local p = {}
function p.main(frame) -- this is the function that's called from the page itself
	local args = require('Module:ProcessArgs').merge(true)

	-- everything above here lets us call this module either from a page via #invoke, or from another module, and have the arguments fed to the template be available in a table called args
	-- you should copy Module:ProcessArgs to your wiki to use it, if it's not already there

	local item = args[1] or mw.title.getCurrentTitle().text
	local result = p.doQuery(item) -- first thing is to do the cargo query, which is done in its own function; see comments on that function for more info
	if not result then -- if there was no result then return
		return '<span style="color:#ccc">\'\'No crafting recipes found.\'\'</span>'
	end

	-- now we format all of the data that we got from our result to prepare it for output
	local formatted = p.formatResult(result)

	-- now we return a table that prints all the formatted data
	return p.makeTable(formatted)
end

## -- cargo
-- the code to do the query is broken up into a bunch of functions
-- just so that it's easier to see what each piece does
-- this is the outer function, which actually does the query
-- and returns the result to the main function, or returns nil if there is no result
-- the query itself will return {} (an empty table) if there is no result to the query
function p.doQuery(item)
	--[[ the query takes 3 arguments:
 1) a list of tables (it expects a comma-separated list as a string)
 2) a list of fields to return (again, a comma separated list)
 3) a table with a bunch of optional values. in this case we give a "where" condition and an "orderBy."
 Mostly these parameters are the same as the ones available in a normal mediawiki Cargo query,
 but there are some differences in what they're called so that they are just 1 single word in Lua
 and can be written more clearly as the keys in a table]]
	local result = mw.ext.cargo.query('Recipes',
		p.cargoFields(),
		-- the fields that we want in this example is actually kind of long and annoying to construct,
		-- so it's done in its own function.
		-- all you need to know is that a string is returned that contains a list of fields
		{
			where = p.cargoWhere(item), -- again, constructing the 'where' is kind of annoying so it's done in its own function. all you need to know is that it returns a string.
			orderBy = 'tool'
		}
	)
	if #result == 0 then
		-- this just checks if the table is non-empty
		-- the following code:
		-- if result == {}
		-- will NOT work because Lua == when used on two tables only returns true if you are comparing
		-- one table to literally itself, and NOT another table that has the same structure & values as it
		-- however consider the following code:
		-- a = {}
		-- b = a
		-- in this case, a does equal b
		return nil
	else
		return result
	end
end

-- don't worry too much about the specifics of this function,
-- just know it returns a string with a list of fields we want to query
-- if we wanted to alias a field name,
-- we could do for example "result=result\_alias" instead of just "result"
-- and then that would be returned to us as row.result\_alias instead of row.result
-- when we do our for k, row in ipairs.... loop later on
function p.cargoFields()
	-- we'll first make a table with all of the fields in it
	local fields = {
		'result','resultAmount','tool'
	}
	-- there are a bunch of items & amounts and i don't want to type them all out so do a loop for the rest
	for i = 1,12 do
		fields[#fields+1] = 'item' .. i
		fields[#fields+1] = 'amount' .. i
	end
	return table.concat(fields,',') -- then concat the table into a comma-separated string and return it to the main function
end

-- also don't worry too much about the specifics of this function, just know it returns a where condition as a string
function p.cargoWhere(item)
	local where = {}
	for i = 1,12 do
		where[#where+1] = string.format('item%s="%s"', i, item)
	end
	return table.concat(where,' OR ')
end

## -- process data for output
-- in the end we want to print a table with three columns.
-- the function that does the actual printing should only need to print data to the page,
-- and not need to do any kind of processing on it anymore,
-- so we are doing the processing / formatting here,
-- saving it into a table called "formatted",
-- and then returning it to the main function where it will then be printed.
function p.formatResult(result)
	local formatted = {}
	-- this loop structure is pretty much always how you will process cargo data.
	-- you will loop over the ipairs iterator of the results table and apply a bunch of processing to the row, and save it.
	-- once it's been processed you will then print it.
	for k, row in ipairs(result) do
		formatted[k] = {
			-- each of these pieces of formatting is done in its own function for ease of reading.
			-- it's not too important to read them individually, just know how string.format works
			p.formatLinkAndImage(row.result, row.resultAmount),
			p.makeIngredientsTable(row),
			p.formatTool(row.tool)
		}
	end
	return formatted
end

function p.formatLinkAndImage(result, amount)
	return string.format("[[File:%s.png|40px|link=%s]] [[%s]] (%s)",
		result or '',
		result or '',
		result or '',
		amount or ''
	)
end

function p.makeIngredientsTable(row)
	local tbl = mw.html.create('table'):css({
		margin='-1px',
		width = 'calc(100% + 2px)',
		['border-collapse'] = 'collapse'
	})
	for i = 1, 12 do
		if row['item' .. i] ~= '' then
			tbl:tag('tr'):tag('td'):wikitext(p.formatLinkAndImage(row['item' .. i], row['amount' .. i]))
		end
	end
	return tostring(tbl)
end

function p.formatTool(tool)
	if lang:lc(tool) == 'by hand' then
		return '[[By Hand]]'
	else
		return string.format('[[File:%s.png|40px|link=%s]] %s',
			tool or '',
			tool or '',
			tool or ''
		)
	end
end

## -- make table for output
-- since we did all our processing above, literally all that's left to do is to make the table for output.
-- this is done using the mw.html library.
function p.makeTable(formatted)
	-- this table of styles corresponds to each of the columns
	local styles = {
		'', -- first column has no style
		{ padding = '0!important', }, -- second column has no padding
		'' -- third column has no style
	}
	local tbl = mw.html.create('table'):addClass('wikitable') -- create a wikitable html class
	tbl:tag('tr') -- and make its header row
		:tag('th'):wikitext('Result'):done()
		:tag('th'):wikitext('Ingredients'):done()
		:tag('th'):wikitext('Crafting Station'):done()
	for \_, row in ipairs(formatted) do -- now iterate over the formatted data
		tr = tbl:tag('tr') -- create a row
		for k, v in ipairs(row) do
			-- create each of the columns within the row,
			-- with the style we defined above and the wikitext as the row value we made in the earlier function
			tr:tag('td'):css(styles[k]):wikitext(v)
		end
	end
	return tbl -- done!
end

return p

```