Skip to main content

bnto_core/editor/
io.rs

1// Editor file I/O — load and save `.bnto.json` files.
2
3use std::path::{Path, PathBuf};
4
5use crate::definition::Definition;
6
7use super::convert::slug_from_name;
8use super::types::{EditorError, EditorModel, EditorSource};
9
10impl EditorModel {
11    /// Load a recipe from a `.bnto.json` file.
12    pub fn load(path: &Path) -> Result<Self, EditorError> {
13        if !path.exists() {
14            return Err(EditorError::NotFound(path.to_path_buf()));
15        }
16        let content = std::fs::read_to_string(path)?;
17        let def: Definition =
18            serde_json::from_str(&content).map_err(|e| EditorError::InvalidJson(e.to_string()))?;
19        Ok(Self::from_definition(
20            &def,
21            EditorSource::File(path.to_path_buf()),
22        ))
23    }
24
25    /// Save the current editor state to a `.bnto.json` file.
26    /// Uses atomic write (write to temp, then rename) to prevent corruption.
27    pub fn save_to(&self, path: &Path) -> Result<(), EditorError> {
28        let def = self.to_definition();
29        let json = serde_json::to_string_pretty(&def)
30            .map_err(|e| EditorError::InvalidJson(e.to_string()))?;
31
32        if let Some(parent) = path.parent() {
33            std::fs::create_dir_all(parent)?;
34        }
35
36        // Atomic write: write to a sibling temp file, then rename.
37        let temp_path = path.with_extension("bnto.json.tmp");
38        std::fs::write(&temp_path, &json)?;
39        std::fs::rename(&temp_path, path)?;
40
41        Ok(())
42    }
43
44    /// Resolve the save destination path based on the recipe's source.
45    ///
46    /// - `File(path)` → save back to that path
47    /// - `New` or `Predefined` → save to `recipes_dir/{slug}.bnto.json`
48    pub fn save_path(&self, recipes_dir: &Path) -> PathBuf {
49        match &self.source {
50            EditorSource::File(path) => path.clone(),
51            EditorSource::New | EditorSource::Predefined(_) => {
52                let slug = slug_from_name(&self.recipe_name);
53                let filename = if slug.is_empty() {
54                    "untitled".to_string()
55                } else {
56                    slug
57                };
58                recipes_dir.join(format!("{filename}.bnto.json"))
59            }
60        }
61    }
62}