pub struct Ingredient { pub name: String, pub quantity: String, pub unit: Option, pub note: Option, } impl std::fmt::Display for Ingredient { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { let n = &self.name; let q = if let Some((whole,frac)) = self.quantity.split_once('-') { match frac_to_char(frac) { Some(frac) => format!("{whole}{frac}"), None => format!("{whole}-{frac}"), } } else if self.quantity.contains('/') { let frac = &self.quantity; match frac_to_char(frac) { Some(frac) => format!("{frac}"), None => format!("{frac}"), } } else { self.quantity.to_string() }; let string = match (&self.unit, &self.note) { (Some(ref u), Some(ref a)) => format!("{q} {u} of {n} ({a})"), (Some(ref u), None) => format!("{q} {u} of {n}"), (None, Some(ref a)) => format!("{q} {n} ({a})"), (None, None) => format!("{q} {n}"), }; f.write_str(&string) } } fn frac_to_char(frac: &str) -> Option<&str> { match frac { "1/2" => Some("½"), "1/3" => Some("⅓"), "1/4" => Some("¼"), "1/8" => Some("⅛"), "2/3" => Some("⅔"), "3/4" => Some("¾"), _ => None, } } pub struct Recipe { pub ingredients: Vec, pub process: Vec, } impl Recipe { pub fn parse(raw_text: &str) -> Self { let mut ingredients = Vec::new(); let mut process = Vec::new(); for line in raw_text.lines() { let line = line.trim(); if line.is_empty() { continue } let mut paragraph = String::new(); let chars: Vec = line.chars().collect(); let mut i = 0; while let Some(c) = chars.get(i) { if *c == '{' { let mut j = i + 1; while let Some(d) = chars.get(j) { if *d == '}' { // {rice, 2 cups} quan unit // {rice, a handful} quan unit // {egg, one} quan let section: String = chars[i+1..j].iter().collect(); let mut clauses = section.split(','); let name = clauses.next().unwrap_or("").trim().to_string(); let (quantity, unit) = { let quantity_unit = clauses.next().unwrap_or("").trim(); match quantity_unit.split_once(' ') { Some((quantity, unit)) => (quantity.to_string(), Some(unit.to_string())), None => (quantity_unit.to_string(), None) } }; let note = clauses.next().and_then(|s| Some(s.trim().to_string())); paragraph.push_str(&name); ingredients.push(Ingredient { name, quantity, unit, note }); i = j; break } j += 1; } } else { paragraph.push(*c); } i += 1; } if !paragraph.is_empty() { process.push(paragraph) } } Self { ingredients, process } } }