ghost_core/recipe/
store.rs1use anyhow::{Context, Result};
4use std::path::PathBuf;
5
6use super::types::Recipe;
7
8pub struct RecipeStore {
10 dir: PathBuf,
11}
12
13impl RecipeStore {
14 pub fn open() -> Result<Self> {
16 let dir = dirs::home_dir()
17 .ok_or_else(|| anyhow::anyhow!("Cannot determine home directory"))?
18 .join(".ghost")
19 .join("recipes");
20 std::fs::create_dir_all(&dir)
21 .with_context(|| format!("Failed to create recipe dir: {}", dir.display()))?;
22 Ok(Self { dir })
23 }
24
25 pub fn open_at(dir: PathBuf) -> Result<Self> {
27 std::fs::create_dir_all(&dir)?;
28 Ok(Self { dir })
29 }
30
31 pub fn list(&self) -> Result<Vec<Recipe>> {
33 let mut recipes = vec![];
34 for entry in std::fs::read_dir(&self.dir)? {
35 let entry = entry?;
36 let path = entry.path();
37 if path.extension().and_then(|e| e.to_str()) == Some("json") {
38 if let Ok(r) = self.load_file(&path) {
39 recipes.push(r);
40 }
41 }
42 }
43 recipes.sort_by(|a, b| a.name.cmp(&b.name));
44 Ok(recipes)
45 }
46
47 pub fn get(&self, name: &str) -> Result<Recipe> {
49 let path = self.recipe_path(name);
50 self.load_file(&path)
51 .with_context(|| format!("Recipe '{}' not found", name))
52 }
53
54 pub fn save(&self, recipe: &Recipe) -> Result<()> {
56 let path = self.recipe_path(&recipe.name);
57 let json = serde_json::to_string_pretty(recipe)?;
58 std::fs::write(&path, json)
59 .with_context(|| format!("Failed to write recipe to {}", path.display()))
60 }
61
62 pub fn save_json(&self, json: &str) -> Result<Recipe> {
64 let recipe: Recipe = serde_json::from_str(json).context("Invalid recipe JSON")?;
65 self.save(&recipe)?;
66 Ok(recipe)
67 }
68
69 pub fn delete(&self, name: &str) -> Result<()> {
71 let path = self.recipe_path(name);
72 if path.exists() {
73 std::fs::remove_file(&path)
74 .with_context(|| format!("Failed to delete recipe '{}'", name))
75 } else {
76 Err(anyhow::anyhow!("Recipe '{}' not found", name))
77 }
78 }
79
80 fn recipe_path(&self, name: &str) -> PathBuf {
81 let safe = name.replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], "_");
82 self.dir.join(format!("{safe}.json"))
83 }
84
85 fn load_file(&self, path: &PathBuf) -> Result<Recipe> {
86 let data = std::fs::read_to_string(path)?;
87 serde_json::from_str(&data).with_context(|| format!("Invalid JSON in {}", path.display()))
88 }
89}