Module:ServicesData
From Against the Storm Official Wiki
Documentation for this module may be created at Module:ServicesData/doc
--- --- Module for compiling services recipes information from wiki data sources. --- Restructures the flat data tables that are produced from CsvUtils to make --- them more conducive to Lua methods that need to display the information. --- --- The standard way of using this module is a call like the following: --- --- goodID = ServicesRecipesData.getServiceGoodID(serviceName) --- --- This will return the ID of the good that is used to satisfy the service --- need. It is preferable to call getter methods with the ID of the service --- rather than retrieving the entire record for the recipe, so that your code --- stays protected from variations in this module. --- --- * serviceID = ServicesRecipesData.getServiceRecipeID(serviceName) --- * gradeStars = ServicesRecipesData.getServiceRecipeGrade(serviceName) --- * goodID = ServicesRecipesData.getServiceGoodID(serviceName) --- --- The most specific way of retrieving data that does not return a table is --- the preferred way of retrieving the data, because it is the most protected --- from changes in this module. --- --- As a last resort, you may use methods that begin with "getAll..." that --- return tables of data (that is, other than the ones that get lists of --- recipeIDs), but as mentioned they are less preferable, because the internal --- workings of this module may change, and these tables may suddenly change --- their structure, ruining your code. When in doubt, take the extra time to --- write code that uses one of the specific getter methods (that begin with --- "getRecipe..."). --- --- The data table for these services recipes has the following structure: --- --- recipesTable = { --- ["recipe1_ID"] = { --- ["id"] = "recipe1_ID", --- ["gradeID"] = 1, --- ["serviceNeed"] = "Education" --- ["serviceGoods"] = { --- [1] = { --- ["goodID"] = "good1_ID", --- ["stackSize"] = 1 --- }, --- [2] = { ... } or missing if fewer --- } --- }, --- ["recipe2_ID"] = { --- ... --- }, --- ["recipe3_ID"] = { --- ... --- }, --- ... --- } --- --- @module ServicesRecipesData local ServicesRecipesData = {} local CsvUtils = require("Module:CsvUtils") --region Private member variables --- Main data tables, like this: table[recipeID] = table containing recipe --- data local recipesTable --- Lookup map. Built once and reused on subsequent calls within this session, --- like this: table[serviceName] = { recipeID, recipeID, ... } local mapNamesToIDs --- Lookup map. Built once and reused on subsequent calls within this session, --- like this: table[goodID] = { serviceNeed1, serviceNeed2, ... } local mapGoodIDsToRecipeIDs --endregion --region Private constants local DATA_TEMPLATE_NAME = "Template:Institutions_Recipes_csv" local INDEX_ID = "id" local INDEX_GRADE = "gradeID" local INDEX_SERVICE_NAME = "serviceNeed" local INDEX_GOODS = "serviceGoods" local INDEX_GOODS_GOOD_ID = "goodID" local INDEX_GOODS_STACK_SIZE = "stackSize" local PATTERN_SPLIT_STACK_AND_ID = "(%d+)%s([%[%]%s%a]+)" local PATTERN_CAPTURE_END_NUMBER = "Grade(%d)" --endregion --region Private methods --- --- Transforms the originalRecipesTable returned from CSV processing to be more --- conducive to member functions looking up data. Converts text strings --- into subtables. Converts header row into field keys on every record and --- stores records by id rather than arbitrary integer. --- --- @param originalRecipesTable table of CSV-based data, with header row, data rows --- @param recipesHeaderLookup table lookup table of headers to get indexes --- @return table better structured with IDs as keys local function restructureRecipesTable(originalRecipesTable, recipesHeaderLookup) -- A few constants we'll need only within this function. local DATA_ROWS = 2 local INDEX_ORIGINAL_ID = 1 local INDEX_ORIGINAL_GRADE = 2 local INDEX_ORIGINAL_SERVED_NEED = 3 local MAX_SERVICES_GOODS = 6 local LOOKUP_SERVICE_GOODS_BASE_STRING = "Good" mapNamesToIDs = {} mapGoodIDsToRecipeIDs = {} local newRecipeTable = {} for _, originalRecipe in ipairs(originalRecipesTable[DATA_ROWS]) do local newRecipe = {} newRecipe[INDEX_ID] = originalRecipe[INDEX_ORIGINAL_ID] newRecipe[INDEX_GRADE] = originalRecipe[INDEX_ORIGINAL_GRADE]:match(PATTERN_CAPTURE_END_NUMBER) newRecipe[INDEX_SERVICE_NAME] = originalRecipe[INDEX_ORIGINAL_SERVED_NEED] local serviceGoods = {} for i = 1, MAX_SERVICES_GOODS do local goodIndex = recipesHeaderLookup[LOOKUP_SERVICE_GOODS_BASE_STRING .. i] local stackSize, goodID = originalRecipe[goodIndex]:match(PATTERN_SPLIT_STACK_AND_ID) if not goodID then break end local newGood = { [INDEX_GOODS_GOOD_ID] = goodID, [INDEX_GOODS_STACK_SIZE] = stackSize } table.insert(serviceGoods, newGood) -- Populate the lookup map for goods. if not mapGoodIDsToRecipeIDs[goodID] then mapGoodIDsToRecipeIDs[goodID] = {} end table.insert(mapGoodIDsToRecipeIDs[goodID], newRecipe[INDEX_ID]) end newRecipe[INDEX_GOODS] = serviceGoods -- Populate the lookup map for services. if not mapNamesToIDs[ newRecipe[INDEX_SERVICE_NAME] ] then mapNamesToIDs[ newRecipe[INDEX_SERVICE_NAME] ] = {} end table.insert( mapNamesToIDs[ newRecipe[INDEX_SERVICE_NAME] ], newRecipe[INDEX_ID]) newRecipeTable[ newRecipe[INDEX_ID] ] = newRecipe end return newRecipeTable end --- --- Data loader function that uses the utility module and restructures the data --- to be easier to access for invoking methods and later method calls. This --- method is automatically called by all public member functions if the main --- data table has not yet been populated in the current session. local function loadData() -- Utility module retrieves the data as basic, flat lua tables. local originalRecipesTable, recipesHeaderLookup = CsvUtils.extractTables(DATA_TEMPLATE_NAME) -- Now restructure to be more conducive. recipesTable = restructureRecipesTable(originalRecipesTable, recipesHeaderLookup) end --endregion --region Public methods --- --- Returns the whole table of data for the specified recipe. Instead of this, --- you should probably be calling the individual getter methods. --- --- Throws an error if called with a nil or empty string. Returns nil if the --- specified recipe cannot be found. --- ---@param recipeID string the ID of the recipe ---@return table containing the data for the specified recipe, or nil if not found function ServicesRecipesData.getAllDataForRecipeByID(recipeID) -- At runtime, this should never be nil or empty. if not recipeID or recipeID == "" then error("Parameter is nil or empty for recipe ID.") end if not recipesTable then loadData() end return recipesTable[recipeID] end --- --- Retrieve all IDs for recipes for the specified service. --- --- @param serviceName string the name of the service --- @return table the recipes that result in the product function ServicesRecipesData.getAllRecipeIDsForServiceName(serviceName) -- At runtime, this should never be nil or empty. if not serviceName or serviceName == "" then error("Parameter is nil or empty for product ID.") end if not recipesTable then loadData() end return mapNamesToIDs[serviceName] end --- --- Retrieves all IDs for recipes for which the specified good is necessary. --- --- @param goodID string the ID of the ingredient (good) --- @return table the recipes that could use that ingredient function ServicesRecipesData.getAllRecipeIDsWithServiceGoodID(goodID) -- At runtime, this should never be nil or empty. if not goodID or goodID == "" then error("Parameter is nil or empty for ingredient ID.") end if not recipesTable then loadData() end return mapGoodIDsToRecipeIDs[goodID] end --- --- Retrieves the grade code for the recipe specified by its ID. --- --- Returns nil if the recipe ID was not found. --- ---@param recipeID string the ID of the recipe ---@return string the code of the grade. function ServicesRecipesData.getRecipeGradeByID(recipeID) local recipe = ServicesRecipesData.getAllDataForRecipeByID(recipeID) if not recipe then return nil end return recipe[INDEX_GRADE] end --- --- Retrieves the service name for the recipe specified by its ID. --- --- Returns nil if the recipe ID was not found. --- ---@param recipeID string the ID of the recipe ---@return string the ID of the good produced by the recipe function ServicesRecipesData.getRecipeServiceNameByID(recipeID) local recipe = ServicesRecipesData.getAllDataForRecipeByID(recipeID) if not recipe then return nil end return recipe[INDEX_SERVICE_NAME] end --- --- Retrieves the subtable of data for ingredients for the recipe specified by --- its ID. --- --- Returns nil if the recipe ID was not found. --- ---@param recipeID string the ID of the recipe ---@return table of good IDs and stack sizes function ServicesRecipesData.getAllRecipeServiceGoodsByID(recipeID) local recipe = ServicesRecipesData.getAllDataForRecipeByID(recipeID) if not recipe then return nil end return recipe[INDEX_GOODS] end --- --- Retrieves the specified service goods for the recipe specified by its ID. --- --- Returns nil if the recipe ID was not found. --- ---@param recipeID string the ID of the recipe ---@param goodIndex number which ingredient to retrieve ---@return string the ID of the good that is the specified option, or nil if none ---@return number the stack size of that good, or nil if none function ServicesRecipesData.getRecipeOptionByID(recipeID, goodIndex) local recipe = ServicesRecipesData.getAllDataForRecipeByID(recipeID) if not recipe then return nil end local goods = ServicesRecipesData.getAllRecipeServiceGoodsByID(recipeID) if not goods then return nil end local good = goods[goodIndex] return good[INDEX_GOODS_GOOD_ID], good[INDEX_GOODS_STACK_SIZE] end --endregion return ServicesRecipesData