flake_edit/
edit.rs

1use std::collections::HashMap;
2
3use crate::change::Change;
4use crate::error::FlakeEditError;
5use crate::input::Input;
6use crate::walk::Walker;
7
8pub struct FlakeEdit {
9    changes: Vec<Change>,
10    walker: Walker,
11}
12
13#[derive(Default, Debug)]
14pub enum Outputs {
15    #[default]
16    None,
17    // needs strict inputs to output mapping
18    Multiple(Vec<String>),
19    // contains ...
20    Any(Vec<String>),
21}
22
23pub type InputMap = HashMap<String, Input>;
24
25#[derive(Default, Debug)]
26pub enum OutputChange {
27    #[default]
28    None,
29    Add(String),
30    Remove(String),
31}
32
33impl FlakeEdit {
34    pub fn new(changes: Vec<Change>, walker: Walker) -> Self {
35        Self { changes, walker }
36    }
37
38    pub fn from_text(stream: &str) -> Result<Self, FlakeEditError> {
39        let walker = Walker::new(stream);
40        Ok(Self::new(Vec::new(), walker))
41    }
42
43    pub fn changes(&self) -> &[Change] {
44        self.changes.as_ref()
45    }
46
47    pub fn add_change(&mut self, change: Change) {
48        self.changes.push(change);
49    }
50
51    pub fn curr_list(&self) -> &InputMap {
52        &self.walker.inputs
53    }
54
55    /// Will walk and then list the inputs, for listing the current inputs,
56    /// use `curr_list()`.
57    pub fn list(&mut self) -> &InputMap {
58        self.walker.inputs.clear();
59        assert!(self.walker.walk(&Change::None).is_none());
60        &self.walker.inputs
61    }
62    /// Apply a specific change to a walker, on some inputs it will need to walk
63    /// multiple times, will error, if the edit could not be applied successfully.
64    pub fn apply_change(&mut self, change: Change) -> Result<Option<String>, FlakeEditError> {
65        match change {
66            Change::None => Ok(None),
67            //TODO: Add outputs, if needed.
68            Change::Add { .. } => {
69                // Check for duplicate input before adding
70                if let Some(input_id) = change.id() {
71                    // First walk to populate the inputs map if it's empty
72                    if self.walker.inputs.is_empty() {
73                        self.walker.walk(&Change::None);
74                    }
75
76                    let input_id_string = input_id.to_string();
77                    if self.walker.inputs.contains_key(&input_id_string) {
78                        return Err(FlakeEditError::DuplicateInput(input_id_string));
79                    }
80                }
81
82                if let Some(maybe_changed_node) = self.walker.walk(&change.clone()) {
83                    let outputs = self.walker.list_outputs();
84                    match outputs {
85                        Outputs::Multiple(out) => {
86                            let id = change.id().unwrap().to_string();
87                            if !out.contains(&id) {
88                                self.walker.root = maybe_changed_node.clone();
89                                if let Some(maybe_changed_node) =
90                                    self.walker.change_outputs(OutputChange::Add(id))
91                                {
92                                    return Ok(Some(maybe_changed_node.to_string()));
93                                }
94                            }
95                        }
96                        Outputs::None | Outputs::Any(_) => {}
97                    }
98                    Ok(Some(maybe_changed_node.to_string()))
99                } else {
100                    self.walker.add_toplevel = true;
101                    let maybe_changed_node = self.walker.walk(&change);
102                    Ok(maybe_changed_node.map(|n| n.to_string()))
103                }
104            }
105            Change::Remove { .. } => {
106                // If we remove a node, it could be a flat structure,
107                // we want to remove all of the references to its toplevel.
108                let mut res = None;
109                while let Some(changed_node) = self.walker.walk(&change) {
110                    if res == Some(changed_node.clone()) {
111                        // TODO: Sanity check, turn into proper error.
112                        break;
113                    }
114                    res = Some(changed_node.clone());
115                    self.walker.root = changed_node.clone();
116                }
117                // Removed nodes should be removed from the outputs
118                let outputs = self.walker.list_outputs();
119                match outputs {
120                    Outputs::Multiple(out) | Outputs::Any(out) => {
121                        let id = change.id().unwrap().to_string();
122                        if out.contains(&id) {
123                            if let Some(changed_node) =
124                                self.walker.change_outputs(OutputChange::Remove(id))
125                            {
126                                res = Some(changed_node.clone());
127                                self.walker.root = changed_node.clone();
128                            }
129                        }
130                    }
131                    Outputs::None => {}
132                }
133                Ok(res.map(|n| n.to_string()))
134            }
135            Change::Pin { .. } => todo!(),
136            Change::Change { .. } => {
137                if let Some(maybe_changed_node) = self.walker.walk(&change) {
138                    Ok(Some(maybe_changed_node.to_string()))
139                } else {
140                    panic!("No change");
141                }
142            }
143        }
144    }
145
146    pub fn walker(&self) -> &Walker {
147        &self.walker
148    }
149}