rrcm/
deploy_status.rs

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}