178 lines
4.3 KiB
Rust
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,
|
|
))
|
|
}
|