comtrya_lib/atoms/file/
link.rs1use crate::atoms::Outcome;
2
3use super::super::Atom;
4use super::FileAtom;
5use std::path::PathBuf;
6use tracing::{error, warn};
7
8pub struct Link {
9 pub source: PathBuf,
10 pub target: PathBuf,
11}
12
13impl FileAtom for Link {
14 fn get_path(&self) -> &PathBuf {
15 &self.source
16 }
17}
18
19impl std::fmt::Display for Link {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(
22 f,
23 "The file {} contents needs to be linked from {}",
24 self.target.display(),
25 self.source.display(),
26 )
27 }
28}
29
30impl Atom for Link {
31 fn plan(&self) -> anyhow::Result<Outcome> {
32 if !self.source.exists() {
34 error!(
35 "Cannot plan: source file is missing: {}",
36 self.source.display()
37 );
38
39 return Ok(Outcome {
40 side_effects: vec![],
41 should_run: false,
42 });
43 }
44
45 if !self.target.exists() {
47 return Ok(Outcome {
48 side_effects: vec![],
49 should_run: true,
50 });
51 }
52 let link = match std::fs::read_link(&self.target) {
56 Ok(link) => link,
57 Err(err) => {
58 warn!(
59 "Cannot plan: target already exists and isn't a link: {}",
60 self.target.display()
61 );
62 error!("Cannot plan: {}", err);
63
64 return Ok(Outcome {
65 side_effects: vec![],
66 should_run: false,
67 });
68 }
69 };
70
71 let source = if cfg!(target_os = "windows") {
72 const PREFIX: &str = r"\\?\";
73 PathBuf::from(&self.source.display().to_string().replace(PREFIX, ""))
74 } else {
75 self.source.to_owned()
76 };
77
78 Ok(Outcome {
80 side_effects: vec![],
81 should_run: !link.eq(&source),
82 })
83 }
84
85 #[cfg(unix)]
86 fn execute(&mut self) -> anyhow::Result<()> {
87 std::os::unix::fs::symlink(&self.source, &self.target)?;
88
89 Ok(())
90 }
91
92 #[cfg(windows)]
93 fn execute(&mut self) -> anyhow::Result<()> {
94 if self.target.is_dir() {
95 std::os::windows::fs::symlink_dir(&self.source, &self.target)?;
96 } else {
97 std::os::windows::fs::symlink_file(&self.source, &self.target)?;
98 }
99
100 Ok(())
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use pretty_assertions::assert_eq;
108
109 #[test]
110 fn it_can() {
111 let from_dir = match tempfile::tempdir() {
112 Ok(dir) => dir,
113 Err(_) => {
114 assert_eq!(false, true);
115 return;
116 }
117 };
118
119 let to_file = match tempfile::NamedTempFile::new() {
120 std::result::Result::Ok(file) => file,
121 std::result::Result::Err(_) => {
122 assert_eq!(false, true);
123 return;
124 }
125 };
126
127 let mut atom = Link {
128 target: from_dir.path().join("symlink"),
129 source: to_file.path().to_path_buf(),
130 };
131 assert_eq!(true, atom.plan().unwrap().should_run);
132 assert_eq!(true, atom.execute().is_ok());
133 assert_eq!(false, atom.plan().unwrap().should_run);
134 }
135}