The shopping list functionality allows you to consolidate ingredients from multiple recipes into a single unified shopping list. This is perfect for meal planning, batch cooking, or preparing multiple dishes at once.
The cooklang package provides functions to:
- Combine ingredients from multiple recipes
- Automatically consolidate duplicate ingredients
- Convert units when possible
- Scale quantities for different serving sizes
- Export to various formats for display or processing
Creates a consolidated shopping list from multiple recipes.
func CreateShoppingList(recipes ...*Recipe) (*ShoppingList, error)Parameters:
recipes ...Recipe: Variable number of recipe pointers to combine
Returns:
*ShoppingList: Consolidated shopping list with all ingredientserror: Error if consolidation fails
Example:
pasta, _ := cooklang.ParseString(pastaRecipe)
salad, _ := cooklang.ParseString(saladRecipe)
bread, _ := cooklang.ParseString(breadRecipe)
shoppingList, err := cooklang.CreateShoppingList(pasta, salad, bread)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total ingredients: %d\n", shoppingList.Count())Creates a shopping list with ingredients converted to a target unit when possible.
func CreateShoppingListWithUnit(targetUnit string, recipes ...*Recipe) (*ShoppingList, error)Parameters:
targetUnit string: The unit to convert compatible ingredients to (e.g., "g", "ml", "kg")recipes ...*Recipe: Variable number of recipe pointers to combine
Returns:
*ShoppingList: Consolidated shopping list with converted unitserror: Error if conversion or consolidation fails
Example:
// Convert all mass ingredients to kilograms
shoppingList, err := cooklang.CreateShoppingListWithUnit("kg", recipe1, recipe2)
if err != nil {
log.Fatal(err)
}Creates a shopping list by scaling each recipe to the target number of servings before combining. This is ideal for meal planning where recipes have different serving sizes.
func CreateShoppingListForServings(targetServings float64, recipes ...*Recipe) (*ShoppingList, error)Parameters:
targetServings float64: The desired number of servings for each reciperecipes ...*Recipe: Variable number of recipe pointers to combine
Returns:
*ShoppingList: Consolidated shopping list with all ingredients scalederror: Error if consolidation fails
Example:
// Recipes with different serving sizes
monday, _ := cooklang.ParseFile("monday.cook") // servings: 2
tuesday, _ := cooklang.ParseFile("tuesday.cook") // servings: 8
// Normalize both to 4 servings before combining
shoppingList, err := cooklang.CreateShoppingListForServings(4, monday, tuesday)
// monday scaled 2x, tuesday scaled 0.5xCombines servings normalization with unit conversion - scales each recipe to the target servings and converts ingredients to the target unit.
func CreateShoppingListForServingsWithUnit(targetServings float64, targetUnit string, recipes ...*Recipe) (*ShoppingList, error)Parameters:
targetServings float64: The desired number of servings for each recipetargetUnit string: The unit to convert compatible ingredients to (e.g., "g", "ml", "kg")recipes ...*Recipe: Variable number of recipe pointers to combine
Returns:
*ShoppingList: Consolidated shopping list with scaled and converted ingredientserror: Error if conversion or consolidation fails
Example:
// Scale to 4 servings and convert masses to grams
shoppingList, err := cooklang.CreateShoppingListForServingsWithUnit(4, "g", recipes...)type ShoppingList struct {
Ingredients *IngredientList `json:"ingredients"`
Recipes []string `json:"recipes,omitempty"`
}Returns the shopping list as a map of ingredient names to their consolidated quantities.
func (sl *ShoppingList) ToMap() map[string]stringExample:
shoppingMap := shoppingList.ToMap()
for ingredient, quantity := range shoppingMap {
fmt.Printf("• %s: %s\n", ingredient, quantity)
}Scales all ingredients in the shopping list by the given multiplier.
func (sl *ShoppingList) Scale(multiplier float64) *ShoppingListParameters:
multiplier float64: The scaling factor (e.g., 2.0 for double, 0.5 for half)
Returns:
*ShoppingList: New shopping list with scaled quantities
Example:
// Scale for a dinner party (double the quantities)
scaledList := shoppingList.Scale(2.0)Returns the number of unique ingredients in the shopping list.
func (sl *ShoppingList) Count() intExample:
fmt.Printf("You need %d different ingredients\n", shoppingList.Count())package main
import (
"fmt"
"log"
"github.com/hilli/cooklang"
)
func main() {
// Define multiple recipes
pastaRecipe := `Add @pasta{500%g} and @olive oil{3%tbsp}.
Season with @salt{1%tsp} and @pepper{0.5%tsp}.`
saladRecipe := `Mix @lettuce{200%g} with @olive oil{2%tbsp}.
Season with @salt{0.5%tsp} and @pepper{0.25%tsp}.`
// Parse recipes
pasta, _ := cooklang.ParseString(pastaRecipe)
salad, _ := cooklang.ParseString(saladRecipe)
// Create consolidated shopping list
shoppingList, err := cooklang.CreateShoppingList(pasta, salad)
if err != nil {
log.Fatal(err)
}
// Display as a map
fmt.Println("Shopping List:")
for ingredient, quantity := range shoppingList.ToMap() {
fmt.Printf(" • %s: %s\n", ingredient, quantity)
}
// Scale for 4 people instead of 2
scaled := shoppingList.Scale(2.0)
fmt.Println("\nFor 4 people:")
for ingredient, quantity := range scaled.ToMap() {
fmt.Printf(" • %s: %s\n", ingredient, quantity)
}
}Output:
Shopping List:
• pasta: 500 g
• olive oil: 5 tbsp
• salt: 1.5 tsp
• pepper: 0.8 tsp
• lettuce: 200 g
For 4 people:
• pasta: 1000 g
• olive oil: 10 tbsp
• salt: 3 tsp
• pepper: 1.5 tsp
• lettuce: 400 g
The shopping list automatically consolidates ingredients with the same name:
// Recipe 1: @flour{500%g}
// Recipe 2: @flour{300%g}
// Result: flour: 800 gWhen ingredients have compatible units, they are automatically converted and summed:
// Recipe 1: @butter{500%g}
// Recipe 2: @butter{0.5%kg}
// Result: butter: 1000 g (or 1 kg if converted)Ingredients with unspecified quantities are preserved as-is:
// Recipe: @vanilla{some}
// Result: vanilla: someThe Scale() method multiplies all quantified ingredients while preserving "some" quantities:
shoppingList.Scale(2.0) // Double all quantities
shoppingList.Scale(0.5) // Halve all quantities
shoppingList.Scale(1.5) // 1.5x quantitiesWhen planning meals for a household, recipes often have different serving sizes. Use CreateShoppingListForServings to normalize each recipe to your household size before combining:
// Recipes with different serving sizes
monday, _ := cooklang.ParseFile("monday.cook") // servings: 2
tuesday, _ := cooklang.ParseFile("tuesday.cook") // servings: 8
wednesday, _ := cooklang.ParseFile("wednesday.cook") // servings: 4
// Normalize all to household of 5 people
weeklyList, _ := cooklang.CreateShoppingListForServings(5, monday, tuesday, wednesday)
// monday scaled 2.5x, tuesday scaled 0.625x, wednesday scaled 1.25xThis is much more practical than using Scale() which would require calculating individual factors for each recipe.
Combine servings normalization with unit standardization:
// Scale to 4 servings and convert to metric
list, _ := cooklang.CreateShoppingListForServingsWithUnit(4, "g", recipes...)# Scale all recipes to 4 servings (household size)
cook shopping-list monday.cook tuesday.cook --servings 4
# With unit conversion
cook shop recipes/*.cook --servings 4 --unit kgCreate a weekly shopping list by combining all your planned recipes:
monday := cooklang.ParseString(mondayRecipe)
tuesday := cooklang.ParseString(tuesdayRecipe)
wednesday := cooklang.ParseString(wednesdayRecipe)
weeklyList, _ := cooklang.CreateShoppingList(monday, tuesday, wednesday)Scale a recipe for meal prep:
recipe := cooklang.ParseString(recipeText)
list, _ := cooklang.CreateShoppingList(recipe)
batchList := list.Scale(5.0) // Make 5x the recipeCombine appetizers, mains, and desserts for an event:
appetizer := cooklang.ParseString(appetizerRecipe)
main := cooklang.ParseString(mainRecipe)
dessert := cooklang.ParseString(dessertRecipe)
partyList, _ := cooklang.CreateShoppingList(appetizer, main, dessert)
guestList := partyList.Scale(float64(numberOfGuests) / 4.0)Convert all ingredients to a preferred unit system:
// Convert all mass ingredients to kilograms
metricList, _ := cooklang.CreateShoppingListWithUnit("kg", recipes...)
// Convert all volume ingredients to milliliters
volumeList, _ := cooklang.CreateShoppingListWithUnit("ml", recipes...)All functions return appropriate errors for:
- Unit conversion failures
- Incompatible ingredient combinations
- Invalid recipe structures
Always check the returned error:
shoppingList, err := cooklang.CreateShoppingList(recipes...)
if err != nil {
// Handle error - perhaps some ingredients couldn't be consolidated
log.Printf("Warning: %v", err)
}The ShoppingList struct supports JSON marshaling for API responses or storage:
import "encoding/json"
jsonData, err := json.Marshal(shoppingList)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))-
Group Similar Recipes: Consolidate recipes that share many ingredients for more efficient shopping.
-
Use Scaling Strategically: Scale individual recipes before combining, or scale the final shopping list - choose based on your needs.
-
Check Counts: Use
Count()to quickly see how many unique ingredients you need. -
Verify Conversions: When using
CreateShoppingListWithUnit, be aware that not all ingredients may convert successfully. -
Map Format for Display: Use
ToMap()for easy iteration and display in user interfaces.
Run the included demo to see all features in action:
go run cmd/shopping-list-demo/main.goThis will demonstrate:
- Creating a shopping list from multiple recipes
- Automatic ingredient consolidation
- Scaling for different serving sizes
- Unit conversion examples
- Different output formats