flake_edit/app/
editor.rs

1use std::fs::File;
2use std::io;
3use std::path::PathBuf;
4use std::process::Command;
5
6use ropey::Rope;
7
8use crate::diff::Diff;
9use crate::edit::FlakeEdit;
10use crate::error::FlakeEditError;
11use crate::validate;
12
13use super::state::AppState;
14
15/// Buffer for a flake file with its content and path.
16#[derive(Debug, Default)]
17pub struct FlakeBuf {
18    text: Rope,
19    path: PathBuf,
20}
21
22impl FlakeBuf {
23    pub fn from_path(path: PathBuf) -> io::Result<Self> {
24        let text = Rope::from_reader(&mut io::BufReader::new(File::open(&path)?))?;
25        Ok(Self { text, path })
26    }
27
28    pub fn text(&self) -> &Rope {
29        &self.text
30    }
31
32    pub fn path(&self) -> &PathBuf {
33        &self.path
34    }
35
36    pub fn write(&self, content: &str) -> io::Result<()> {
37        std::fs::write(&self.path, content)
38    }
39}
40
41/// Editor that drives changes to flake.nix files.
42///
43/// Handles file I/O, applying changes, and running nix flake lock.
44#[derive(Debug)]
45pub struct Editor {
46    flake: FlakeBuf,
47}
48
49impl Editor {
50    pub fn new(flake: FlakeBuf) -> Self {
51        Self { flake }
52    }
53
54    pub fn from_path(path: PathBuf) -> io::Result<Self> {
55        let flake = FlakeBuf::from_path(path)?;
56        Ok(Self { flake })
57    }
58
59    pub fn text(&self) -> String {
60        self.flake.text().to_string()
61    }
62
63    pub fn path(&self) -> &PathBuf {
64        self.flake.path()
65    }
66
67    pub fn create_flake_edit(&self) -> Result<FlakeEdit, FlakeEditError> {
68        FlakeEdit::from_text(&self.text())
69    }
70
71    fn run_nix_flake_lock(&self) -> io::Result<()> {
72        let flake_dir = match self.flake.path.parent() {
73            Some(parent) if !parent.as_os_str().is_empty() => parent.to_path_buf(),
74            _ => PathBuf::from("."),
75        };
76
77        let output = Command::new("nix")
78            .args(["flake", "lock"])
79            .current_dir(&flake_dir)
80            .output()?;
81
82        if !output.status.success() {
83            let stderr = String::from_utf8_lossy(&output.stderr);
84            eprintln!("Warning: nix flake lock failed: {}", stderr);
85            return Err(io::Error::other(format!(
86                "nix flake lock failed: {}",
87                stderr
88            )));
89        }
90
91        println!("Updated flake.lock");
92        Ok(())
93    }
94
95    /// Apply changes to the flake file, or show diff if in diff mode.
96    ///
97    /// Validates the new content for duplicate attributes before writing.
98    pub fn apply_or_diff(&self, new_content: &str, state: &AppState) -> Result<(), FlakeEditError> {
99        let validation = validate::validate(new_content);
100        if validation.has_errors() {
101            return Err(FlakeEditError::Validation(validation.errors));
102        }
103
104        if state.diff {
105            let old = self.text();
106            let diff = Diff::new(&old, new_content);
107            diff.compare();
108        } else {
109            self.flake.write(new_content)?;
110
111            if !state.no_lock
112                && let Err(e) = self.run_nix_flake_lock()
113            {
114                eprintln!("Warning: Failed to update lockfile: {}", e);
115            }
116        }
117        Ok(())
118    }
119}