1use crate::fs;
2use core::fmt::{self, Display};
3use core::hash::Hash;
4use std::fs::read_link;
5use std::path::Path;
6
7#[derive(Debug, Eq, Clone)]
8pub enum DeployStatus {
9 UnDeployed,
10 Deployed,
11 Conflict { cause: String },
12 UnManaged,
13}
14impl PartialEq for DeployStatus {
15 fn eq(&self, other: &Self) -> bool {
16 matches!(
17 (self, other),
18 (DeployStatus::UnDeployed, DeployStatus::UnDeployed)
19 | (DeployStatus::Deployed, DeployStatus::Deployed)
20 | (DeployStatus::UnManaged, DeployStatus::UnManaged)
21 | (DeployStatus::Conflict { .. }, DeployStatus::Conflict { .. })
22 )
23 }
24}
25
26impl Hash for DeployStatus {
27 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
28 match self {
29 DeployStatus::UnDeployed => 0.hash(state),
30 DeployStatus::Deployed => 1.hash(state),
31 DeployStatus::UnManaged => 2.hash(state),
32 DeployStatus::Conflict { .. } => 3.hash(state),
33 }
34 }
35}
36
37impl Display for DeployStatus {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 DeployStatus::UnDeployed => write!(f, "UnDeployed"),
41 DeployStatus::Deployed => write!(f, "Deployed"),
42 DeployStatus::UnManaged => write!(f, "UnManaged"),
43 DeployStatus::Conflict { .. } => write!(f, "Conflict"),
44 }
45 }
46}
47
48pub fn get_status<P, Q>(from: P, to: Q) -> DeployStatus
49where
50 P: AsRef<Path>,
51 Q: AsRef<Path>,
52{
53 let Some(from) = from.as_ref().exists().then_some(from) else {
54 return DeployStatus::UnManaged;
55 };
56
57 if !to.as_ref().is_symlink() {
58 if to.as_ref().exists() {
59 return DeployStatus::Conflict {
60 cause: format!("Other file exists. {}", to.as_ref().to_string_lossy()),
61 };
62 } else {
63 return DeployStatus::UnDeployed;
64 }
65 }
66
67 let abs_to_link = fs::absolutize(read_link(to).unwrap()).unwrap();
68 if fs::absolutize(from).unwrap() != abs_to_link {
69 return DeployStatus::Conflict {
70 cause: format!(
71 "Symlink to different path. {}",
72 abs_to_link.to_string_lossy()
73 ),
74 };
75 }
76
77 DeployStatus::Deployed
78}
79
80#[cfg(test)]
81mod tests {
82 use pretty_assertions::{assert_eq, assert_ne};
83 use rstest::rstest;
84 use std::collections::hash_map::DefaultHasher;
85 use std::hash::{Hash, Hasher};
86
87 use super::*;
88
89 #[rstest]
90 #[case(DeployStatus::UnDeployed, DeployStatus::UnDeployed, true)]
91 #[case(DeployStatus::Deployed, DeployStatus::Deployed, true)]
92 #[case(DeployStatus::UnManaged, DeployStatus::UnManaged, true)]
93 #[case(
94 DeployStatus::Conflict {
95 cause: "cause".to_string()
96 },
97 DeployStatus::Conflict {
98 cause: "cause".to_string()
99 },
100 true
101 )]
102 #[case(
103 DeployStatus::Conflict {
104 cause: "cause1".to_string()
105 },
106 DeployStatus::Conflict {
107 cause: "cause2".to_string()
108 },
109 true
110 )]
111 #[case(DeployStatus::UnDeployed, DeployStatus::Deployed, false)]
112 #[case(DeployStatus::UnDeployed, DeployStatus::UnManaged, false)]
113 #[case(DeployStatus::UnDeployed, DeployStatus::Conflict { cause: "cause".to_string() }, false)]
114 #[case(DeployStatus::Deployed, DeployStatus::UnDeployed, false)]
115 #[case(DeployStatus::Deployed, DeployStatus::UnManaged, false)]
116 #[case(DeployStatus::Deployed, DeployStatus::Conflict { cause: "cause".to_string() }, false)]
117 #[case(DeployStatus::UnManaged, DeployStatus::UnDeployed, false)]
118 #[case(DeployStatus::UnManaged, DeployStatus::Deployed, false)]
119 #[case(DeployStatus::UnManaged, DeployStatus::Conflict { cause: "cause".to_string() }, false)]
120 #[case(
121 DeployStatus::Conflict {
122 cause: "cause".to_string()
123 },
124 DeployStatus::UnDeployed,
125 false
126 )]
127 #[case(
128 DeployStatus::Conflict {
129 cause: "cause".to_string()
130 },
131 DeployStatus::Deployed,
132 false
133 )]
134 #[case(
135 DeployStatus::Conflict {
136 cause: "cause".to_string()
137 },
138 DeployStatus::UnManaged,
139 false
140 )]
141 fn test_deploy_status_eq(
142 #[case] a: DeployStatus,
143 #[case] b: DeployStatus,
144 #[case] expected: bool,
145 ) {
146 if expected {
147 assert_eq!(a, b);
148 } else {
149 assert_ne!(a, b);
150 }
151 }
152
153 #[rstest]
154 #[case(DeployStatus::UnDeployed, DeployStatus::UnDeployed, true)]
155 #[case(DeployStatus::Deployed, DeployStatus::Deployed, true)]
156 #[case(DeployStatus::UnManaged, DeployStatus::UnManaged, true)]
157 #[case(
158 DeployStatus::Conflict {
159 cause: "cause".to_string()
160 },
161 DeployStatus::Conflict {
162 cause: "cause".to_string()
163 },
164 true
165 )]
166 #[case(
167 DeployStatus::Conflict {
168 cause: "cause1".to_string()
169 },
170 DeployStatus::Conflict {
171 cause: "cause2".to_string()
172 },
173 true
174 )]
175 #[case(DeployStatus::UnDeployed, DeployStatus::Deployed, false)]
176 #[case(DeployStatus::UnDeployed, DeployStatus::UnManaged, false)]
177 #[case(DeployStatus::UnDeployed, DeployStatus::Conflict { cause: "cause".to_string() }, false)]
178 #[case(DeployStatus::Deployed, DeployStatus::UnDeployed, false)]
179 #[case(DeployStatus::Deployed, DeployStatus::UnManaged, false)]
180 #[case(DeployStatus::Deployed, DeployStatus::Conflict { cause: "cause".to_string() }, false)]
181 #[case(DeployStatus::UnManaged, DeployStatus::UnDeployed, false)]
182 #[case(DeployStatus::UnManaged, DeployStatus::Deployed, false)]
183 #[case(DeployStatus::UnManaged, DeployStatus::Conflict { cause: "cause".to_string() }, false)]
184 #[case(
185 DeployStatus::Conflict {
186 cause: "cause".to_string()
187 },
188 DeployStatus::UnDeployed,
189 false
190 )]
191 #[case(
192 DeployStatus::Conflict {
193 cause: "cause".to_string()
194 },
195 DeployStatus::Deployed,
196 false
197 )]
198 #[case(
199 DeployStatus::Conflict {
200 cause: "cause".to_string()
201 },
202 DeployStatus::UnManaged,
203 false
204 )]
205 fn test_deploy_status_hash(
206 #[case] a: DeployStatus,
207 #[case] b: DeployStatus,
208 #[case] expected: bool,
209 ) {
210 if expected {
211 assert_eq!(hash(&a), hash(&b));
212 } else {
213 assert_ne!(hash(&a), hash(&b));
214 }
215 }
216
217 fn hash<T: Hash>(t: &T) -> u64 {
218 let mut hasher = DefaultHasher::new();
219 t.hash(&mut hasher);
220 hasher.finish()
221 }
222
223 #[rstest]
224 #[case(DeployStatus::UnDeployed, "UnDeployed")]
225 #[case(DeployStatus::Deployed, "Deployed")]
226 #[case(DeployStatus::UnManaged, "UnManaged")]
227 #[case(
228 DeployStatus::Conflict {
229 cause: "cause".to_string()
230 },
231 "Conflict"
232 )]
233 fn test_deploy_status_display(#[case] status: DeployStatus, #[case] expected: &str) {
234 assert_eq!(status.to_string(), expected);
235 }
236}