Initial commit
This commit is contained in:
commit
6eee110291
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -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]
|
|
@ -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,
|
||||
))
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue