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)
65 .context("Invalid recipe JSON")?;
66 self.save(&recipe)?;
67 Ok(recipe)
68 }
69
70 pub fn delete(&self, name: &str) -> Result<()> {
72 let path = self.recipe_path(name);
73 if path.exists() {
74 std::fs::remove_file(&path)
75 .with_context(|| format!("Failed to delete recipe '{}'", name))
76 } else {
77 Err(anyhow::anyhow!("Recipe '{}' not found", name))
78 }
79 }
80
81 fn recipe_path(&self, name: &str) -> PathBuf {
82 let safe = name.replace(['/', '\\', ':', '*', '?', '"', '<', '>', '|'], "_");
83 self.dir.join(format!("{safe}.json"))
84 }
85
86 fn load_file(&self, path: &PathBuf) -> Result<Recipe> {
87 let data = std::fs::read_to_string(path)?;
88 serde_json::from_str(&data).with_context(|| format!("Invalid JSON in {}", path.display()))
89 }
90}