1#![deny(missing_docs)]
27
28mod common;
29mod error;
30mod template;
31
32#[cfg(test)]
33mod testing;
34
35const DEFAULT_EXTENSION: &str = "bak";
36
37use crate::common::*;
38
39pub fn move_aside(path: impl AsRef<Path>) -> io::Result<PathBuf> {
41 move_aside_with_extension(path, DEFAULT_EXTENSION)
42}
43
44pub fn move_aside_with_extension(
46 path: impl AsRef<Path>,
47 extension: impl AsRef<OsStr>,
48) -> io::Result<PathBuf> {
49 let template = Template::new(path.as_ref())?;
50
51 let source = template.source();
52
53 let destination = template.destination(extension.as_ref())?;
54
55 fs::rename(source, &destination)?;
56
57 Ok(destination)
58}
59
60pub fn destination(path: impl AsRef<Path>) -> io::Result<PathBuf> {
63 destination_with_extension(path, DEFAULT_EXTENSION)
64}
65
66pub fn destination_with_extension(
69 path: impl AsRef<Path>,
70 extension: impl AsRef<OsStr>,
71) -> io::Result<PathBuf> {
72 Template::new(path.as_ref())?.destination(extension.as_ref())
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 use std::fs::File;
80
81 macro_rules! test {
82 {
83 name: $name:ident,
84 files: [$($file:expr),*],
85 source: $source:expr,
86 extension: $extension:expr,
87 destination: $destination:expr,
88 } => {
89 #[test]
90 fn $name() -> io::Result<()> {
91 let mut files = Vec::new();
92 $(
93 {
94 files.push(PathBuf::from($file));
95 }
96 )*;
97
98 let source = PathBuf::from($source);
99
100 let extension: Option<&OsStr> = $extension.map(|extension: &str| extension.as_ref());
101
102 let desired_destination = PathBuf::from($destination);
103
104 let tempdir = tempfile::tempdir()?;
105
106 let base = tempdir.path();
107
108 for file in &files {
109 File::create(base.join(file))?;
110 }
111
112 let planned_destination = match extension {
113 Some(extension) => destination_with_extension(base.join(&source), extension)?,
114 None => destination(base.join(&source))?,
115 };
116
117 let planned_destination = planned_destination.strip_prefix(base.canonicalize()?).unwrap();
118
119 assert_eq!(planned_destination, desired_destination);
120
121 let actual_destination = match extension {
122 Some(extension) => move_aside_with_extension(base.join(&source), extension)?,
123 None => move_aside(base.join(&source))?,
124 };
125
126 let actual_destination = actual_destination.strip_prefix(base.canonicalize()?).unwrap();
127
128 assert_eq!(actual_destination, desired_destination);
129
130 let mut want = files.clone();
131 want.retain(|file| file != &source);
132 want.push(desired_destination);
133 want.sort();
134
135 let mut have = tempdir.path()
136 .read_dir()?
137 .map(|result| result.map(|entry| PathBuf::from(entry.file_name())))
138 .collect::<io::Result<Vec<PathBuf>>>()?;
139 have.sort();
140
141 assert_eq!(have, want, "{:?} != {:?}", have, want);
142
143 Ok(())
144 }
145 }
146 }
147
148 test! {
149 name: no_conflicts,
150 files: ["foo"],
151 source: "foo",
152 extension: None,
153 destination: "foo.bak",
154 }
155
156 test! {
157 name: one_conflict,
158 files: ["foo", "foo.bak"],
159 source: "foo",
160 extension: None,
161 destination: "foo.bak.0",
162 }
163
164 test! {
165 name: two_conflicts,
166 files: ["foo", "foo.bak", "foo.bak.0"],
167 source: "foo",
168 extension: None,
169 destination: "foo.bak.1",
170 }
171
172 test! {
173 name: three_conflicts,
174 files: ["foo", "foo.bak", "foo.bak.0", "foo.bak.1"],
175 source: "foo",
176 extension: None,
177 destination: "foo.bak.2",
178 }
179
180 test! {
181 name: no_conflicts_ext,
182 files: ["foo"],
183 source: "foo",
184 extension: Some("bar"),
185 destination: "foo.bar",
186 }
187
188 test! {
189 name: one_conflict_ext,
190 files: ["foo", "foo.bar"],
191 source: "foo",
192 extension: Some("bar"),
193 destination: "foo.bar.0",
194 }
195
196 test! {
197 name: two_conflicts_ext,
198 files: ["foo", "foo.bar", "foo.bar.0"],
199 source: "foo",
200 extension: Some("bar"),
201 destination: "foo.bar.1",
202 }
203
204 test! {
205 name: three_conflicts_ext,
206 files: ["foo", "foo.bar", "foo.bar.0", "foo.bar.1"],
207 source: "foo",
208 extension: Some("bar"),
209 destination: "foo.bar.2",
210 }
211}