Initial commit

This commit is contained in:
Ben Bridle 2022-08-25 20:57:37 +12:00
commit 6eee110291
4 changed files with 204 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "recipe_parser"
version = "1.0.0"
authors = ["Ben Bridle <bridle.benjamin@gmail.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

171
src/lib.rs Normal file
View File

@ -0,0 +1,171 @@
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;
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,
))
}

22
src/main.rs Normal file
View File

@ -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);
}
}