jt_consoleutils/
fs_utils.rs1use std::path::Path;
2
3pub fn same_file(a: &Path, b: &Path) -> bool {
6 match (std::fs::canonicalize(a), std::fs::canonicalize(b)) {
7 (Ok(ca), Ok(cb)) => ca == cb,
8 _ => false
9 }
10}
11
12pub fn same_content(a: &Path, b: &Path) -> bool {
16 let (Ok(meta_a), Ok(meta_b)) = (std::fs::metadata(a), std::fs::metadata(b)) else {
17 return false;
18 };
19 if meta_a.len() != meta_b.len() {
20 return false;
21 }
22 let (Ok(bytes_a), Ok(bytes_b)) = (std::fs::read(a), std::fs::read(b)) else {
23 return false;
24 };
25 bytes_a == bytes_b
26}
27
28pub fn make_executable(path: &Path) -> std::io::Result<()> {
30 #[cfg(unix)]
31 {
32 use std::os::unix::fs::PermissionsExt;
33 let mut perms = std::fs::metadata(path)?.permissions();
34 perms.set_mode(perms.mode() | 0o111);
35 std::fs::set_permissions(path, perms)?;
36 }
37 #[cfg(not(unix))]
38 {
39 let _ = path;
40 }
41 Ok(())
42}
43
44pub fn remove_symlink_dir_like(path: &Path) -> Result<bool, std::io::Error> {
48 if !path.is_symlink() {
49 return Ok(false);
50 }
51
52 #[cfg(windows)]
53 std::fs::remove_dir(path)?;
54
55 #[cfg(unix)]
56 std::fs::remove_file(path)?;
57
58 Ok(true)
59}
60
61#[cfg(test)]
62mod tests {
63 use std::fs;
64
65 use tempfile::TempDir;
66
67 use super::*;
68
69 #[test]
74 fn same_file_returns_true_for_identical_paths() {
75 let dir = TempDir::new().unwrap();
77 let path = dir.path().join("file.txt");
78 fs::write(&path, b"hello").unwrap();
79
80 assert!(same_file(&path, &path));
82 }
83
84 #[test]
85 fn same_file_returns_false_for_different_files() {
86 let dir = TempDir::new().unwrap();
88 let a = dir.path().join("a.txt");
89 let b = dir.path().join("b.txt");
90 fs::write(&a, b"hello").unwrap();
91 fs::write(&b, b"hello").unwrap();
92
93 assert!(!same_file(&a, &b));
95 }
96
97 #[test]
98 fn same_file_returns_false_when_path_does_not_exist() {
99 let dir = TempDir::new().unwrap();
101 let missing = dir.path().join("missing.txt");
102 let existing = dir.path().join("existing.txt");
103 fs::write(&existing, b"hi").unwrap();
104
105 assert!(!same_file(&missing, &existing));
107 }
108
109 #[test]
114 fn same_content_returns_true_for_identical_bytes() {
115 let dir = TempDir::new().unwrap();
117 let a = dir.path().join("a.txt");
118 let b = dir.path().join("b.txt");
119 fs::write(&a, b"hello world").unwrap();
120 fs::write(&b, b"hello world").unwrap();
121
122 assert!(same_content(&a, &b));
124 }
125
126 #[test]
127 fn same_content_returns_false_for_different_bytes() {
128 let dir = TempDir::new().unwrap();
130 let a = dir.path().join("a.txt");
131 let b = dir.path().join("b.txt");
132 fs::write(&a, b"hello").unwrap();
133 fs::write(&b, b"world").unwrap();
134
135 assert!(!same_content(&a, &b));
137 }
138
139 #[test]
140 fn same_content_returns_false_for_different_sizes() {
141 let dir = TempDir::new().unwrap();
143 let a = dir.path().join("a.txt");
144 let b = dir.path().join("b.txt");
145 fs::write(&a, b"hi").unwrap();
146 fs::write(&b, b"hello").unwrap();
147
148 assert!(!same_content(&a, &b));
150 }
151
152 #[test]
153 fn same_content_returns_false_when_file_missing() {
154 let dir = TempDir::new().unwrap();
156 let a = dir.path().join("a.txt");
157 let missing = dir.path().join("missing.txt");
158 fs::write(&a, b"data").unwrap();
159
160 assert!(!same_content(&a, &missing));
162 }
163
164 #[test]
169 fn make_executable_does_not_error_on_existing_file() {
170 let dir = TempDir::new().unwrap();
172 let path = dir.path().join("script.sh");
173 fs::write(&path, b"#!/bin/sh\n").unwrap();
174
175 assert!(make_executable(&path).is_ok());
177 }
178
179 #[cfg(unix)]
180 #[test]
181 fn make_executable_sets_exec_bit_on_unix() {
182 use std::os::unix::fs::PermissionsExt;
184 let dir = TempDir::new().unwrap();
185 let path = dir.path().join("script.sh");
186 fs::write(&path, b"#!/bin/sh\n").unwrap();
187
188 let mut perms = fs::metadata(&path).unwrap().permissions();
190 perms.set_mode(0o600);
191 fs::set_permissions(&path, perms).unwrap();
192
193 make_executable(&path).unwrap();
195
196 let mode = fs::metadata(&path).unwrap().permissions().mode();
198 assert!(mode & 0o111 != 0, "exec bit should be set");
199 }
200
201 #[cfg(unix)]
206 #[test]
207 fn remove_symlink_dir_like_removes_symlink_on_unix() {
208 let dir = TempDir::new().unwrap();
210 let target = dir.path().join("target.txt");
211 let link = dir.path().join("link");
212 fs::write(&target, b"data").unwrap();
213 std::os::unix::fs::symlink(&target, &link).unwrap();
214 assert!(link.is_symlink());
215
216 let result = remove_symlink_dir_like(&link).unwrap();
218
219 assert!(result);
221 assert!(!link.exists() && !link.is_symlink());
222 }
223
224 #[test]
225 fn remove_symlink_dir_like_returns_false_for_non_symlink() {
226 let dir = TempDir::new().unwrap();
228 let path = dir.path().join("plain.txt");
229 fs::write(&path, b"data").unwrap();
230
231 let result = remove_symlink_dir_like(&path).unwrap();
233
234 assert!(!result);
236 }
237}