averse/
add.rs

1//! Module for interactively adding recipes
2use crate::utils::{get_recipe_out_path, input_msg, print_table, title};
3use crate::{Ingredient, IngredientRow, Recipe, StepRow};
4use colored::*;
5use dialoguer::Input;
6use std::fs;
7use std::io;
8use std::str::FromStr;
9
10/// Adds recipe to `recipe_dir` interactively.
11/// Displays a table of current ingredients / steps
12pub fn add_recipe(recipe_dir: &String) -> io::Result<()> {
13    title("\t\u{21F8} Recipe Name\n\n");
14    let name: String = Input::new()
15        .with_prompt("Enter recipe name")
16        .interact_text()?;
17    let recipe_path = get_recipe_out_path(&recipe_dir, &name);
18    let tags = add_tags().expect("Failed to parse tags");
19    let ingredients = add_ingredients().expect("Failed adding ingredients");
20    let steps = add_steps().expect("Failed adding steps");
21    let recipe = Recipe {
22        name,
23        tags,
24        ingredients,
25        steps,
26    };
27    let serialized = serde_yaml::to_string(&recipe).expect("Failed to serialize recipe");
28    fs::write(&recipe_path, &serialized).expect("Failed to save recipe");
29    println!("Recipe saved to {}", recipe_path.to_str().unwrap());
30    Ok(())
31}
32
33/// Ask user to input tags for recipe
34fn add_tags() -> io::Result<Vec<String>> {
35    title("\t\u{21F8} Tags\n\n");
36    Ok(input_msg("Enter associated tags (e.g. soup, mealprep)")?
37        .split(", ")
38        .map(|s| s.to_owned())
39        .collect())
40}
41
42/// Ask user to add ingredient with loop for bad input
43fn add_ingredients() -> io::Result<Vec<Ingredient>> {
44    let base = "\t\u{21F8} Ingredients\n\n";
45    let mut rows: Vec<IngredientRow> = vec![];
46    let mut ingredients: Vec<Ingredient> = vec![];
47    title(&format!(
48        "{}<AMOUNT> <UNIT> <INGREDIENT> (Ex: 1 lb beef)",
49        base
50    ));
51    loop {
52        if !ingredients.is_empty() {
53            print_table(&rows);
54        }
55        let ingredient_string: String = input_msg("Enter ingredient (or ENTER to continue)")?;
56        if ingredient_string.is_empty() {
57            break;
58        }
59        let ingredient = match Ingredient::from_str(&ingredient_string) {
60            Ok(ingr) => ingr,
61            Err(e) => {
62                title(&format!("{} (or ENTER to continue)", base));
63                println!("{e}\n{}\n", "...Please try again.".red());
64                continue;
65            }
66        };
67        ingredients.push(ingredient.clone());
68        rows.push(ingredient.try_into().expect("IngredientRow failed"));
69        title(&format!("{} (or ENTER to continue)", base));
70    }
71    Ok(ingredients)
72}
73
74/// Ask user for recipe steps
75fn add_steps() -> io::Result<Vec<String>> {
76    let msg = "\t\u{21F8} Steps\n\n";
77    title(msg);
78    let mut steps: Vec<String> = vec![];
79    let mut rows: Vec<StepRow> = vec![];
80    let mut i: u16 = 1;
81    loop {
82        if !steps.is_empty() {
83            print_table(&rows);
84        }
85        let step: String = input_msg("Enter step (or ENTER to quit)")?;
86        if step.is_empty() {
87            break;
88        };
89        steps.push(step.clone());
90        rows.push(StepRow {
91            Step: i,
92            Details: step,
93        });
94        i += 1;
95        title(msg);
96    }
97    Ok(steps)
98}