1use std::fs::File;
16use std::iter;
17use std::path::{Component, Path, PathBuf};
18
19use tempfile::{NamedTempFile, PersistError};
20
21pub fn relative_path(from: &Path, to: &Path) -> PathBuf {
26 for (i, base) in from.ancestors().enumerate() {
28 if let Ok(suffix) = to.strip_prefix(base) {
29 if i == 0 && suffix.as_os_str().is_empty() {
30 return ".".into();
31 } else {
32 let mut result = PathBuf::from_iter(iter::repeat("..").take(i));
33 result.push(suffix);
34 return result;
35 }
36 }
37 }
38
39 to.to_owned()
41}
42
43pub fn normalize_path(path: &Path) -> PathBuf {
45 let mut result = PathBuf::new();
46 for c in path.components() {
47 match c {
48 Component::CurDir => {}
49 Component::ParentDir
50 if matches!(result.components().next_back(), Some(Component::Normal(_))) =>
51 {
52 let popped = result.pop();
54 assert!(popped);
55 }
56 _ => {
57 result.push(c);
58 }
59 }
60 }
61
62 if result.as_os_str().is_empty() {
63 ".".into()
64 } else {
65 result
66 }
67}
68
69pub fn persist_content_addressed_temp_file<P: AsRef<Path>>(
72 temp_file: NamedTempFile,
73 new_path: P,
74) -> Result<File, PersistError> {
75 match temp_file.persist(&new_path) {
76 Ok(file) => Ok(file),
77 Err(PersistError { error, file }) => {
78 if let Ok(existing_file) = File::open(new_path) {
79 Ok(existing_file)
80 } else {
81 Err(PersistError { error, file })
82 }
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use std::io::Write;
90
91 use test_case::test_case;
92
93 use super::*;
94
95 #[test]
96 fn normalize_too_many_dot_dot() {
97 assert_eq!(normalize_path(Path::new("foo/..")), Path::new("."));
98 assert_eq!(normalize_path(Path::new("foo/../..")), Path::new(".."));
99 assert_eq!(
100 normalize_path(Path::new("foo/../../..")),
101 Path::new("../..")
102 );
103 assert_eq!(
104 normalize_path(Path::new("foo/../../../bar/baz/..")),
105 Path::new("../../bar")
106 );
107 }
108
109 #[test]
110 fn test_persist_no_existing_file() {
111 let temp_dir = testutils::new_temp_dir();
112 let target = temp_dir.path().join("file");
113 let mut temp_file = NamedTempFile::new_in(&temp_dir).unwrap();
114 temp_file.write_all(b"contents").unwrap();
115 assert!(persist_content_addressed_temp_file(temp_file, target).is_ok());
116 }
117
118 #[test_case(false ; "existing file open")]
119 #[test_case(true ; "existing file closed")]
120 fn test_persist_target_exists(existing_file_closed: bool) {
121 let temp_dir = testutils::new_temp_dir();
122 let target = temp_dir.path().join("file");
123 let mut temp_file = NamedTempFile::new_in(&temp_dir).unwrap();
124 temp_file.write_all(b"contents").unwrap();
125
126 let mut file = File::create(&target).unwrap();
127 file.write_all(b"contents").unwrap();
128 if existing_file_closed {
129 drop(file);
130 }
131
132 assert!(persist_content_addressed_temp_file(temp_file, &target).is_ok());
133 }
134}