--- Recipe module
-- @module Recipe
require 'stdlib/data/data'
Recipe = {}
--- Selects all recipe values where the key matches the selector pattern.
-- The selector pattern is divided into groups. The pattern should have a colon character `:` to denote the selection for each group.
--
The first group is for the name of the recipe element
--
The second group is for the name of keys inside of the recipe element, and is optional. If missing, all elements matching prior groups are returned.
--
The third group is for the name of values inside of the recipe element, and is optional. If missing, all elements matching prior groups are returned.
--
Selectors without a colon `:` separator are assumed to select all values in the first group. -- @usage Recipe.select('.*') -- returns a table with all recipes, equivalent to Data.select('recipe:.*') -- @usage Recipe.select('steel.*') -- returns a table with all recipes whose name matches 'steel.*' -- @usage Recipe.select('steel.*:ingredients') -- returns a table with all ingredients from all recipes whose name matches 'steel.*' -- @usage Recipe.select('steel.*:ingredients:iron-plate') -- returns a table with all iron-plate ingredient objects, from all recipes whose name matches 'steel.*' -- @param pattern to search with -- @return table containing the elements matching the selector pattern, or an empty table if there was no matches function Recipe.select(pattern) fail_if_missing(pattern, "missing pattern argument") local results = {} local parts = string.split(pattern, ":") local inner_field_pattern = #parts > 1 and parts[2] or nil if inner_field_pattern then -- Data.select --> { { recipe }, { recipe } } for _, recipe in pairs(Data.select('recipe:' .. pattern)) do for field_key, field_value in pairs(recipe) do -- field_key --> ingredients, field_value --> { { 'copper-ore', 1} } if string.match(field_key, inner_field_pattern) then local contents_field_pattern = #parts > 2 and parts[3] or nil if contents_field_pattern then -- escape the '-' in names contents_field_pattern = string.gsub(contents_field_pattern, "%-", "%%-") -- ex: field_value --> { { 'copper-ore', 1} } for _, content_value in pairs(field_value) do -- ex: content_value --> { 'copper-ore', 1} for _, content in pairs(content_value) do -- ex: content --> 'copper-ore', 1 if string.match(content, contents_field_pattern) then Recipe.format_items({recipe}) table.insert(results, content_value) end end end else for _, value in pairs(field_value) do setmetatable(value, Recipe._item_metatable.new(value)) table.insert(results, value) end end end end end else return Recipe.format_items(Data.select('recipe:' .. pattern)) end setmetatable(results, Data._select_metatable.new(results)) return results end -- this metatable is set on recipes, to control access to ingredients and results Recipe._item_metatable = {} Recipe._item_metatable.new = function(item) local self = { } self.__index = function(tbl, key) if type(key) == 'number' then local keys = { 'name', 'amount' } local val = rawget(tbl, keys[key]) -- amount defaults to one if not val and keys[key] == 'amount' then return 1 end return val elseif type(key) == 'string' then local keys = { name = 1, amount = 2 } local val = rawget(tbl, keys[key]) -- amount defaults to one if not val and key == 'amount' then return 1 end return val end return rawget(tbl, key) end self.__newindex = function(tbl, key, value) if type(key) == 'number' and #tbl == 0 then local keys = { 'name', 'amount' } rawset(tbl, keys[key], value) elseif type(key) == 'string' and #tbl > 0 then local keys = { name = 1, amount = 2 } rawset(tbl, keys[key], value) else return rawset(tbl, key, value) end end return self end function Recipe.format_items(recipes) recipes = recipes or data.raw.recipe table.each(recipes, function(recipe, recipe_name) if recipe.ingredients and type(recipe.ingredients) == 'table' then table.each(recipe.ingredients, function(ingredient) setmetatable(ingredient, Recipe._item_metatable.new(ingredient)) end) end if recipe.results and type(recipe.results) == 'table' then table.each(recipe.results, function(result) setmetatable(result, Recipe._item_metatable.new(result)) end) end end) return recipes end