Module:RenderRecipe

--- -- This module renders the 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 = " " 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(groupOfQuantities[1] .. " " .. 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(quant):done :tag('td') :wikitext(RenderRecipe.rlTemplate(alt)):done end end end -- close up the table row with the product wikiTableDataRow:tag('td'):wikitext(number .. " " .. 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