summaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: e671989703128ed9526c87ae79f6cc064ad99539 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
pub struct Ingredient {
    pub name: String,
    pub quantity: String,
    pub unit: Option<String>,
    pub note: Option<String>,
}

impl std::fmt::Display for Ingredient {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        let n = &self.name;
        let q = if let Some((whole,frac)) = self.quantity.split_once('-') {
            match frac_to_char(frac) {
                Some(frac) => format!("{whole}{frac}"),
                None => format!("{whole}-{frac}"),
            }
        } else if self.quantity.contains('/') {
            let frac = &self.quantity;
            match frac_to_char(frac) {
                Some(frac) => format!("{frac}"),
                None => format!("{frac}"),
            }
        } else {
            self.quantity.to_string()
        };

        let string = match (&self.unit, &self.note) {
            (Some(ref u), Some(ref a)) => format!("{q} {u} of {n} ({a})"),
            (Some(ref u), None) => format!("{q} {u} of {n}"),
            (None, Some(ref a)) => format!("{q} {n} ({a})"),
            (None, None) => format!("{q} {n}"),
        };
        f.write_str(&string)
    }
}

fn frac_to_char(frac: &str) -> Option<&str> {
    match frac {
        "1/2" => Some("½"),
        "1/3" => Some("⅓"),
        "1/4" => Some("¼"),
        "1/8" => Some("⅛"),
        "2/3" => Some("⅔"),
        "3/4" => Some("¾"),
        _ => None,
    }
}

pub struct Recipe {
    pub ingredients: Vec<Ingredient>,
    pub process: Vec<String>,
}

impl Recipe {
    pub fn parse(raw_text: &str) -> Self {
        let mut ingredients = Vec::new();
        let mut process = Vec::new();

        for line in raw_text.lines() {
            let line = line.trim();
            if line.is_empty() { continue }
            let mut paragraph = String::new();
            let chars: Vec<char> = line.chars().collect();
            let mut i = 0;

            while let Some(c) = chars.get(i) {
                if *c == '{' {
                    let mut j = i + 1;
                    while let Some(d) = chars.get(j) {
                        if *d == '}' {
                            // {rice, 2 cups}  quan unit
                            // {rice, a handful} quan unit
                            // {egg, one}  quan
                            let section: String = chars[i+1..j].iter().collect();
                            let mut clauses = section.split(',');
                            let name = clauses.next().unwrap_or("").trim().to_string();
                            let (quantity, unit) = {
                                let quantity_unit = clauses.next().unwrap_or("").trim();
                                match quantity_unit.split_once(' ') {
                                    Some((quantity, unit)) => (quantity.to_string(), Some(unit.to_string())),
                                    None => (quantity_unit.to_string(), None)
                                }
                            };
                            let note = clauses.next().and_then(|s| Some(s.trim().to_string()));
                            paragraph.push_str(&name);
                            ingredients.push(Ingredient { name, quantity, unit, note });
                            i = j; break
                        }
                        j += 1;
                    }
                } else {
                    paragraph.push(*c);
                }
                i += 1;
            }
            if !paragraph.is_empty() { process.push(paragraph) }
        }

        Self { ingredients, process }
    }
}