1use std::collections::HashMap;
2
3use crate::change::Change;
4use crate::error::FlakeEditError;
5use crate::input::{Follows, Input};
6use crate::validate;
7use crate::walk::Walker;
8
9pub struct FlakeEdit {
10 changes: Vec<Change>,
11 walker: Walker,
12}
13
14#[derive(Default, Debug)]
15pub enum Outputs {
16 #[default]
17 None,
18 Multiple(Vec<String>),
19 Any(Vec<String>),
20}
21
22pub type InputMap = HashMap<String, Input>;
23
24pub fn sorted_input_ids(inputs: &InputMap) -> Vec<&String> {
25 let mut keys: Vec<_> = inputs.keys().collect();
26 keys.sort();
27 keys
28}
29
30pub fn sorted_input_ids_owned(inputs: &InputMap) -> Vec<String> {
32 let mut keys: Vec<String> = inputs.keys().cloned().collect();
33 keys.sort();
34 keys
35}
36
37#[derive(Default, Debug)]
38pub enum OutputChange {
39 #[default]
40 None,
41 Add(String),
42 Remove(String),
43}
44
45impl FlakeEdit {
46 pub fn new(changes: Vec<Change>, walker: Walker) -> Self {
47 Self { changes, walker }
48 }
49
50 pub fn from_text(stream: &str) -> Result<Self, FlakeEditError> {
51 let validation = validate::validate(stream);
52 if validation.has_errors() {
53 return Err(FlakeEditError::Validation(validation.errors));
54 }
55
56 let walker = Walker::new(stream);
57 Ok(Self::new(Vec::new(), walker))
58 }
59
60 pub fn changes(&self) -> &[Change] {
61 self.changes.as_ref()
62 }
63
64 pub fn add_change(&mut self, change: Change) {
65 self.changes.push(change);
66 }
67
68 pub fn curr_list(&self) -> &InputMap {
69 &self.walker.inputs
70 }
71
72 pub fn list(&mut self) -> &InputMap {
75 self.walker.inputs.clear();
76 assert!(self.walker.walk(&Change::None).ok().flatten().is_none());
78 &self.walker.inputs
79 }
80 pub fn apply_change(&mut self, change: Change) -> Result<Option<String>, FlakeEditError> {
83 match change {
84 Change::None => Ok(None),
85 Change::Add { .. } => {
86 if let Some(input_id) = change.id() {
88 self.ensure_inputs_populated()?;
89
90 let input_id_string = input_id.to_string();
91 if self.walker.inputs.contains_key(&input_id_string) {
92 return Err(FlakeEditError::DuplicateInput(input_id_string));
93 }
94 }
95
96 if let Some(maybe_changed_node) = self.walker.walk(&change.clone())? {
97 let outputs = self.walker.list_outputs()?;
98 match outputs {
99 Outputs::Multiple(out) => {
100 let id = change.id().unwrap().to_string();
101 if !out.contains(&id) {
102 self.walker.root = maybe_changed_node.clone();
103 if let Some(maybe_changed_node) =
104 self.walker.change_outputs(OutputChange::Add(id))?
105 {
106 return Ok(Some(maybe_changed_node.to_string()));
107 }
108 }
109 }
110 Outputs::None | Outputs::Any(_) => {}
111 }
112 Ok(Some(maybe_changed_node.to_string()))
113 } else {
114 self.walker.add_toplevel = true;
115 let maybe_changed_node = self.walker.walk(&change)?;
116 Ok(maybe_changed_node.map(|n| n.to_string()))
117 }
118 }
119 Change::Remove { .. } => {
120 self.ensure_inputs_populated()?;
121
122 let removed_id = change.id().unwrap().to_string();
123
124 let mut res = None;
127 while let Some(changed_node) = self.walker.walk(&change)? {
128 if res == Some(changed_node.clone()) {
129 break;
130 }
131 res = Some(changed_node.clone());
132 self.walker.root = changed_node.clone();
133 }
134 let outputs = self.walker.list_outputs()?;
136 match outputs {
137 Outputs::Multiple(out) | Outputs::Any(out) => {
138 if out.contains(&removed_id)
139 && let Some(changed_node) = self
140 .walker
141 .change_outputs(OutputChange::Remove(removed_id.clone()))?
142 {
143 res = Some(changed_node.clone());
144 self.walker.root = changed_node.clone();
145 }
146 }
147 Outputs::None => {}
148 }
149
150 let orphaned_follows = self.collect_orphaned_follows(&removed_id);
152 for orphan_change in orphaned_follows {
153 while let Some(changed_node) = self.walker.walk(&orphan_change)? {
154 if res == Some(changed_node.clone()) {
155 break;
156 }
157 res = Some(changed_node.clone());
158 self.walker.root = changed_node.clone();
159 }
160 }
161
162 Ok(res.map(|n| n.to_string()))
163 }
164 Change::Follows { .. } => {
165 self.ensure_inputs_populated()?;
166
167 if let Some(input_id) = change.id() {
169 let parent_input = input_id.input();
170 if !self.walker.inputs.contains_key(parent_input) {
171 return Err(FlakeEditError::InputNotFound(parent_input.to_string()));
172 }
173 }
174
175 if let Some(maybe_changed_node) = self.walker.walk(&change)? {
176 Ok(Some(maybe_changed_node.to_string()))
177 } else {
178 Ok(None)
179 }
180 }
181 Change::Change { .. } => {
182 if let Some(input_id) = change.id() {
183 self.ensure_inputs_populated()?;
184
185 let input_id_string = input_id.to_string();
186 if !self.walker.inputs.contains_key(&input_id_string) {
187 return Err(FlakeEditError::InputNotFound(input_id_string));
188 }
189 }
190
191 if let Some(maybe_changed_node) = self.walker.walk(&change)? {
192 Ok(Some(maybe_changed_node.to_string()))
193 } else {
194 Ok(None)
195 }
196 }
197 }
198 }
199
200 pub fn walker(&self) -> &Walker {
201 &self.walker
202 }
203
204 fn ensure_inputs_populated(&mut self) -> Result<(), FlakeEditError> {
206 if self.walker.inputs.is_empty() {
207 let _ = self.walker.walk(&Change::None)?;
208 }
209 Ok(())
210 }
211
212 fn collect_orphaned_follows(&self, removed_id: &str) -> Vec<Change> {
215 let mut orphaned = Vec::new();
216 for (input_id, input) in &self.walker.inputs {
217 for follows in input.follows() {
218 if let Follows::Indirect(follows_name, target) = follows {
219 if target.trim_matches('"') == removed_id {
221 let nested_id = format!("{}.{}", input_id, follows_name);
222 orphaned.push(Change::Remove {
223 ids: vec![nested_id.into()],
224 });
225 }
226 }
227 }
228 }
229 orphaned
230 }
231}