Module:RenderRecipe: Difference between revisions
From Against the Storm Official Wiki
m (trying some styling) |
m (making numbers in swappable ingredients bold too) |
||
(26 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 | ||
---- | -- | ||
-- CSS classes used to style the recipe tables | |||
local CSS_CLASS_RECIPE_TABLE = " | local CSS_CLASS_WIKITABLE_SORTABLE = "wikitable sortable" | ||
local CSS_CLASS_RECIPE_CAPTION = " | 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 | 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\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) | ||
Line 47: | Line 75: | ||
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 | |||
-- both product and building are specified | -- build the table; we'll add to this once we know the arguments are valid, but that's after the if/then blocks start | ||
if | 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 | local tOneRecipe = RecipeData.getRecipeAtBuilding(argProduct, argBuilding) | ||
if not | if not tOneRecipe then | ||
return " | return "RenderRecipe error: no product '" .. argProduct .. "' or building '" .. argBuilding .. "' found." | ||
end | end | ||
-- only one to render | 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) | |||
-- only product is specified | |||
elseif | -- if only the product is specified, then we need to assume multiple buildings | ||
elseif argProduct and argProduct ~= "" then | |||
-- gets a | -- gets a stack of buildings for the given product, and make sure there was one | ||
local | local tRecipeStack = RecipeData.getRecipeForProduct(argProduct) | ||
if not | if not tRecipeStack then | ||
return " | return "RenderRecipe error: no product '" .. argProduct .. "' found." | ||
end | end | ||
wikiTableCaption:wikitext(argProduct .. " recipe.") | |||
-- | -- there are multiple places (1-6) to loop through; places have no key, so ipairs works | ||
for | 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 | end | ||
-- only building is specified | -- if only the building is specified, then we need to assume multiple products, but each has exactly one place | ||
elseif | elseif argBuilding and argBuilding ~= "" then | ||
-- gets a table of | -- gets a table of different products' recipes, and make sure there was one | ||
local | local tProducts = RecipeData.getBuildingsRecipes(argBuilding) | ||
if not | if not tProducts then | ||
return " | return "RenderRecipe error: no building '" .. argBuilding .. "' found." | ||
end | end | ||
wikiTableCaption:wikitext("Recipes in the " .. argBuilding .. ".") | |||
-- | |||
for | -- 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 | -- something was bad about the arguments; can't print them since they may be nil | ||
else | else | ||
return " | |||
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 | 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 214: | 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 226: | 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 242: | 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 } } | 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 } } | 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 | ||
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} | 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 | ||
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} | return thisFrame:expandTemplate{ title= intStars .. TEMPLATE_STAR_SUFFIX} | ||
end | end | ||
Line 289: | Line 505: | ||
-- 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