recipe_parser/src/lib.rs

178 lines
4.3 KiB
Rust

pub struct Ingredient {
pub name: String,
pub quantity: String,
pub unit: Option<String>,
pub addendum: Option<String>,
}
pub struct Recipe {
pub title: Option<String>,
pub ingredients: Vec<Ingredient>,
pub process: Vec<String>,
}
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<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;
}
}
}
}
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;
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,
))
}