From 55d5db47378b926ac09996cccb7a507dbfbbf7df Mon Sep 17 00:00:00 2001 From: Joseph Livesey Date: Tue, 2 Jan 2024 14:20:39 -0500 Subject: [PATCH 1/3] fix: implement Display for StoreType --- crates/api/src/lib.rs | 8 ++++---- crates/persistence/src/store.rs | 12 +++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 7c6186b..6d2301e 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -38,11 +38,11 @@ pub struct Api { impl Api { pub async fn init(store: StoreType) -> Result { - info!("Initializing API with store type: {:?}", store); + info!("Initializing API with store type: {store}"); - let store = Store::from_store_type(store).await?.init().await?; - - let api = Api { store }; + let api = Api { + store: Store::from_store_type(store).await?.init().await?, + }; let (tx, mut rx) = mpsc::channel::(10); let dispatch = ApiDispatch { tx }; diff --git a/crates/persistence/src/store.rs b/crates/persistence/src/store.rs index 7530215..518e703 100644 --- a/crates/persistence/src/store.rs +++ b/crates/persistence/src/store.rs @@ -16,7 +16,7 @@ use tokio::sync::{ use tracing::warn; use url::Url; -use std::{error::Error, fmt::Debug, str::FromStr}; +use std::{error::Error, fmt::Debug, fmt::Display, str::FromStr}; use crate::{ json::{ @@ -72,6 +72,16 @@ pub enum StoreType { SqliteInMem, } +impl Display for StoreType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StoreType::Json => write!(f, "json"), + StoreType::Sqlite => write!(f, "sqlite"), + StoreType::SqliteInMem => write!(f, "sqlite-inmem"), + } + } +} + impl FromStr for StoreType { type Err = StoreError; From cde3cbc4a07ac0392817f134e0e34436f38a64e2 Mon Sep 17 00:00:00 2001 From: Joseph Livesey Date: Tue, 2 Jan 2024 14:22:19 -0500 Subject: [PATCH 2/3] fix: clear up duplicated use of 'trim' --- crates/common/src/recipes.rs | 4 ++++ crates/gust/src/command.rs | 28 +++++++++++++--------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/common/src/recipes.rs b/crates/common/src/recipes.rs index beff428..7af48b2 100644 --- a/crates/common/src/recipes.rs +++ b/crates/common/src/recipes.rs @@ -25,6 +25,10 @@ impl Recipe { pub fn as_str(&self) -> &str { &self.0 } + + pub fn from_input_string(s: &str) -> Self { + Self::from(s) + } } impl From<&str> for Recipe { diff --git a/crates/gust/src/command.rs b/crates/gust/src/command.rs index 4292498..8b84d69 100644 --- a/crates/gust/src/command.rs +++ b/crates/gust/src/command.rs @@ -1,7 +1,7 @@ use common::{ commands::{Add, ApiCommand, Delete, Read, Update}, item::{Name, Section}, - recipes::Ingredients, + recipes::{Ingredients, Recipe}, }; use clap::ArgMatches; @@ -28,34 +28,32 @@ impl TryFrom for GustCommand { matches.get_one::("recipe"), matches.get_one::("ingredients"), ) { - let (recipe, ingredients) = ( - recipe.as_str().into(), - Ingredients::from_input_string(ingredients.trim()), - ); - - Add::recipe_from_name_and_ingredients(recipe, ingredients) + Add::recipe_from_name_and_ingredients( + Recipe::from_input_string(recipe), + Ingredients::from_input_string(ingredients), + ) } else if let Some(name) = matches.get_one::("item") { Add::item_from_name_and_section( - Name::from(name.trim()), + Name::from(name.as_str()), matches .get_one::("section") .map(|section| Section::from(section.trim())), ) } else if let Some(item) = matches.get_one::("checklist-item") { - Add::checklist_item_from_name(Name::from(item.trim())) + Add::checklist_item_from_name(Name::from(item.as_str())) } else { match matches.subcommand() { Some(("checklist", matches)) => Add::checklist_item_from_name(Name::from( matches .get_one::("item") .expect("item required") - .trim(), + .as_str(), )), Some(("list", matches)) => { if let Some(name) = matches.get_one::("recipe") { Add::list_recipe_from_name(name.as_str().into()) } else if let Some(name) = matches.get_one::("item") { - Add::list_item_from_name(Name::from(name.trim())) + Add::list_item_from_name(Name::from(name.as_str())) } else { unimplemented!() } @@ -68,14 +66,14 @@ impl TryFrom for GustCommand { if let Some(name) = matches.get_one::("recipe") { Delete::recipe_from_name(name.as_str().into()) } else if let Some(name) = matches.get_one::("item") { - Delete::item_from_name(Name::from(name.trim())) + Delete::item_from_name(Name::from(name.as_str())) } else { match matches.subcommand() { Some(("checklist", matches)) => { let Some(name) = matches.get_one::("checklist-item") else { unimplemented!() }; - Delete::ChecklistItem(Name::from(name.trim())) + Delete::ChecklistItem(Name::from(name.as_str())) } _ => unimplemented!(), } @@ -85,14 +83,14 @@ impl TryFrom for GustCommand { let Some(url) = matches.get_one::("url") else { unreachable!("Providing a URL is required") }; - let url: Url = Url::parse(url.trim())?; + let url: Url = Url::parse(url)?; Ok(GustCommand::FetchRecipe(url)) } Some(("read", matches)) => Ok(GustCommand::Read( if let Some(name) = matches.get_one::("recipe") { Read::recipe_from_name(name.as_str().into()) } else if let Some(name) = matches.get_one::("item") { - Read::item_from_name(Name::from(name.trim())) + Read::item_from_name(Name::from(name.as_str())) } else { match matches.subcommand() { Some(("checklist", _matches)) => Read::Checklist, From ffab78777c90863b89f1f1ebd00d87fc597cf134 Mon Sep 17 00:00:00 2001 From: Joseph Livesey Date: Tue, 2 Jan 2024 20:00:56 -0500 Subject: [PATCH 3/3] fix(json): reduce test case sizes and fix add item and recipe 'Items' methods --- crates/common/src/item.rs | 17 +- crates/common/src/items.rs | 12 +- crates/persistence/src/json/mod.rs | 843 +++-------------------------- 3 files changed, 110 insertions(+), 762 deletions(-) diff --git a/crates/common/src/item.rs b/crates/common/src/item.rs index 3c9b344..4682831 100644 --- a/crates/common/src/item.rs +++ b/crates/common/src/item.rs @@ -38,9 +38,14 @@ impl Item { } pub fn add_recipe(&mut self, recipe: &str) { - self.recipes - .get_or_insert_with(Vec::new) - .push(recipe.into()); + let recipe = recipe.into(); + if let Some(recipes) = &mut self.recipes { + if !recipes.contains(&recipe) { + recipes.push(recipe); + } + } else { + self.recipes = Some(vec![recipe]); + } } pub fn delete_recipe(&mut self, name: &str) { @@ -70,6 +75,12 @@ impl fmt::Display for Item { } } +impl From<&Name> for Item { + fn from(name: &Name) -> Self { + Self::new(name.as_str()) + } +} + #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] pub struct Name(String); diff --git a/crates/common/src/items.rs b/crates/common/src/items.rs index 892dd06..b5a0288 100644 --- a/crates/common/src/items.rs +++ b/crates/common/src/items.rs @@ -46,7 +46,9 @@ impl Items { } pub fn add_item(&mut self, item: Item) { - self.collection.push(item); + if !self.collection.iter().any(|i| i.name() == item.name()) { + self.collection.push(item); + } } pub fn delete_item(&mut self, name: &str) { @@ -75,9 +77,15 @@ impl Items { } pub fn add_recipe(&mut self, name: &str, ingredients: &str) { + let ingredients = Ingredients::from_input_string(ingredients); + + ingredients + .iter() + .for_each(|ingredient| self.add_item(ingredient.into())); + self.collection .iter_mut() - .filter(|item| Ingredients::from_input_string(ingredients).contains(item.name())) + .filter(|item| ingredients.contains(item.name())) .for_each(|item| item.add_recipe(name)); self.recipes.push(name.into()); diff --git a/crates/persistence/src/json/mod.rs b/crates/persistence/src/json/mod.rs index 38871fb..35e2157 100644 --- a/crates/persistence/src/json/mod.rs +++ b/crates/persistence/src/json/mod.rs @@ -197,108 +197,6 @@ pub mod test { "oatmeal chocolate chip cookies", "fried eggs for breakfast" ] - }, - { - "name": "milk", - "section": "dairy", - "recipes": [] - }, - { - "name": "spinach", - "section": "fresh", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "beer", - "section": "dairy", - "recipes": [] - }, - { - "name": "unsalted butter", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast" - ] - }, - { - "name": "bread", - "section": "pantry", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "old fashioned rolled oats", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "chocolate chips", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking powder", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking soda", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "salt", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "white sugar", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "vanilla extract", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "whole-wheat flour", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "1/2 & 1/2", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "feta", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] } ], "recipes": [ @@ -344,16 +242,18 @@ pub mod test { } "#); store.save_items(items)?; - let StoreResponse::Items(items) = store.items().await.unwrap() else { - todo!() - }; - insta::assert_json_snapshot!(items, @r#" - { - "sections": [], - "collection": [], - "recipes": [] - } - "#); + match store.items().await.unwrap() { + StoreResponse::Items(items) => { + insta::assert_json_snapshot!(items, @r#" + { + "sections": [], + "collection": [], + "recipes": [] + } + "#); + } + _ => panic!(), + } std::fs::remove_file(store.items)?; Ok(()) } @@ -388,218 +288,11 @@ pub mod test { "oatmeal chocolate chip cookies", "fried eggs for breakfast" ] - }, - { - "name": "milk", - "section": "dairy", - "recipes": [] - }, - { - "name": "spinach", - "section": "fresh", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "beer", - "section": "dairy", - "recipes": [] - }, - { - "name": "unsalted butter", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast" - ] - }, - { - "name": "bread", - "section": "pantry", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "old fashioned rolled oats", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "chocolate chips", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking powder", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking soda", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "salt", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "white sugar", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "vanilla extract", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "whole-wheat flour", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "1/2 & 1/2", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "feta", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] } ] "###); items.delete_item("eggs"); - insta::assert_json_snapshot!(items.collection().collect::>(), @r###" - [ - { - "name": "milk", - "section": "dairy", - "recipes": [] - }, - { - "name": "spinach", - "section": "fresh", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "beer", - "section": "dairy", - "recipes": [] - }, - { - "name": "unsalted butter", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast" - ] - }, - { - "name": "bread", - "section": "pantry", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "old fashioned rolled oats", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "chocolate chips", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking powder", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking soda", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "salt", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "white sugar", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "vanilla extract", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "whole-wheat flour", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "1/2 & 1/2", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "feta", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] - } - ] - "###); + insta::assert_json_snapshot!(items.collection().collect::>(), @"[]"); Ok(()) } @@ -623,108 +316,6 @@ pub mod test { "oatmeal chocolate chip cookies", "fried eggs for breakfast" ] - }, - { - "name": "milk", - "section": "dairy", - "recipes": [] - }, - { - "name": "spinach", - "section": "fresh", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "beer", - "section": "dairy", - "recipes": [] - }, - { - "name": "unsalted butter", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast" - ] - }, - { - "name": "bread", - "section": "pantry", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "old fashioned rolled oats", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "chocolate chips", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking powder", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "baking soda", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "salt", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "white sugar", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "vanilla extract", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "whole-wheat flour", - "section": "pantry", - "recipes": [ - "oatmeal chocolate chip cookies" - ] - }, - { - "name": "1/2 & 1/2", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] - }, - { - "name": "feta", - "section": "dairy", - "recipes": [ - "fried eggs for breakfast" - ] } ], "recipes": [ @@ -740,7 +331,7 @@ pub mod test { .with_section("fresh") .with_recipes(&[Recipe::from(recipe)]); - let ingredients = "kumquats, carrots, dried apricots, dried cranberries, chili, onion, garlic, cider vinegar, granulated sugar, honey, kosher salt, cardamom, cloves, coriander, ginger, black peppercorns"; + let ingredients = "cumquats, carrots, dried apricots, dried cranberries, chili, onion, garlic, cider vinegar, granulated sugar, honey, kosher salt, cardamom, cloves, coriander, ginger, black peppercorns"; items.add_item(item); items.add_recipe(recipe, ingredients); @@ -764,110 +355,113 @@ pub mod test { ] }, { - "name": "milk", - "section": "dairy", - "recipes": [] + "name": "cumquats", + "section": "fresh", + "recipes": [ + "cumquat chutney" + ] }, { - "name": "spinach", - "section": "fresh", + "name": "carrots", + "section": null, "recipes": [ - "fried eggs for breakfast" + "cumquat chutney" ] }, { - "name": "beer", - "section": "dairy", - "recipes": [] + "name": "dried apricots", + "section": null, + "recipes": [ + "cumquat chutney" + ] }, { - "name": "unsalted butter", - "section": "dairy", + "name": "dried cranberries", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast" + "cumquat chutney" ] }, { - "name": "bread", - "section": "pantry", + "name": "chili", + "section": null, "recipes": [ - "fried eggs for breakfast" + "cumquat chutney" ] }, { - "name": "old fashioned rolled oats", - "section": "pantry", + "name": "onion", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "chocolate chips", - "section": "pantry", + "name": "garlic", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "baking powder", - "section": "pantry", + "name": "cider vinegar", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "baking soda", - "section": "pantry", + "name": "granulated sugar", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "salt", - "section": "pantry", + "name": "honey", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "white sugar", - "section": "pantry", + "name": "kosher salt", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "vanilla extract", - "section": "pantry", + "name": "cardamom", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "whole-wheat flour", - "section": "pantry", + "name": "cloves", + "section": null, "recipes": [ - "oatmeal chocolate chip cookies" + "cumquat chutney" ] }, { - "name": "1/2 & 1/2", - "section": "dairy", + "name": "coriander", + "section": null, "recipes": [ - "fried eggs for breakfast" + "cumquat chutney" ] }, { - "name": "feta", - "section": "dairy", + "name": "ginger", + "section": null, "recipes": [ - "fried eggs for breakfast" + "cumquat chutney" ] }, { - "name": "cumquats", - "section": "fresh", + "name": "black peppercorns", + "section": null, "recipes": [ "cumquat chutney" ] @@ -902,15 +496,7 @@ pub mod test { "name": "garlic", "section": "fresh", "recipes": [ - "sheet pan salmon with broccoli", - "crispy tofu with cashews and blistered snap peas", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "crispy sheet-pan noodles", - "flue flighter chicken stew", - "sheet-pan chicken with jammy tomatoes", - "swordfish pasta" + "tomato pasta" ] }, { @@ -927,72 +513,27 @@ pub mod test { "tomato pasta" ] }, - { - "name": "lemons", - "section": "fresh", - "recipes": [ - "chicken breasts with lemon", - "hummus", - "sheet-pan chicken with jammy tomatoes", - "flue flighter chicken stew" - ] - }, { "name": "pasta", "section": "pantry", "recipes": [ - "tomato pasta", - "swordfish pasta" + "tomato pasta" ] }, { "name": "olive oil", "section": "pantry", "recipes": [ - "sheet pan salmon with broccoli", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "sheet-pan chicken with jammy tomatoes", - "turkey meatballs", - "swordfish pasta" - ] - }, - { - "name": "short grain brown rice", - "section": "pantry", - "recipes": [ - "sheet pan salmon with broccoli", - "flue flighter chicken stew" + "tomato pasta" ] }, { "name": "parmigiana", "section": "dairy", "recipes": [ - "tomato pasta", - "turkey meatballs" - ] - }, - { - "name": "eggs", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast", - "turkey meatballs" + "tomato pasta" ] }, - { - "name": "sausages", - "section": "protein", - "recipes": [] - }, - { - "name": "dumplings", - "section": "freezer", - "recipes": [] - }, { "name": "kumquats", "section": "fresh", @@ -1007,15 +548,7 @@ pub mod test { "name": "garlic", "section": "fresh", "recipes": [ - "sheet pan salmon with broccoli", - "crispy tofu with cashews and blistered snap peas", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "crispy sheet-pan noodles", - "flue flighter chicken stew", - "sheet-pan chicken with jammy tomatoes", - "swordfish pasta" + "tomato pasta" ] }, { @@ -1032,71 +565,26 @@ pub mod test { "tomato pasta" ] }, - { - "name": "lemons", - "section": "fresh", - "recipes": [ - "chicken breasts with lemon", - "hummus", - "sheet-pan chicken with jammy tomatoes", - "flue flighter chicken stew" - ] - }, { "name": "pasta", "section": "pantry", "recipes": [ - "tomato pasta", - "swordfish pasta" + "tomato pasta" ] }, { "name": "olive oil", "section": "pantry", "recipes": [ - "sheet pan salmon with broccoli", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "sheet-pan chicken with jammy tomatoes", - "turkey meatballs", - "swordfish pasta" - ] - }, - { - "name": "short grain brown rice", - "section": "pantry", - "recipes": [ - "sheet pan salmon with broccoli", - "flue flighter chicken stew" + "tomato pasta" ] }, { "name": "parmigiana", "section": "dairy", "recipes": [ - "tomato pasta", - "turkey meatballs" - ] - }, - { - "name": "eggs", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast", - "turkey meatballs" + "tomato pasta" ] - }, - { - "name": "sausages", - "section": "protein", - "recipes": [] - }, - { - "name": "dumplings", - "section": "freezer", - "recipes": [] } ] "###); @@ -1108,7 +596,7 @@ pub mod test { let file = assert_fs::NamedTempFile::new("test3.json")?; file.write_str( r#" - {"checklist":[],"recipes":["tomato pasta"],"items":[{"name":"garlic","section":"fresh","is_ingredient":true,"recipes":["sheet pan salmon with broccoli","crispy tofu with cashews and blistered snap peas","chicken breasts with lemon","hummus","tomato pasta","crispy sheet-pan noodles","flue flighter chicken stew","sheet-pan chicken with jammy tomatoes","swordfish pasta"]},{"name":"tomatoes","section":"fresh","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"basil","section":"fresh","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"lemons","section":"fresh","is_ingredient":true,"recipes":["chicken breasts with lemon","hummus","sheet-pan chicken with jammy tomatoes","flue flighter chicken stew"]},{"name":"pasta","section":"pantry","is_ingredient":true,"recipes":["tomato pasta","swordfish pasta"]},{"name":"olive oil","section":"pantry","is_ingredient":true,"recipes":["sheet pan salmon with broccoli","chicken breasts with lemon","hummus","tomato pasta","sheet-pan chicken with jammy tomatoes","turkey meatballs","swordfish pasta"]},{"name":"short grain brown rice","section":"pantry","is_ingredient":true,"recipes":["sheet pan salmon with broccoli","flue flighter chicken stew"]},{"name":"parmigiana","section":"dairy","is_ingredient":true,"recipes":["tomato pasta","turkey meatballs"]},{"name":"eggs","section":"dairy","is_ingredient":true,"recipes":["oatmeal chocolate chip cookies","fried eggs for breakfast","turkey meatballs"]},{"name":"sausages","section":"protein","is_ingredient":true,"recipes":[]},{"name":"dumplings","section":"freezer","is_ingredient":false,"recipes":[]}]} + {"checklist":[],"recipes":["tomato pasta"],"items":[{"name":"garlic","section":"fresh","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"tomatoes","section":"fresh","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"basil","section":"fresh","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"pasta","section":"pantry","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"olive oil","section":"pantry","is_ingredient":true,"recipes":["tomato pasta"]},{"name":"parmigiana","section":"dairy","is_ingredient":true,"recipes":["tomato pasta"]}]} "# )?; Ok(file) @@ -1168,15 +656,7 @@ pub mod test { "name": "garlic", "section": "fresh", "recipes": [ - "sheet pan salmon with broccoli", - "crispy tofu with cashews and blistered snap peas", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "crispy sheet-pan noodles", - "flue flighter chicken stew", - "sheet-pan chicken with jammy tomatoes", - "swordfish pasta" + "tomato pasta" ] }, { @@ -1193,71 +673,26 @@ pub mod test { "tomato pasta" ] }, - { - "name": "lemons", - "section": "fresh", - "recipes": [ - "chicken breasts with lemon", - "hummus", - "sheet-pan chicken with jammy tomatoes", - "flue flighter chicken stew" - ] - }, { "name": "pasta", "section": "pantry", "recipes": [ - "tomato pasta", - "swordfish pasta" + "tomato pasta" ] }, { "name": "olive oil", "section": "pantry", "recipes": [ - "sheet pan salmon with broccoli", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "sheet-pan chicken with jammy tomatoes", - "turkey meatballs", - "swordfish pasta" - ] - }, - { - "name": "short grain brown rice", - "section": "pantry", - "recipes": [ - "sheet pan salmon with broccoli", - "flue flighter chicken stew" + "tomato pasta" ] }, { "name": "parmigiana", "section": "dairy", "recipes": [ - "tomato pasta", - "turkey meatballs" - ] - }, - { - "name": "eggs", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast", - "turkey meatballs" + "tomato pasta" ] - }, - { - "name": "sausages", - "section": "protein", - "recipes": [] - }, - { - "name": "dumplings", - "section": "freezer", - "recipes": [] } ] } @@ -1279,15 +714,7 @@ pub mod test { "name": "garlic", "section": "fresh", "recipes": [ - "sheet pan salmon with broccoli", - "crispy tofu with cashews and blistered snap peas", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "crispy sheet-pan noodles", - "flue flighter chicken stew", - "sheet-pan chicken with jammy tomatoes", - "swordfish pasta" + "tomato pasta" ] }, { @@ -1304,71 +731,26 @@ pub mod test { "tomato pasta" ] }, - { - "name": "lemons", - "section": "fresh", - "recipes": [ - "chicken breasts with lemon", - "hummus", - "sheet-pan chicken with jammy tomatoes", - "flue flighter chicken stew" - ] - }, { "name": "pasta", "section": "pantry", "recipes": [ - "tomato pasta", - "swordfish pasta" + "tomato pasta" ] }, { "name": "olive oil", "section": "pantry", "recipes": [ - "sheet pan salmon with broccoli", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "sheet-pan chicken with jammy tomatoes", - "turkey meatballs", - "swordfish pasta" - ] - }, - { - "name": "short grain brown rice", - "section": "pantry", - "recipes": [ - "sheet pan salmon with broccoli", - "flue flighter chicken stew" + "tomato pasta" ] }, { "name": "parmigiana", "section": "dairy", "recipes": [ - "tomato pasta", - "turkey meatballs" - ] - }, - { - "name": "eggs", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast", - "turkey meatballs" + "tomato pasta" ] - }, - { - "name": "sausages", - "section": "protein", - "recipes": [] - }, - { - "name": "dumplings", - "section": "freezer", - "recipes": [] } ] } @@ -1394,15 +776,7 @@ pub mod test { "name": "garlic", "section": "fresh", "recipes": [ - "sheet pan salmon with broccoli", - "crispy tofu with cashews and blistered snap peas", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "crispy sheet-pan noodles", - "flue flighter chicken stew", - "sheet-pan chicken with jammy tomatoes", - "swordfish pasta" + "tomato pasta" ] }, { @@ -1419,72 +793,27 @@ pub mod test { "tomato pasta" ] }, - { - "name": "lemons", - "section": "fresh", - "recipes": [ - "chicken breasts with lemon", - "hummus", - "sheet-pan chicken with jammy tomatoes", - "flue flighter chicken stew" - ] - }, { "name": "pasta", "section": "pantry", "recipes": [ - "tomato pasta", - "swordfish pasta" + "tomato pasta" ] }, { "name": "olive oil", "section": "pantry", "recipes": [ - "sheet pan salmon with broccoli", - "chicken breasts with lemon", - "hummus", - "tomato pasta", - "sheet-pan chicken with jammy tomatoes", - "turkey meatballs", - "swordfish pasta" - ] - }, - { - "name": "short grain brown rice", - "section": "pantry", - "recipes": [ - "sheet pan salmon with broccoli", - "flue flighter chicken stew" + "tomato pasta" ] }, { "name": "parmigiana", "section": "dairy", "recipes": [ - "tomato pasta", - "turkey meatballs" - ] - }, - { - "name": "eggs", - "section": "dairy", - "recipes": [ - "oatmeal chocolate chip cookies", - "fried eggs for breakfast", - "turkey meatballs" + "tomato pasta" ] }, - { - "name": "sausages", - "section": "protein", - "recipes": [] - }, - { - "name": "dumplings", - "section": "freezer", - "recipes": [] - }, { "name": "cumquats", "section": "fresh",