Module:RenderRecipe: Difference between revisions
From Against the Storm Official Wiki
No edit summary |
m making numbers in swappable ingredients bold too |
||
| (45 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- | -- This module renders the {{Recipe}} template. | ||
-- https://hoodedhorse.com/wiki/Against_the_Storm/Template:Recipe. | |||
-- | -- | ||
-- | -- The template #invokes RenderRecipe.renderRecipe(frame), below. | ||
-- | -- | ||
-- The template requires at least one of its two arguments. The first is called | |||
-- | -- "product". and the second is called "building". Both are passed to | ||
-- | -- Module:RecipeData module, which returns one or more recipes. | ||
-- | -- | ||
-------- | -- Using the recipe data returned, this module creates an HTML table that | ||
-- displays the recipes, which replaces the template, whereever it is written | |||
-- in the wiki markup. The table will have exactly one row if both the product | |||
-- and building are specified. If either is missing, then expect a table with | |||
-- multiple rows. | |||
-- @module RenderRecipe | |||
local RenderRecipe = {} | local RenderRecipe = {} | ||
-- | |||
-- Dependencies | |||
-- | |||
RecipeData = RecipeData or require("Module:RecipeData") -- used to get recipe data | |||
-- | |||
-- Constants | -- Constants | ||
---- | -- | ||
local | -- CSS classes used to style the recipe tables | ||
local CSS_CLASS_REQUIRED_INGREDIENT = | local CSS_CLASS_WIKITABLE_SORTABLE = "wikitable sortable" | ||
local CSS_CLASS_SWAPPABLE_INGREDIENT = " | local CSS_CLASS_RECIPE_TABLE = "ATSrecipe" | ||
local CSS_CLASS_RECIPE_CAPTION = "ATSrecipecaption" | |||
local CSS_CLASS_REQUIRED_INGREDIENT = "ATSrequired" | |||
local CSS_CLASS_SWAPPABLE_INGREDIENT = "ATSswappable" | |||
local CSS_CLASS_EMPTY_INGREDIENT = "ATSempty" | |||
-- the names of other templates used, by way of frame:expandTemplate | |||
local TEMPLATE_BUILDING_LINK = "Building_link" | local TEMPLATE_BUILDING_LINK = "Building_link" | ||
local TEMPLATE_RESOURCE_LINK = "Resource_link" | local TEMPLATE_RESOURCE_LINK = "Resource_link" | ||
local TEMPLATE_STAR_SUFFIX = "Star" | local TEMPLATE_STAR_SUFFIX = "Star" | ||
-- HTML markup | |||
local BR = "<br />" | local BR = "<br />" | ||
local NL = "\n" | local NL = "\n\n" | ||
-- | |||
-- Class Variables | |||
-- | |||
local thisFrame = {} -- save the frame on first call for later use in expandTemplate | |||
-- | |||
-- Main rendering functions | |||
-- | |||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- | -- Renders an HTML table given the MediaWiki frame (which contains the | ||
-- arguments given to the template). | |||
-- | -- | ||
------------------ | -- Uses the MediaWiki frame to access the template arguments. One or both | ||
-- | -- arguments are required; calling this template with no arguments or empty | ||
-- strings will result in an error message. | |||
-- @param frame a table describing the MediaWiki frame; frame['args'] is a table | |||
-- containg the template arguments; frame['args']['product'] is the first | |||
-- argument, assigned to the key 'product'; frame['args']['building'] is the | |||
-- second argument, assigned to the key 'building' | |||
-- @return a string containing the entire table in HTML markup (or an error | |||
-- message if a problem occured) | |||
function RenderRecipe.renderRecipe(frame) | function RenderRecipe.renderRecipe(frame) | ||
-- need to set the class variable so expandTemplate can work later | |||
thisFrame = frame | thisFrame = frame | ||
local | -- extract the arguments we care about from the frame | ||
local | local argProduct = frame.args.product | ||
local argBuilding = frame.args.building | |||
if | -- build the table; we'll add to this once we know the arguments are valid, but that's after the if/then blocks start | ||
local wikiTable = mw.html.create('table'):addClass(CSS_CLASS_WIKITABLE_SORTABLE):addClass(CSS_CLASS_RECIPE_TABLE) | |||
-- save for later once we check the variables | |||
local wikiTableCaption = wikiTable:tag('caption'):addClass(CSS_CLASS_RECIPE_CAPTION) | |||
-- build the header row; don't need to save it | |||
wikiTable:tag('tr') | |||
:tag('th'):wikitext("Building"):done() | |||
:tag('th'):wikitext("Ingredient #1"):done() | |||
:tag('th'):wikitext("Ingredient #2"):done() | |||
:tag('th'):wikitext("Ingredient #3"):done() | |||
:tag('th'):wikitext("Product") | |||
-- use this to store any error messages that return from the inner function | |||
local errorMessage = "" | |||
-- | -- | ||
-- Everything else depends on whether the arguments are valid and which ones were specified. | |||
-- | |||
-- | -- if both product and building are specified (and valid), then we can assume exactly one recipe at one building | ||
if | if argProduct and argProduct ~= "" and argBuilding and argBuilding ~= "" then | ||
if not | -- ask for the matching recipe and make sure there was one | ||
return " | local tOneRecipe = RecipeData.getRecipeAtBuilding(argProduct, argBuilding) | ||
if not tOneRecipe then | |||
return "RenderRecipe error: no product '" .. argProduct .. "' or building '" .. argBuilding .. "' found." | |||
end | end | ||
wikiTableCaption:wikitext(argProduct .. " recipe in the " .. argBuilding .. ".") | |||
-- assume only one row to render, and renderTableRow will use only the first place in the recipe anyway | |||
wikiTable, errorMessage = RenderRecipe.renderTableRow(tOneRecipe, wikiTable) | |||
-- if only the product is specified, then we need to assume multiple buildings | |||
elseif argProduct and argProduct ~= "" then | |||
if not | -- gets a stack of buildings for the given product, and make sure there was one | ||
return " | local tRecipeStack = RecipeData.getRecipeForProduct(argProduct) | ||
if not tRecipeStack then | |||
return "RenderRecipe error: no product '" .. argProduct .. "' found." | |||
end | end | ||
-- | wikiTableCaption:wikitext(argProduct .. " recipe.") | ||
for | -- there are multiple places (1-6) to loop through; places have no key, so ipairs works | ||
for _, place in ipairs(tRecipeStack.places) do | |||
-- add | -- make a simple version of tRecipeStack with just the one matching place to the table renderer | ||
if | -- recipes are {product, pattern, place} | ||
local simplerRecipe = { product=argProduct, pattern=tRecipeStack.pattern, places={place} } | |||
-- each loop calls renderTableRow to add a new row to the passed table | |||
wikiTable, errorMessage = RenderRecipe.renderTableRow(simplerRecipe, wikiTable) | |||
end | |||
-- if only the building is specified, then we need to assume multiple products, but each has exactly one place | |||
elseif argBuilding and argBuilding ~= "" then | |||
-- gets a table of different products' recipes, and make sure there was one | |||
local tProducts = RecipeData.getBuildingsRecipes(argBuilding) | |||
if not tProducts then | |||
return "RenderRecipe error: no building '" .. argBuilding .. "' found." | |||
end | |||
wikiTableCaption:wikitext("Recipes in the " .. argBuilding .. ".") | |||
-- there are multiple products to loop through; the products have a string key, so pairs is better than ipairs | |||
for _, tRecipe in pairs(tProducts) do | |||
-- each call to renderTableRow adds a new row to the passed table | |||
wikiTable, errorMessage = RenderRecipe.renderTableRow(tRecipe, wikiTable) | |||
end | end | ||
-- something was bad about the arguments; can't print them since they may be nil | |||
else | |||
return "RenderRecipe error: Invalid template parameters. This template requires at least a product or building." | |||
end | end | ||
return | -- check whether an error message was returned; if so, append it to the | ||
-- HTML table wherever it is right now | |||
if errorMessage and "" ~= errorMessage then | |||
wikiTable:wikitext(errorMessage) | |||
end | |||
-- finally, make sure we're returning an actual string, not a Lua table | |||
return mw.allToString(wikiTable) | |||
end | end | ||
------------------------------------------------------------------------------- | |||
-- Writes a new row of HTML tags and content to the provided wiki table. | |||
-- | |||
-- Using the provided recipe (combination of a product and a building), this | |||
-- method uses the provided HTML table object to add an additional row | |||
-- containing the relevant content of the recipe. This function uses only the | |||
-- first 'place' (building) where the product can be made; any others are | |||
-- ignored for a single call to this function. | |||
-- | |||
-- @param tRecipe a table of a recipe {product, pattern, places} | |||
-- @param wikiTable a table containing an HTML-markup table that has already | |||
-- been started, to which this function will add a new row, cells, and content. | |||
-- @return the updated wikiTable | |||
-- @return an error message or nil if successful | |||
function RenderRecipe.renderTableRow(tRecipe, wikiTable) | |||
-- | -- verify we have valid things to work with | ||
if not tRecipe and not wikiTable then | |||
return nil, nil | |||
if not tRecipe then | |||
return nil | |||
end | end | ||
-- call the helper | -- call the helper function to do the extraction and error checking | ||
local errorMessage, building, stars, speed, product, number, ingredients, quantities = RenderRecipe.extractRecipeData(tRecipe) | |||
local building, stars = | if errorMessage and "" ~= errorMessage then | ||
if | return wikiTable, errorMessage | ||
end | end | ||
-- | |||
-- with the data extracted, start writing the HTML tags and content | |||
-- | |||
local | -- write the first cell with the building | ||
local wikiTableDataRow = wikiTable:tag('tr') | |||
:tag('td') | |||
:wikitext(RenderRecipe.blTemplate(building) .. BR .. RenderRecipe.starTemplate(stars) .. BR .. speed) | |||
-- | -- first fill in any empty cells like in the recipe book, one for each | ||
-- | -- blank ingredient | ||
local ingredients | local blanks = 3 - #ingredients | ||
for h = 1,blanks do | |||
wikiTableDataRow:tag('td'):addClass(CSS_CLASS_EMPTY_INGREDIENT) | |||
end | end | ||
-- simultaneously loop over the groups of alternative ingredients and | |||
-- quantities; so use a group of ingredients and a groupOfQuantities each | |||
-- the groups have no keys, so ipairs ensures we are iterating over them | |||
-- | -- in a consistent order with the quantities | ||
-- so use | |||
for i, group in ipairs(ingredients) do | for i, group in ipairs(ingredients) do | ||
local groupOfQuantities = quantities[i] | local groupOfQuantities = quantities[i] | ||
-- | -- start the table cell that will list the ingredients | ||
local wikiTableDataRowDataCell = wikiTableDataRow:tag('td') | |||
-- | -- add a class to the table cell based on whether there's only one | ||
-- in the group | -- (required) ingredient or more than one (swappable) ingredients | ||
-- in the group; if more than one, create an inner table to lay | |||
-- things out more nicely | |||
if 1 == #group then | if 1 == #group then | ||
wikiTableDataRowDataCell:addClass(CSS_CLASS_REQUIRED_INGREDIENT) | |||
" | -- we know there's just one item in group, so we don't need to loop | ||
wikiTableDataRowDataCell:wikitext("<span style=\"font-weight: bold\">" .. groupOfQuantities[1] .. "</span> " .. RenderRecipe.rlTemplate(group[1])) | |||
else | else | ||
wikiTableDataRowDataCell:addClass(CSS_CLASS_SWAPPABLE_INGREDIENT) | |||
-- for alignment purposes, create a table within this cell to lay | |||
-- out the quantities and ingerdients | |||
local innerTable = wikiTableDataRowDataCell:tag('table') | |||
local | |||
-- | -- within every group of ingredients, list out the required or | ||
-- alternatives within that group | |||
-- the items in each group do not have a given key, so ipairs | |||
-- ensures we iterate in a consistent order | |||
for j, alt in ipairs(group) do | |||
local quant = groupOfQuantities[j] | |||
-- write a new row to the inner table and fill it in | |||
innerTable:tag('tr') | |||
:tag('td'):css('text-align', 'right') | |||
:wikitext("<span style=\"font-weight: bold\">" .. quant .. "</span>"):done() | |||
:tag('td') | |||
:wikitext(RenderRecipe.rlTemplate(alt)):done() | |||
end | end | ||
end | end | ||
end | end | ||
-- close up the table row with the product | |||
" | wikiTableDataRow:tag('td'):wikitext("<span style=\"font-weight: bold\">" .. number .. "</span> " .. RenderRecipe.rlTemplate(product,"large")) | ||
-- pass the table back in case more rows need to be added | |||
return wikiTable | return wikiTable | ||
end | end | ||
| Line 182: | Line 266: | ||
-- currently unused. need to define a new way to call the template in order to invoke this function. | |||
-- redirect a rendering request to the recipe data and create appropriate | -- redirect a rendering request to the recipe data and create appropriate | ||
-- templates for the list. The caller can decide how to output the list | -- templates for the list. The caller can decide how to output the list | ||
function RenderRecipe.getBuildingsAndStarsForProduct(frame) | function RenderRecipe.getBuildingsAndStarsForProduct(frame) | ||
local | local argProduct = frame.args.product | ||
-- make sure there's an argument to work with | -- make sure there's an argument to work with | ||
| Line 194: | Line 279: | ||
-- get the associated recipe data, including all buildings | -- get the associated recipe data, including all buildings | ||
local tableRecipe = RecipeData.getAllRecipes( | local tableRecipe = RecipeData.getAllRecipes(argProduct) | ||
-- make sure what's returned is valid before we loop through it | -- make sure what's returned is valid before we loop through it | ||
if not tableRecipe or not tableRecipe.places or #tableRecipe.places < 1 then | if not tableRecipe or not tableRecipe.places or #tableRecipe.places < 1 then | ||
| Line 210: | Line 295: | ||
return listOfTemplates | return listOfTemplates | ||
end | end | ||
-- | |||
-- Helper rendering functions | |||
-- | |||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- Helper | -- Helper function to extract data from the recipe to use as table content. | ||
-- | -- | ||
-- ( | -- Calls several RecipeData helper functions that keep the inner workings of | ||
-- | -- recipes hidden. | ||
-- This function knows only the first values in each of the returned | |||
-- tables are useful, so we can immediately grab only the first values, | |||
-- after confirming something valid was returned. | |||
-- @param tRecipe | |||
-- @return an error message, and if there is any, the rest of the return | |||
-- values will be nil | |||
-- @return the name of the building | |||
-- @return the number of stars of efficiency | |||
-- @return the product time, represented as a string | |||
-- @return the name of the product | |||
-- @return the number of products produced | |||
-- @return a table of ingredient names | |||
-- @return a table of quantities of ingredients | |||
function RenderRecipe.extractRecipeData(tRecipe) | |||
-- verify we have valid things to work with | |||
if not tRecipe then | |||
return "RenderRecipe error: cannot get data from invalid recipe." | |||
end | |||
-- first, the name of the building and the stars of efficiency for the | |||
-- recipe, and we only need the first one | |||
local building, stars = RecipeData.getBuildingsAndStarsLists(tRecipe) | |||
if building and stars then | |||
building, stars = building[1], stars[1] | |||
else | |||
return "RenderRecipe error: could not get the buildings or stars in the recipe." | |||
end | |||
-- second, the speed of production for the recipe, and we only need the | |||
-- first one | |||
local speed = RecipeData.getProductionSpeed(tRecipe) | |||
if speed then | |||
speed = speed[1] | |||
else | |||
return "RenderRecipe error: could not get the production speed in the recipe." | |||
end | |||
-- third, the name and output number of the product, and we only need the | |||
-- first one | |||
local product, number = RecipeData.getProductAndNumbers(tRecipe) | |||
if product and number then | |||
number = number[1] | |||
else | |||
return "RenderRecipe error: could not get the product or output number of the recipe." | |||
end | |||
-- fourth and finally, the list of ingredients and the list-of-list of | |||
-- quantities of those ingredients; the ingredients is independent of the | |||
-- places, so we don't need to extract only the first one, but we do for | |||
-- the list of quantities; because they are different structures, do | |||
-- separate error checking | |||
local ingredients, quantities = RecipeData.getIngredientsAndQuantities(tRecipe) | |||
if not ingredients or 0 == #ingredients then | |||
return "RenderRecipe error: could not get the ingredients in the recipe." | |||
end | |||
if quantities then | |||
quantities = quantities[1] -- now ingredients and quantities have the same structure | |||
else | |||
return "RenderRecipe error: could not get the quantities in recipe." | |||
end | |||
-- refer to return values specified in function comment | |||
return nil, building, stars, speed, product, number, ingredients, quantities | |||
end | |||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- | -- Helper function to fill out the Building_link template and process it into | ||
function RenderRecipe.blTemplate(strBuilding,strSize) | -- HTML to return in our table. | ||
-- | |||
-- This invokes an ATS wiki templates defined elsehwere, to protect variations | |||
-- in the respective templates and modules. Requires the name of the building | |||
-- to render the link to. Assumes the size should be "large," but allows | |||
-- specifying which size should be used. Requires the class variable | |||
-- 'thisFrame' has been previously captured. | |||
-- @param strBuilding the name of the building to link to | |||
-- @param[opt] strSize a string describing the size of the thumbnail image that | |||
-- accompanies the link, default is "large" | |||
-- @return a string of HTML markup | |||
function RenderRecipe.blTemplate(strBuilding, strSize) | |||
-- validate the mw.frame is usable | |||
if not thisFrame then | |||
return "RenderRecipe error: MediaWiki frame was not captured correctly." | |||
end | |||
-- validate the building name | |||
if not strBuilding or "" == strBuilding then | |||
return "RenderRecipe error: cannot make a link to an invalid building name." | |||
end | |||
-- choose a default size if none provided | |||
if not strSize then | if not strSize then | ||
strSize = " | strSize = "large" | ||
end | end | ||
-- invoke the method to process the right template | |||
return thisFrame:expandTemplate{ title=TEMPLATE_BUILDING_LINK, args={ strBuilding, strSize } } | |||
end | end | ||
-- | |||
------------------------------------------------------------------------------- | |||
-- Helper function to fill out the Resource_link template and process it into | |||
-- HTML to return in our table. | |||
-- | |||
-- This invokes an ATS wiki templates defined elsehwere, to protect variations | |||
-- in the respective templates and modules. Requires the name of the resource | |||
-- to render the link to. Assumes the size should be "med," but allows | |||
-- specifying which size should be used. Requires the class variable | |||
-- 'thisFrame' has been previously captured. | |||
-- @param strResource the name of the resource to link to | |||
-- @param[opt] strSize a string describing the size of the thumbnail image that | |||
-- accompanies the link, default is "med" | |||
-- @return a string of HTML markup | |||
function RenderRecipe.rlTemplate(strResource,strSize) | function RenderRecipe.rlTemplate(strResource,strSize) | ||
-- validate the mw.frame is usable | |||
if not thisFrame then | |||
return "RenderRecipe error: MediaWiki frame was not captured correctly." | |||
end | |||
-- validate the resource name | |||
if not strResource or "" == strResource then | |||
return "RenderRecipe error: cannot make a link to an invalid resource name." | |||
end | |||
-- choose a default size if none provided | |||
if not strSize then | if not strSize then | ||
strSize = "med" | strSize = "med" | ||
end | end | ||
-- invoke the method to process the right template | |||
return thisFrame:expandTemplate{ title=TEMPLATE_RESOURCE_LINK, args={ strResource, strSize } } | |||
end | end | ||
-- | |||
------------------------------------------------------------------------------- | |||
-- Helper function to fill out the PStars template and process it into | |||
-- HTML to return in our table. | |||
-- | |||
-- This invokes an ATS wiki templates defined elsehwere, to protect variations | |||
-- in the respective templates and modules. Requires the number of stars to | |||
-- render the icons. Requires the class variable 'thisFrame' has been | |||
-- previously captured. | |||
-- @param intStars the number of stars | |||
-- @return a string of HTML markup | |||
function RenderRecipe.pstarTemplate(intStars) | function RenderRecipe.pstarTemplate(intStars) | ||
return " | -- validate the mw.frame is usable | ||
--return thisFrame:expandTemplate{ title= "P" .. intStars .. TEMPLATE_STAR_SUFFIX} | if not thisFrame then | ||
return "RenderRecipe error: MediaWiki frame was not captured correctly." | |||
end | |||
-- validate the number of stars | |||
if not intStars then | |||
return "RenderRecipe error: missing number of stars for template." | |||
end | |||
-- invoke the method to process the right template | |||
return thisFrame:expandTemplate{ title= "P" .. intStars .. TEMPLATE_STAR_SUFFIX} | |||
end | end | ||
-- | |||
------------------------------------------------------------------------------- | |||
-- Helper function to fill out the Stars template and process it into | |||
-- HTML to return in our table. | |||
-- | |||
-- This invokes an ATS wiki templates defined elsehwere, to protect variations | |||
-- in the respective templates and modules. Requires the number of stars to | |||
-- render the icons. Requires the class variable 'thisFrame' has been | |||
-- previously captured. | |||
-- @param intStars the number of stars | |||
-- @return a string of HTML markup | |||
function RenderRecipe.starTemplate(intStars) | function RenderRecipe.starTemplate(intStars) | ||
return " | -- validate the mw.frame is usable | ||
--return thisFrame:expandTemplate{ title= intStars .. TEMPLATE_STAR_SUFFIX} | if not thisFrame then | ||
return "RenderRecipe error: MediaWiki frame was not captured correctly." | |||
end | |||
-- validate the number of stars | |||
if not intStars then | |||
return "RenderRecipe error: missing number of stars for template." | |||
end | |||
-- invoke the method to process the right template | |||
return thisFrame:expandTemplate{ title= intStars .. TEMPLATE_STAR_SUFFIX} | |||
end | end | ||
-- Return this class when required into another module. | |||
-- Return when required into another | |||
return RenderRecipe | return RenderRecipe | ||
Latest revision as of 14:31, 19 February 2023
Documentation for this module may be created at Module:RenderRecipe/doc
-------------------------------------------------------------------------------
-- This module renders the {{Recipe}} template.
-- https://hoodedhorse.com/wiki/Against_the_Storm/Template:Recipe.
--
-- The template #invokes RenderRecipe.renderRecipe(frame), below.
--
-- The template requires at least one of its two arguments. The first is called
-- "product". and the second is called "building". Both are passed to
-- Module:RecipeData module, which returns one or more recipes.
--
-- Using the recipe data returned, this module creates an HTML table that
-- displays the recipes, which replaces the template, whereever it is written
-- in the wiki markup. The table will have exactly one row if both the product
-- and building are specified. If either is missing, then expect a table with
-- multiple rows.
-- @module RenderRecipe
local RenderRecipe = {}
--
-- Dependencies
--
RecipeData = RecipeData or require("Module:RecipeData") -- used to get recipe data
--
-- Constants
--
-- CSS classes used to style the recipe tables
local CSS_CLASS_WIKITABLE_SORTABLE = "wikitable sortable"
local CSS_CLASS_RECIPE_TABLE = "ATSrecipe"
local CSS_CLASS_RECIPE_CAPTION = "ATSrecipecaption"
local CSS_CLASS_REQUIRED_INGREDIENT = "ATSrequired"
local CSS_CLASS_SWAPPABLE_INGREDIENT = "ATSswappable"
local CSS_CLASS_EMPTY_INGREDIENT = "ATSempty"
-- the names of other templates used, by way of frame:expandTemplate
local TEMPLATE_BUILDING_LINK = "Building_link"
local TEMPLATE_RESOURCE_LINK = "Resource_link"
local TEMPLATE_STAR_SUFFIX = "Star"
-- HTML markup
local BR = "<br />"
local NL = "\n\n"
--
-- Class Variables
--
local thisFrame = {} -- save the frame on first call for later use in expandTemplate
--
-- Main rendering functions
--
-------------------------------------------------------------------------------
-- Renders an HTML table given the MediaWiki frame (which contains the
-- arguments given to the template).
--
-- Uses the MediaWiki frame to access the template arguments. One or both
-- arguments are required; calling this template with no arguments or empty
-- strings will result in an error message.
-- @param frame a table describing the MediaWiki frame; frame['args'] is a table
-- containg the template arguments; frame['args']['product'] is the first
-- argument, assigned to the key 'product'; frame['args']['building'] is the
-- second argument, assigned to the key 'building'
-- @return a string containing the entire table in HTML markup (or an error
-- message if a problem occured)
function RenderRecipe.renderRecipe(frame)
-- need to set the class variable so expandTemplate can work later
thisFrame = frame
-- extract the arguments we care about from the frame
local argProduct = frame.args.product
local argBuilding = frame.args.building
-- build the table; we'll add to this once we know the arguments are valid, but that's after the if/then blocks start
local wikiTable = mw.html.create('table'):addClass(CSS_CLASS_WIKITABLE_SORTABLE):addClass(CSS_CLASS_RECIPE_TABLE)
-- save for later once we check the variables
local wikiTableCaption = wikiTable:tag('caption'):addClass(CSS_CLASS_RECIPE_CAPTION)
-- build the header row; don't need to save it
wikiTable:tag('tr')
:tag('th'):wikitext("Building"):done()
:tag('th'):wikitext("Ingredient #1"):done()
:tag('th'):wikitext("Ingredient #2"):done()
:tag('th'):wikitext("Ingredient #3"):done()
:tag('th'):wikitext("Product")
-- use this to store any error messages that return from the inner function
local errorMessage = ""
--
-- Everything else depends on whether the arguments are valid and which ones were specified.
--
-- if both product and building are specified (and valid), then we can assume exactly one recipe at one building
if argProduct and argProduct ~= "" and argBuilding and argBuilding ~= "" then
-- ask for the matching recipe and make sure there was one
local tOneRecipe = RecipeData.getRecipeAtBuilding(argProduct, argBuilding)
if not tOneRecipe then
return "RenderRecipe error: no product '" .. argProduct .. "' or building '" .. argBuilding .. "' found."
end
wikiTableCaption:wikitext(argProduct .. " recipe in the " .. argBuilding .. ".")
-- assume only one row to render, and renderTableRow will use only the first place in the recipe anyway
wikiTable, errorMessage = RenderRecipe.renderTableRow(tOneRecipe, wikiTable)
-- if only the product is specified, then we need to assume multiple buildings
elseif argProduct and argProduct ~= "" then
-- gets a stack of buildings for the given product, and make sure there was one
local tRecipeStack = RecipeData.getRecipeForProduct(argProduct)
if not tRecipeStack then
return "RenderRecipe error: no product '" .. argProduct .. "' found."
end
wikiTableCaption:wikitext(argProduct .. " recipe.")
-- there are multiple places (1-6) to loop through; places have no key, so ipairs works
for _, place in ipairs(tRecipeStack.places) do
-- make a simple version of tRecipeStack with just the one matching place to the table renderer
-- recipes are {product, pattern, place}
local simplerRecipe = { product=argProduct, pattern=tRecipeStack.pattern, places={place} }
-- each loop calls renderTableRow to add a new row to the passed table
wikiTable, errorMessage = RenderRecipe.renderTableRow(simplerRecipe, wikiTable)
end
-- if only the building is specified, then we need to assume multiple products, but each has exactly one place
elseif argBuilding and argBuilding ~= "" then
-- gets a table of different products' recipes, and make sure there was one
local tProducts = RecipeData.getBuildingsRecipes(argBuilding)
if not tProducts then
return "RenderRecipe error: no building '" .. argBuilding .. "' found."
end
wikiTableCaption:wikitext("Recipes in the " .. argBuilding .. ".")
-- there are multiple products to loop through; the products have a string key, so pairs is better than ipairs
for _, tRecipe in pairs(tProducts) do
-- each call to renderTableRow adds a new row to the passed table
wikiTable, errorMessage = RenderRecipe.renderTableRow(tRecipe, wikiTable)
end
-- something was bad about the arguments; can't print them since they may be nil
else
return "RenderRecipe error: Invalid template parameters. This template requires at least a product or building."
end
-- check whether an error message was returned; if so, append it to the
-- HTML table wherever it is right now
if errorMessage and "" ~= errorMessage then
wikiTable:wikitext(errorMessage)
end
-- finally, make sure we're returning an actual string, not a Lua table
return mw.allToString(wikiTable)
end
-------------------------------------------------------------------------------
-- Writes a new row of HTML tags and content to the provided wiki table.
--
-- Using the provided recipe (combination of a product and a building), this
-- method uses the provided HTML table object to add an additional row
-- containing the relevant content of the recipe. This function uses only the
-- first 'place' (building) where the product can be made; any others are
-- ignored for a single call to this function.
--
-- @param tRecipe a table of a recipe {product, pattern, places}
-- @param wikiTable a table containing an HTML-markup table that has already
-- been started, to which this function will add a new row, cells, and content.
-- @return the updated wikiTable
-- @return an error message or nil if successful
function RenderRecipe.renderTableRow(tRecipe, wikiTable)
-- verify we have valid things to work with
if not tRecipe and not wikiTable then
return nil, nil
end
-- call the helper function to do the extraction and error checking
local errorMessage, building, stars, speed, product, number, ingredients, quantities = RenderRecipe.extractRecipeData(tRecipe)
if errorMessage and "" ~= errorMessage then
return wikiTable, errorMessage
end
--
-- with the data extracted, start writing the HTML tags and content
--
-- write the first cell with the building
local wikiTableDataRow = wikiTable:tag('tr')
:tag('td')
:wikitext(RenderRecipe.blTemplate(building) .. BR .. RenderRecipe.starTemplate(stars) .. BR .. speed)
-- first fill in any empty cells like in the recipe book, one for each
-- blank ingredient
local blanks = 3 - #ingredients
for h = 1,blanks do
wikiTableDataRow:tag('td'):addClass(CSS_CLASS_EMPTY_INGREDIENT)
end
-- simultaneously loop over the groups of alternative ingredients and
-- quantities; so use a group of ingredients and a groupOfQuantities each
-- the groups have no keys, so ipairs ensures we are iterating over them
-- in a consistent order with the quantities
for i, group in ipairs(ingredients) do
local groupOfQuantities = quantities[i]
-- start the table cell that will list the ingredients
local wikiTableDataRowDataCell = wikiTableDataRow:tag('td')
-- add a class to the table cell based on whether there's only one
-- (required) ingredient or more than one (swappable) ingredients
-- in the group; if more than one, create an inner table to lay
-- things out more nicely
if 1 == #group then
wikiTableDataRowDataCell:addClass(CSS_CLASS_REQUIRED_INGREDIENT)
-- we know there's just one item in group, so we don't need to loop
wikiTableDataRowDataCell:wikitext("<span style=\"font-weight: bold\">" .. groupOfQuantities[1] .. "</span> " .. RenderRecipe.rlTemplate(group[1]))
else
wikiTableDataRowDataCell:addClass(CSS_CLASS_SWAPPABLE_INGREDIENT)
-- for alignment purposes, create a table within this cell to lay
-- out the quantities and ingerdients
local innerTable = wikiTableDataRowDataCell:tag('table')
-- within every group of ingredients, list out the required or
-- alternatives within that group
-- the items in each group do not have a given key, so ipairs
-- ensures we iterate in a consistent order
for j, alt in ipairs(group) do
local quant = groupOfQuantities[j]
-- write a new row to the inner table and fill it in
innerTable:tag('tr')
:tag('td'):css('text-align', 'right')
:wikitext("<span style=\"font-weight: bold\">" .. quant .. "</span>"):done()
:tag('td')
:wikitext(RenderRecipe.rlTemplate(alt)):done()
end
end
end
-- close up the table row with the product
wikiTableDataRow:tag('td'):wikitext("<span style=\"font-weight: bold\">" .. number .. "</span> " .. RenderRecipe.rlTemplate(product,"large"))
-- pass the table back in case more rows need to be added
return wikiTable
end
-- currently unused. need to define a new way to call the template in order to invoke this function.
-- redirect a rendering request to the recipe data and create appropriate
-- templates for the list. The caller can decide how to output the list
function RenderRecipe.getBuildingsAndStarsForProduct(frame)
local argProduct = frame.args.product
-- make sure there's an argument to work with
if not tableRecipe then
return "Render_Recipe Error: no product given."
end
-- get the associated recipe data, including all buildings
local tableRecipe = RecipeData.getAllRecipes(argProduct)
-- make sure what's returned is valid before we loop through it
if not tableRecipe or not tableRecipe.places or #tableRecipe.places < 1 then
return "Render_Recipe Error: no recipes found"
end
local listOfTemplates = {}
local buildings, stars = RecipeData.getBuildingsAndStarsLists(tRecipe)
-- loop through the buildings and stars and build the templates
for i, building in pairs(buildings) do
listOfTemplates[i] = RenderRecipe.blTemplate(building) .. " " .. RenderRecipe.pstarTemplate(stars[i])
end
return listOfTemplates
end
--
-- Helper rendering functions
--
-------------------------------------------------------------------------------
-- Helper function to extract data from the recipe to use as table content.
--
-- Calls several RecipeData helper functions that keep the inner workings of
-- recipes hidden.
-- This function knows only the first values in each of the returned
-- tables are useful, so we can immediately grab only the first values,
-- after confirming something valid was returned.
-- @param tRecipe
-- @return an error message, and if there is any, the rest of the return
-- values will be nil
-- @return the name of the building
-- @return the number of stars of efficiency
-- @return the product time, represented as a string
-- @return the name of the product
-- @return the number of products produced
-- @return a table of ingredient names
-- @return a table of quantities of ingredients
function RenderRecipe.extractRecipeData(tRecipe)
-- verify we have valid things to work with
if not tRecipe then
return "RenderRecipe error: cannot get data from invalid recipe."
end
-- first, the name of the building and the stars of efficiency for the
-- recipe, and we only need the first one
local building, stars = RecipeData.getBuildingsAndStarsLists(tRecipe)
if building and stars then
building, stars = building[1], stars[1]
else
return "RenderRecipe error: could not get the buildings or stars in the recipe."
end
-- second, the speed of production for the recipe, and we only need the
-- first one
local speed = RecipeData.getProductionSpeed(tRecipe)
if speed then
speed = speed[1]
else
return "RenderRecipe error: could not get the production speed in the recipe."
end
-- third, the name and output number of the product, and we only need the
-- first one
local product, number = RecipeData.getProductAndNumbers(tRecipe)
if product and number then
number = number[1]
else
return "RenderRecipe error: could not get the product or output number of the recipe."
end
-- fourth and finally, the list of ingredients and the list-of-list of
-- quantities of those ingredients; the ingredients is independent of the
-- places, so we don't need to extract only the first one, but we do for
-- the list of quantities; because they are different structures, do
-- separate error checking
local ingredients, quantities = RecipeData.getIngredientsAndQuantities(tRecipe)
if not ingredients or 0 == #ingredients then
return "RenderRecipe error: could not get the ingredients in the recipe."
end
if quantities then
quantities = quantities[1] -- now ingredients and quantities have the same structure
else
return "RenderRecipe error: could not get the quantities in recipe."
end
-- refer to return values specified in function comment
return nil, building, stars, speed, product, number, ingredients, quantities
end
-------------------------------------------------------------------------------
-- Helper function to fill out the Building_link template and process it into
-- HTML to return in our table.
--
-- This invokes an ATS wiki templates defined elsehwere, to protect variations
-- in the respective templates and modules. Requires the name of the building
-- to render the link to. Assumes the size should be "large," but allows
-- specifying which size should be used. Requires the class variable
-- 'thisFrame' has been previously captured.
-- @param strBuilding the name of the building to link to
-- @param[opt] strSize a string describing the size of the thumbnail image that
-- accompanies the link, default is "large"
-- @return a string of HTML markup
function RenderRecipe.blTemplate(strBuilding, strSize)
-- validate the mw.frame is usable
if not thisFrame then
return "RenderRecipe error: MediaWiki frame was not captured correctly."
end
-- validate the building name
if not strBuilding or "" == strBuilding then
return "RenderRecipe error: cannot make a link to an invalid building name."
end
-- choose a default size if none provided
if not strSize then
strSize = "large"
end
-- invoke the method to process the right template
return thisFrame:expandTemplate{ title=TEMPLATE_BUILDING_LINK, args={ strBuilding, strSize } }
end
-------------------------------------------------------------------------------
-- Helper function to fill out the Resource_link template and process it into
-- HTML to return in our table.
--
-- This invokes an ATS wiki templates defined elsehwere, to protect variations
-- in the respective templates and modules. Requires the name of the resource
-- to render the link to. Assumes the size should be "med," but allows
-- specifying which size should be used. Requires the class variable
-- 'thisFrame' has been previously captured.
-- @param strResource the name of the resource to link to
-- @param[opt] strSize a string describing the size of the thumbnail image that
-- accompanies the link, default is "med"
-- @return a string of HTML markup
function RenderRecipe.rlTemplate(strResource,strSize)
-- validate the mw.frame is usable
if not thisFrame then
return "RenderRecipe error: MediaWiki frame was not captured correctly."
end
-- validate the resource name
if not strResource or "" == strResource then
return "RenderRecipe error: cannot make a link to an invalid resource name."
end
-- choose a default size if none provided
if not strSize then
strSize = "med"
end
-- invoke the method to process the right template
return thisFrame:expandTemplate{ title=TEMPLATE_RESOURCE_LINK, args={ strResource, strSize } }
end
-------------------------------------------------------------------------------
-- Helper function to fill out the PStars template and process it into
-- HTML to return in our table.
--
-- This invokes an ATS wiki templates defined elsehwere, to protect variations
-- in the respective templates and modules. Requires the number of stars to
-- render the icons. Requires the class variable 'thisFrame' has been
-- previously captured.
-- @param intStars the number of stars
-- @return a string of HTML markup
function RenderRecipe.pstarTemplate(intStars)
-- validate the mw.frame is usable
if not thisFrame then
return "RenderRecipe error: MediaWiki frame was not captured correctly."
end
-- validate the number of stars
if not intStars then
return "RenderRecipe error: missing number of stars for template."
end
-- invoke the method to process the right template
return thisFrame:expandTemplate{ title= "P" .. intStars .. TEMPLATE_STAR_SUFFIX}
end
-------------------------------------------------------------------------------
-- Helper function to fill out the Stars template and process it into
-- HTML to return in our table.
--
-- This invokes an ATS wiki templates defined elsehwere, to protect variations
-- in the respective templates and modules. Requires the number of stars to
-- render the icons. Requires the class variable 'thisFrame' has been
-- previously captured.
-- @param intStars the number of stars
-- @return a string of HTML markup
function RenderRecipe.starTemplate(intStars)
-- validate the mw.frame is usable
if not thisFrame then
return "RenderRecipe error: MediaWiki frame was not captured correctly."
end
-- validate the number of stars
if not intStars then
return "RenderRecipe error: missing number of stars for template."
end
-- invoke the method to process the right template
return thisFrame:expandTemplate{ title= intStars .. TEMPLATE_STAR_SUFFIX}
end
-- Return this class when required into another module.
return RenderRecipe


