diff --git a/recipes/src/lib.rs b/recipes/src/lib.rs index a44ba4f..c9bb3c3 100644 --- a/recipes/src/lib.rs +++ b/recipes/src/lib.rs @@ -135,6 +135,7 @@ impl IngredientAccumulator { self.inner } } + /// A Recipe step. It has the time for the step if there is one, instructions, and an ingredients /// list. #[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] diff --git a/recipes/src/parse.rs b/recipes/src/parse.rs index 3f71e4b..b895a58 100644 --- a/recipes/src/parse.rs +++ b/recipes/src/parse.rs @@ -111,7 +111,7 @@ make_fn!( _ => either!( discard!(text_token!("|")), discard!(eoi), - discard!(text_token!("\n")) + discard!(peek!(text_token!("\n"))) ), (ingredient.trim().to_owned()) ) diff --git a/recipes/src/test.rs b/recipes/src/test.rs index 3ec56e8..29cfb33 100644 --- a/recipes/src/test.rs +++ b/recipes/src/test.rs @@ -507,7 +507,7 @@ step: "; } #[test] -fn test_category_line_happy_path() { +fn test_category_single_line_happy_path() { let line = "Produce: onion|green pepper|bell pepper|corn|potato|green onion|scallions|lettuce"; match parse::as_categories(line) { Ok(map) => { @@ -525,13 +525,58 @@ fn test_category_line_happy_path() { } } +#[test] +fn test_category_double_line_happy_path() { + let line = "Produce: onion|green pepper|bell pepper|corn|potato|green onion|scallions|lettuce\nDairy: milk|butter"; + match parse::as_categories(line) { + Ok(map) => { + assert_eq!(map.len(), 10); + + assert!( + map.contains_key("onion"), + "map does not contain onion {:?}", + map + ); + assert!( + map.contains_key("milk"), + "map does not contain milk {:?}", + map + ); + assert_eq!(map["milk"], "Dairy"); + println!("{:?}", map); + } + Err(e) => { + assert!(false, "{:?}", e); + } + } +} + +#[test] +fn test_triple_line() { + let line = "Produce: onion|green pepper|bell pepper|corn|potato|green onion|scallions|lettuce +Meat: ground beef|beef|pork|chicken|sausage|hot dogs|bacon|lamb +Dairy: milk|butter|heavy cream|cheddar cheese|mozarella|cheddar|white american|american|swiss"; + match parse::as_categories(line) { + Ok(map) => { + let mut categories = BTreeSet::new(); + categories.extend(map.values()); + println!("map: {:?}", map); + assert_eq!(categories.len(), 3); + } + Err(e) => { + assert!(false, "{:?}", e); + } + } +} + #[test] fn test_category_single_ingredient_happy_paths() { - let ingredients = vec!["foo", "foo\n", "foo|"]; + let ingredients = vec!["foo", "foo\n", "foo|", "foo\nCategory: "]; for ingredient in ingredients { match parse::cat_ingredient(StrIter::new(ingredient)) { ParseResult::Complete(_itr, _i) => { // yay we pass + assert_eq!(_i, "foo"); } res => { assert!(false, "{:?}", res); diff --git a/run.sh b/run.sh index 5735705..84f14e8 100755 --- a/run.sh +++ b/run.sh @@ -16,6 +16,8 @@ make clean kitchen pushd web trunk serve \ --public-url /ui \ + --watch . \ + --watch ../recipes \ --proxy-backend http://localhost:3030/api/v1 & trunkpid=$! popd diff --git a/web/src/components/shopping_list.rs b/web/src/components/shopping_list.rs index cd4cd07..c5d343e 100644 --- a/web/src/components/shopping_list.rs +++ b/web/src/components/shopping_list.rs @@ -54,8 +54,9 @@ pub fn shopping_list() -> View { let amt = modified_amt_set.entry(k.clone()).or_insert(Signal::new(format!("{}", i.amt.normalize()))).clone(); modified_amts.set(modified_amt_set); let name = i.name; + let category = if i.category == "" { "other".to_owned() } else { i.category }; let form = i.form.map(|form| format!("({})", form)).unwrap_or_default(); - let names = rs.iter().fold(String::new(), |acc, s| format!("{}{},", acc, s)).trim_end_matches(",").to_owned(); + let recipes = rs.iter().fold(String::new(), |acc, s| format!("{}{},", acc, s)).trim_end_matches(",").to_owned(); view! { tr { td { @@ -68,8 +69,8 @@ pub fn shopping_list() -> View { filtered_keys.set(keyset); })) } - td { (name) " " (form) } - td { (names) } + td { (name) " " (form) "" br {} "" (category) "" } + td { (recipes) } } } }), @@ -82,7 +83,6 @@ pub fn shopping_list() -> View { } }), ); - // TODO(jwall): Sort by categories and names. view! { h1 { "Shopping List " } (table_view.get().as_ref().clone()) diff --git a/web/src/service.rs b/web/src/service.rs index d7f37ae..9b90be7 100644 --- a/web/src/service.rs +++ b/web/src/service.rs @@ -85,6 +85,7 @@ impl AppService { console_log!("Synchronizing categories"); match Self::fetch_categories_http().await { Ok(Some(categories_content)) => { + console_debug!("categories: {}", categories_content); storage .set_item("categories", &categories_content) .map_err(|e| format!("{:?}", e))?; @@ -105,13 +106,16 @@ impl AppService { .get_item("categories") .map_err(|e| format!("{:?}", e))? { - Some(s) => match parse::as_categories(&s) { - Ok(categories) => Ok(Some(categories)), - Err(e) => { - console_debug!("Error parsing categories {}", e); - Err(format!("Error parsing categories {}", e)) + Some(s) => { + let parsed = serde_json::from_str::(&s).map_err(|e| format!("{}", e))?; + match parse::as_categories(&parsed) { + Ok(categories) => Ok(Some(categories)), + Err(e) => { + console_debug!("Error parsing categories {}", e); + Err(format!("Error parsing categories {}", e)) + } } - }, + } None => Ok(None), } } @@ -136,7 +140,7 @@ impl AppService { continue; } }; - console_debug!("We parsed a recipe {}", recipe.title); + //console_debug!("We parsed a recipe {}", recipe.title); if recipe.title == "Staples" { staples = Some(recipe); } else { @@ -186,7 +190,17 @@ impl AppService { if let Some(staples) = self.staples.get().as_ref() { acc.accumulate_from(staples); } - acc.ingredients() + let mut ingredients = acc.ingredients(); + self.category_map.get().as_ref().as_ref().map(|cm| { + for (_, (i, _)) in ingredients.iter_mut() { + if let Some(cat) = cm.get(&i.name) { + i.category = cat.clone(); + } + } + }); + console_debug!("{:?}", self.category_map); + // TODO(jwall): Sort by categories and names. + ingredients } pub fn set_recipe_count_by_index(&mut self, i: usize, count: usize) {