summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2022-08-25 20:57:37 +1200
committerBen Bridle <bridle.benjamin@gmail.com>2022-08-25 20:57:37 +1200
commit6eee1102918857b11ecc193e684e2438520ef90f (patch)
tree19533d851c646331c38965a2ab3fd91a15f131a3
downloadrecipe-6eee1102918857b11ecc193e684e2438520ef90f.zip
Initial commitv1.0
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml9
-rw-r--r--src/lib.rs171
-rw-r--r--src/main.rs22
4 files changed, 204 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..96ef6c0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..8bc9dd4
--- /dev/null
+++ b/Cargo.toml
@@ -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]
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b7db9df
--- /dev/null
+++ b/src/lib.rs
@@ -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,
+ ))
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..96fac3a
--- /dev/null
+++ b/src/main.rs
@@ -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);
+ }
+}