1use std::collections::HashMap;
9
10use nix_uri::FlakeRef;
11use nix_uri::urls::UrlWrapper;
12
13use crate::change::Change;
14use crate::diff::Diff;
15use crate::edit::FlakeEdit;
16use crate::lock::NestedInput;
17
18#[derive(Debug, Clone)]
20pub struct SingleSelectResult {
21 pub item: String,
22 pub show_diff: bool,
23}
24
25#[derive(Debug, Clone)]
27pub struct MultiSelectResultData {
28 pub items: Vec<String>,
29 pub show_diff: bool,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ConfirmResultAction {
35 Apply,
36 Back,
37 Exit,
38}
39
40#[derive(Debug, Clone, PartialEq)]
42pub enum AddPhase {
43 Uri,
44 Id,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub enum FollowPhase {
50 SelectInput,
52 SelectTarget,
54}
55
56#[derive(Debug, Clone)]
58pub enum UpdateResult {
59 Continue,
61 Done,
63 Cancelled,
65}
66
67#[derive(Debug, Clone)]
69pub enum AppResult {
70 Change(Change),
72 SingleSelect(SingleSelectResult),
74 MultiSelect(MultiSelectResultData),
76 Confirm(ConfirmResultAction),
78}
79
80#[derive(Debug, Clone)]
82pub enum WorkflowData {
83 Add {
84 phase: AddPhase,
85 uri: Option<String>,
86 id: Option<String>,
87 },
88 Change {
89 selected_input: Option<String>,
90 uri: Option<String>,
91 input_uris: HashMap<String, String>,
92 all_inputs: Vec<String>,
93 },
94 Remove {
95 selected_inputs: Vec<String>,
96 all_inputs: Vec<String>,
97 },
98 SelectOne {
99 selected_input: Option<String>,
100 },
101 SelectMany {
102 selected_inputs: Vec<String>,
103 },
104 ConfirmOnly {
105 action: Option<ConfirmResultAction>,
106 },
107 Follow {
108 phase: FollowPhase,
109 selected_input: Option<String>,
111 selected_target: Option<String>,
113 nested_inputs: Vec<NestedInput>,
115 top_level_inputs: Vec<String>,
117 },
118}
119
120impl WorkflowData {
121 pub fn build_change(&self) -> Change {
123 match self {
124 WorkflowData::Add { id, uri, .. } => Change::Add {
125 id: id.clone(),
126 uri: uri.clone(),
127 flake: true,
128 },
129 WorkflowData::Change {
130 selected_input,
131 uri,
132 ..
133 } => Change::Change {
134 id: selected_input.clone(),
135 uri: uri.clone(),
136 ref_or_rev: None,
137 },
138 WorkflowData::Remove {
139 selected_inputs, ..
140 } => {
141 if selected_inputs.is_empty() {
142 Change::None
143 } else {
144 Change::Remove {
145 ids: selected_inputs.iter().map(|s| s.clone().into()).collect(),
146 }
147 }
148 }
149 WorkflowData::SelectOne { .. }
151 | WorkflowData::SelectMany { .. }
152 | WorkflowData::ConfirmOnly { .. } => Change::None,
153 WorkflowData::Follow {
154 selected_input,
155 selected_target,
156 ..
157 } => {
158 if let (Some(input), Some(target)) = (selected_input, selected_target) {
159 Change::Follows {
160 input: input.clone().into(),
161 target: target.clone(),
162 }
163 } else {
164 Change::None
165 }
166 }
167 }
168 }
169}
170
171pub fn parse_uri_and_infer_id(uri: &str) -> (Option<String>, String) {
176 let flake_ref: Result<FlakeRef, _> = UrlWrapper::convert_or_parse(uri);
177 if let Ok(flake_ref) = flake_ref {
178 let parsed_uri = flake_ref.to_string();
179 let final_uri = if parsed_uri.is_empty() || parsed_uri == "none" {
180 uri.to_string()
181 } else {
182 parsed_uri
183 };
184 (flake_ref.id(), final_uri)
185 } else {
186 (None, uri.to_string())
187 }
188}
189
190pub fn compute_diff(flake_text: &str, change: &Change) -> String {
192 if matches!(change, Change::None) {
194 return String::new();
195 }
196
197 let Ok(mut edit) = FlakeEdit::from_text(flake_text) else {
198 return "Error parsing flake".to_string();
199 };
200
201 let new_text = match edit.apply_change(change.clone()) {
202 Ok(Some(text)) => text,
203 Ok(None) => flake_text.to_string(),
204 Err(e) => return format!("Error: {e}"),
205 };
206
207 Diff::new(flake_text, &new_text).to_string_plain()
208}