1use std::collections::HashMap;
9
10use nix_uri::FlakeRef;
11
12use crate::change::Change;
13use crate::diff::Diff;
14use crate::edit::FlakeEdit;
15use crate::lock::NestedInput;
16
17#[derive(Debug, Clone)]
19pub struct SingleSelectResult {
20 pub item: String,
21 pub show_diff: bool,
22}
23
24#[derive(Debug, Clone)]
26pub struct MultiSelectResultData {
27 pub items: Vec<String>,
28 pub show_diff: bool,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum ConfirmResultAction {
34 Apply,
35 Back,
36 Exit,
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub enum AddStep {
42 Uri,
43 Id,
44}
45
46#[derive(Debug, Clone, PartialEq)]
48pub enum FollowStep {
49 SelectInput,
51 SelectTarget,
53}
54
55#[derive(Debug, Clone)]
57pub enum UpdateResult {
58 Continue,
60 Done,
62 Cancelled,
64}
65
66#[derive(Debug, Clone)]
68pub enum AppResult {
69 Change(Change),
71 SingleSelect(SingleSelectResult),
73 MultiSelect(MultiSelectResultData),
75 Confirm(ConfirmResultAction),
77}
78
79#[derive(Debug, Clone)]
81pub enum WorkflowData {
82 Add {
83 step: AddStep,
84 uri: Option<String>,
85 id: Option<String>,
86 },
87 Change {
88 selected_input: Option<String>,
89 uri: Option<String>,
90 input_uris: HashMap<String, String>,
91 all_inputs: Vec<String>,
92 },
93 Remove {
94 selected_inputs: Vec<String>,
95 all_inputs: Vec<String>,
96 },
97 SelectOne {
98 selected_input: Option<String>,
99 },
100 SelectMany {
101 selected_inputs: Vec<String>,
102 },
103 ConfirmOnly {
104 action: Option<ConfirmResultAction>,
105 },
106 Follow {
107 step: FollowStep,
108 selected_input: Option<String>,
110 selected_target: Option<String>,
112 nested_inputs: Vec<NestedInput>,
114 top_level_inputs: Vec<String>,
116 },
117}
118
119impl WorkflowData {
120 pub fn build_change(&self) -> Change {
122 match self {
123 WorkflowData::Add { id, uri, .. } => Change::Add {
124 id: id
125 .as_deref()
126 .and_then(|s| crate::change::ChangeId::parse(s).ok()),
127 uri: uri.clone(),
128 flake: true,
129 },
130 WorkflowData::Change {
131 selected_input,
132 uri,
133 ..
134 } => Change::Change {
135 id: selected_input
136 .as_deref()
137 .and_then(|s| crate::change::ChangeId::parse(s).ok()),
138 uri: uri.clone(),
139 },
140 WorkflowData::Remove {
141 selected_inputs, ..
142 } => {
143 if selected_inputs.is_empty() {
144 Change::None
145 } else {
146 Change::Remove {
147 ids: selected_inputs
148 .iter()
149 .filter_map(|s| crate::change::ChangeId::parse(s).ok())
150 .collect(),
151 }
152 }
153 }
154 WorkflowData::SelectOne { .. }
156 | WorkflowData::SelectMany { .. }
157 | WorkflowData::ConfirmOnly { .. } => Change::None,
158 WorkflowData::Follow {
159 selected_input,
160 selected_target,
161 ..
162 } => {
163 if let (Some(input), Some(target)) = (selected_input, selected_target) {
164 let parsed = crate::change::ChangeId::parse(input)
165 .ok()
166 .zip(crate::follows::AttrPath::parse(target).ok());
167 if let Some((change_id, target_path)) = parsed {
168 Change::Follows {
169 input: change_id,
170 target: target_path,
171 }
172 } else {
173 Change::None
174 }
175 } else {
176 Change::None
177 }
178 }
179 }
180 }
181}
182
183pub fn parse_uri_and_infer_id(uri: &str) -> (Option<String>, String) {
188 match uri.parse::<FlakeRef>() {
189 Ok(flake_ref) => {
190 let id = flake_ref.id().map(str::to_owned);
191 (id, flake_ref.into_uri())
192 }
193 Err(_) => (None, uri.to_string()),
194 }
195}
196
197pub fn compute_diff(flake_text: &str, change: &Change) -> String {
199 if matches!(change, Change::None) {
201 return String::new();
202 }
203
204 let Ok(mut edit) = FlakeEdit::from_text(flake_text) else {
205 return "Error parsing flake".to_string();
206 };
207
208 let new_text = match edit.apply_change(change.clone()) {
209 Ok(outcome) => outcome.text.unwrap_or_else(|| flake_text.to_string()),
210 Err(e) => return format!("Error: {e}"),
211 };
212
213 Diff::new(flake_text, &new_text).to_string_plain()
214}