diff --git a/recipes/src/parse.rs b/recipes/src/parse.rs index e9d9168..bf95e5d 100644 --- a/recipes/src/parse.rs +++ b/recipes/src/parse.rs @@ -16,7 +16,7 @@ use std::time::Duration; use abortable_parser::{ ascii_digit, consume_all, discard, do_each, either, eoi, make_fn, must, not, optional, peek, - repeat, separated, text_token, trap, until, Result, StrIter, + repeat, separated, text_token, trap, until, with_err, Result, StrIter, }; use inflector::Inflector; use num_rational::Ratio; @@ -37,7 +37,7 @@ pub fn as_recipe(i: &str) -> std::result::Result { make_fn!( pub recipe, do_each!( - title => title, + title => must!(title), _ => optional!(para_separator), desc => optional!(do_each!( _ => peek!(not!(step_prefix)), @@ -127,7 +127,7 @@ make_fn!( pub step, do_each!( dur => step_prefix, - ingredients => must!(ingredient_list), + ingredients => with_err!(must!(ingredient_list), "Missing ingredient list"), _ => para_separator, desc => description, _ => either!(discard!(para_separator), eoi), @@ -138,7 +138,7 @@ make_fn!( make_fn!( pub step_list>, do_each!( - first_step => must!(step), + first_step => with_err!(must!(step), "Missing recipe steps"), rest => repeat!(step), ({ let mut steps = vec![first_step]; @@ -303,6 +303,21 @@ pub fn measure(i: StrIter) -> abortable_parser::Result { } } +pub fn normalize_name(name: &str) -> String { + let parts: Vec<&str> = name.split_whitespace().collect(); + if parts.len() >= 2 { + let mut prefix = parts[0..parts.len() - 1].join(" "); + // NOTE(jwall): The below unwrap is safe because of the length + // check above. + let last = parts.last().unwrap(); + let normalized = last.to_singular(); + prefix.push(' '); + prefix.push_str(&normalized); + return prefix; + } + return name.trim().to_owned(); +} + make_fn!( pub ingredient_name, do_each!( @@ -310,7 +325,7 @@ make_fn!( discard!(text_token!("\n")), eoi, discard!(text_token!("(")))), - (name.trim().to_singular()) + (normalize_name(name)) ) ); @@ -318,8 +333,8 @@ make_fn!( ingredient_modifier, do_each!( _ => text_token!("("), - modifier => until!(text_token!(")")), - _ => text_token!(")"), + modifier => must!(until!(text_token!(")"))), + _ => must!(text_token!(")")), (modifier) ) ); diff --git a/recipes/src/test.rs b/recipes/src/test.rs index a07f7ca..7dc056f 100644 --- a/recipes/src/test.rs +++ b/recipes/src/test.rs @@ -466,3 +466,33 @@ until thickened. Set aside to cool. err => assert!(false, "{:?}", err), } } + +#[test] +fn test_recipe_missing_steps_parse_failure() { + let recipe = "title: gooey apple bake + +A simple gooey apple bake recipe. +"; + match parse::recipe(StrIter::new(recipe)) { + ParseResult::Abort(e) => { + assert_eq!(e.get_msg(), "Missing recipe steps"); + } + other => assert!(false, "{:?}", other), + } +} + +#[test] +fn test_step_no_ingredients_parse_failure() { + let step = "step: + +step: "; + match parse::step(StrIter::new(step)) { + ParseResult::Abort(e) => { + eprintln!("err: {:?}", e); + assert_eq!(e.get_msg(), "Missing ingredient list"); + } + other => { + assert!(false, "{:?}", other); + } + } +}