commit 6eee1102918857b11ecc193e684e2438520ef90f Author: Ben Bridle Date: Thu Aug 25 20:57:37 2022 +1200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8bc9dd4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "recipe_parser" +version = "1.0.0" +authors = ["Ben Bridle "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b7db9df --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,171 @@ +pub struct Ingredient { + pub name: String, + pub quantity: String, + pub unit: Option, + pub addendum: Option, +} + +pub struct Recipe { + pub title: Option, + pub ingredients: Vec, + pub process: Vec, +} +impl Recipe { + pub fn parse(recipe: &str) -> Self { + let mut ingredients = Vec::new(); + let mut process = Vec::new(); + let mut paragraph = String::new(); + let mut title = None; + + for line in recipe.lines() { + if line.trim().is_empty() { + let paragraph = std::mem::take(&mut paragraph); + if !paragraph.is_empty() { + process.push(paragraph); + } + continue; + } + if line.starts_with("# ") && title.is_none() { + title = Some(line[2..].to_string()); + continue; + } + + let chars: Vec = line.chars().collect(); + let mut i = 0; + while i < chars.len() { + match capture(&chars[i..]) { + Some((ingredient, length)) => { + paragraph.push_str(&ingredient.name); + ingredients.push(ingredient); + i += length; + } + None => { + paragraph.push(*chars.get(i).unwrap()); + i += 1; + } + } + } + } + + let paragraph = std::mem::take(&mut paragraph); + if !paragraph.is_empty() { + process.push(paragraph); + } + + Self { + title, + ingredients, + process, + } + } +} + +fn capture(chars: &[char]) -> Option<(Ingredient, usize)> { + if chars.get(0) != Some(&'{') { + return None; + } + let mut i = 1; + let mut name = String::new(); + let mut quantity = String::new(); + let mut unit = None; + let mut addendum = None; + + // Ingredient name + loop { + match chars.get(i) { + Some(&',') => { + i += 1; + break; + } + Some(c) => { + name.push(*c); + i += 1; + } + None => return None, + } + } + + // Eat spaces + loop { + match chars.get(i) { + Some(&' ') => i += 1, + Some(_) => break, + None => return None, + } + } + + // Quantity + loop { + match chars.get(i) { + Some(&' ') => { + i += 1; + unit = Some(String::new()); + break; + } + Some(&'}') => { + i += 1; + break; + } + Some(c) => { + quantity.push(*c); + i += 1; + } + None => return None, + } + } + + // Unit + if let Some(ref mut unit) = unit { + loop { + match chars.get(i) { + Some(&'}') => { + i += 1; + break; + } + Some(&',') => { + i += 1; + addendum = Some(String::new()); + break; + } + Some(c) => { + unit.push(*c); + i += 1; + } + None => return None, + } + } + } + + // Addendum + if let Some(ref mut addendum) = addendum { + loop { + match chars.get(i) { + Some(&'}') => { + i += 1; + break; + } + Some(c) => { + addendum.push(*c); + i += 1; + } + None => return None, + } + } + } + + // Trim values + let name = name.trim().to_string(); + let quantity = quantity.trim().to_string(); + let unit = unit.and_then(|s| Some(s.trim().to_string())); + let addendum = addendum.and_then(|s| Some(s.trim().to_string())); + + Some(( + Ingredient { + name, + quantity, + unit, + addendum, + }, + i, + )) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..96fac3a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,22 @@ +use recipe_parser::Recipe; + +fn main() { + let recipe = Recipe::parse( + "Combine {bulghur wheat, 1 cup} and {boiling water, 1 cup, for wheat}. Set aside for 20 minutes. Put {whole-meal flour, 2 cups}, {salt, 2 tsp}, and {yeast, 2 tbsp} in a bowl and stir together. Add {cold water, 1 cup} and {golden syrup, 1 tbsp}, immediately followed by {boiling water, 1 cup}. Stir to a smooth paste and stand for 2 to 3 minutes. Mix in the {egg, 1} and {high-grade flour, 3 cups}, adding the last cup of flour slowly (more or less than the cup may be needed to give a very thick batter, which is not quite as stiff as dough). Mix for 3 to 4 minutes. Cover and put in a warm place for 15 minutes. Stir well and pour into two 22cm greased loaf tins. Put in a warm place until the dough doubles in volume. Bake at 200°C for 35 minutes or until the loaf sounds hollow when tapped on the bottom. If loaf is browning too quickly, cover with foil.", + ); + for i in recipe.ingredients { + let unit = match i.unit { + Some(unit) => format!("{} of ", unit), + None => String::new(), + }; + let addendum = match i.addendum { + Some(addendum) => format!(" ({})", addendum), + None => String::new(), + }; + println!("- {} {}{}{}", i.quantity, unit, i.name, addendum); + } + println!(); + for paragraph in recipe.process { + println!("{}\n", paragraph); + } +}