diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 230 |
1 files changed, 77 insertions, 153 deletions
@@ -2,176 +2,100 @@ pub struct Ingredient { pub name: String, pub quantity: String, pub unit: Option<String>, - pub addendum: Option<String>, + pub note: Option<String>, +} + +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 title: Option<String>, pub ingredients: Vec<Ingredient>, pub process: Vec<String>, } + impl Recipe { - pub fn parse(recipe: &str) -> Self { + pub fn parse(raw_text: &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; - } + for line in raw_text.lines() { + let line = line.trim(); + if line.is_empty() { continue } + let mut paragraph = String::new(); let chars: Vec<char> = 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; + + 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); } - } - } - - 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, + if !paragraph.is_empty() { process.push(paragraph) } } - } - // Eat spaces - loop { - match chars.get(i) { - Some(&' ') => i += 1, - Some(_) => break, - None => return None, - } + Self { ingredients, process } } - - // Quantity - loop { - match chars.get(i) { - Some(&' ') => { - i += 1; - unit = Some(String::new()); - break; - } - Some(&',') => { - i += 1; - addendum = 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, - )) } + |