flake_edit/app/commands/
pin.rs1use nix_uri::FlakeRef;
9
10use crate::edit::{FlakeEdit, sorted_input_ids};
11use crate::follows::AttrPath;
12
13use super::super::editor::Editor;
14use super::super::state::AppState;
15use super::{Error, Result, interactive_single_select, load_flake_lock, updater};
16
17fn lock_path_display(state: &AppState) -> std::path::PathBuf {
18 state
19 .lock_file
20 .clone()
21 .unwrap_or_else(|| std::path::PathBuf::from("flake.lock"))
22}
23
24pub fn pin(
25 editor: &Editor,
26 flake_edit: &mut FlakeEdit,
27 state: &AppState,
28 id: Option<String>,
29 rev: Option<String>,
30) -> Result<()> {
31 let inputs = flake_edit.list().clone();
32 let input_ids = sorted_input_ids(&inputs)
33 .into_iter()
34 .cloned()
35 .collect::<Vec<_>>();
36
37 if let Some(id) = id {
38 let lock = load_flake_lock(state).map_err(|source| Error::LockFile {
39 path: lock_path_display(state),
40 source,
41 })?;
42 let target_rev = if let Some(rev) = rev {
43 rev
44 } else {
45 let path = AttrPath::parse(&id).map_err(|source| Error::InvalidInputId {
46 id: id.clone(),
47 source,
48 })?;
49 lock.rev_for(&path)?
50 };
51 let mut updater = updater(editor, inputs);
52 updater
53 .pin_input_to_ref(&id, &target_rev)
54 .map_err(|id| Error::InputNotPinnable { id })?;
55 let change = updater.get_changes();
56 editor.apply_or_diff(&change, state)?;
57 if !state.diff {
58 println!("Pinned input: {} to {}", id, target_rev);
59 }
60 } else if state.interactive {
61 if input_ids.is_empty() {
62 return Err(Error::NoInputs);
63 }
64 let lock = load_flake_lock(state).map_err(|source| Error::LockFile {
65 path: lock_path_display(state),
66 source,
67 })?;
68
69 interactive_single_select(
70 editor,
71 state,
72 "Pin",
73 "Select input",
74 input_ids,
75 |id| {
76 let path = AttrPath::parse(id).map_err(|source| Error::InvalidInputId {
77 id: id.to_string(),
78 source,
79 })?;
80 let target_rev = lock.rev_for(&path)?;
81 let mut updater = updater(editor, inputs.clone());
82 updater
83 .pin_input_to_ref(id, &target_rev)
84 .map_err(|id| Error::InputNotPinnable { id })?;
85 Ok((updater.get_changes(), target_rev))
86 },
87 |id, target_rev| println!("Pinned input: {} to {}", id, target_rev),
88 )?;
89 } else {
90 return Err(Error::NoId);
91 }
92
93 Ok(())
94}
95
96pub fn unpin(
97 editor: &Editor,
98 flake_edit: &mut FlakeEdit,
99 state: &AppState,
100 id: Option<String>,
101) -> Result<()> {
102 let inputs = flake_edit.list().clone();
103 let input_ids = sorted_input_ids(&inputs)
104 .into_iter()
105 .cloned()
106 .collect::<Vec<_>>();
107
108 if let Some(id) = id {
109 let mut updater = updater(editor, inputs);
110 updater
111 .unpin_input(&id)
112 .map_err(|id| Error::InputNotPinnable { id })?;
113 let change = updater.get_changes();
114 editor.apply_or_diff(&change, state)?;
115 if !state.diff {
116 println!("Unpinned input: {}", id);
117 }
118 } else if state.interactive {
119 let pinned_ids: Vec<String> = input_ids
120 .into_iter()
121 .filter(|id| {
122 inputs[id]
123 .url()
124 .parse::<FlakeRef>()
125 .is_ok_and(|f| f.ref_kind() != nix_uri::RefKind::None)
126 })
127 .collect();
128
129 if pinned_ids.is_empty() {
130 return Err(Error::NoInputs);
131 }
132
133 interactive_single_select(
134 editor,
135 state,
136 "Unpin",
137 "Select pinned input",
138 pinned_ids,
139 |id| {
140 let mut updater = updater(editor, inputs.clone());
141 updater
142 .unpin_input(id)
143 .map_err(|id| Error::InputNotPinnable { id })?;
144 Ok((updater.get_changes(), ()))
145 },
146 |id, ()| println!("Unpinned input: {}", id),
147 )?;
148 } else {
149 return Err(Error::NoId);
150 }
151
152 Ok(())
153}