file_io/copy.rs
1use crate::create::create_folder_for_file;
2use std::path::Path;
3use walkdir::WalkDir;
4
5/// Copies a file from one location to another.
6///
7/// # Arguments
8///
9/// * `from` - The source file path (can be a `&str`, [`String`], [`Path`], or
10/// [`std::path::PathBuf`]).
11/// * `to` - The destination file path (can be a `&str`, [`String`], [`Path`], or
12/// [`std::path::PathBuf`]).
13///
14/// # Panics
15///
16/// If the source file does not exist or cannot be accessed, or if the destination cannot be
17/// created.
18///
19/// # Note
20///
21/// * The parent folder for the destination file will be created if it does not already exist.
22/// * If the destination file already exists, it will be overwritten.
23///
24/// # Examples
25///
26/// ## Using string literals
27///
28/// ```
29/// use file_io::copy_file;
30///
31/// // Copy 'Cargo.toml' to 'folder/Cargo_new_1.toml'.
32/// let from: &str = "Cargo.toml";
33/// let to: &str = "folder/Cargo_new_1.toml";
34/// copy_file(from, to);
35/// ```
36///
37/// ## Using `Path` references
38///
39/// ```
40/// use file_io::copy_file;
41/// use std::path::Path;
42///
43/// // Copy 'Cargo.toml' to 'folder/Cargo_new_2.toml'.
44/// let from: &Path = Path::new("Cargo.toml");
45/// let to: &Path = Path::new("folder/Cargo_new_2.toml");
46/// copy_file(from, to);
47/// ```
48pub fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) {
49 let from = from.as_ref();
50 let to = to.as_ref();
51 create_folder_for_file(to);
52 std::fs::copy(from, to)
53 .unwrap_or_else(|_| panic!("Failed to copy file from '{from:?}' to '{to:?}'."));
54}
55
56/// Copies a folder and its contents from one location to another.
57///
58/// # Arguments
59///
60/// * `from` - The source folder path (can be a `&str`, [`String`], [`Path`], or
61/// [`std::path::PathBuf`]).
62/// * `to` - The destination folder path (can be a `&str`, [`String`], [`Path`], or
63/// [`std::path::PathBuf`]).
64///
65/// # Panics
66///
67/// If any error occurs while copying the folder or its contents.
68///
69/// # Note
70///
71/// * The desination folder and/or any of its subdirectories will be created if they do not already
72/// exist.
73/// * Any existing files in the destination folder will be overwritten.
74///
75/// # Examples
76///
77/// ## Using string literals
78///
79/// ```
80/// use file_io::copy_folder;
81///
82/// // Copy 'src/' to 'folder/src/'.
83/// let from: &str = "src";
84/// let to: &str = "folder/src";
85/// copy_folder(from, to);
86/// ```
87///
88/// ## Using `Path` references
89///
90/// ```
91/// use file_io::copy_folder;
92/// use std::path::Path;
93///
94/// // Copy 'src/' to 'folder/src/'.
95/// let from: &Path = Path::new("src");
96/// let to: &Path = Path::new("folder/src");
97/// copy_folder(from, to);
98/// ```
99pub fn copy_folder<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) {
100 // Convert the input paths to `Path` references.
101 let from = from.as_ref();
102 let to = to.as_ref();
103
104 // Traverse over all entries (files and folders) in the directory and its subdirectories.
105 for entry in WalkDir::new(from).into_iter().filter_map(Result::ok) {
106 // Get the path of the current entry.
107 let entry_path = entry.path();
108
109 // Construct the destination path.
110 let destination_path = to.join(entry_path.strip_prefix(from).unwrap());
111
112 // Copy any files (note that `WalkDir` will also traverse subdirectories, and we don't need
113 // to manually create subdirectories since `copy_file` will handle that for us).
114 if entry_path.is_file() {
115 copy_file(entry_path, &destination_path);
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::delete::{delete_file, delete_folder};
124 use crate::load::load_file_as_string;
125 use crate::path::to_path_buf;
126 use crate::save::save_string_to_file;
127 use crate::test_utils::get_temp_dir_path;
128 use std::path::Path;
129 use tempfile::tempdir;
130
131 #[test]
132 fn test_copy_file() {
133 // Create a temporary directory to work in.
134 let temp_dir = tempdir().unwrap();
135
136 // Define the paths for the source file.
137 let source_path = get_temp_dir_path(&temp_dir).join("source.txt");
138 let source_paths: Vec<Box<dyn AsRef<Path>>> = vec![
139 Box::new(source_path.to_str().unwrap()), // &str
140 Box::new(source_path.to_str().unwrap().to_string()), // String
141 Box::new(source_path.as_path()), // Path
142 Box::new(source_path.clone()), // PathBuf
143 ];
144
145 // Define the paths for the destination file.
146 let destination_path = get_temp_dir_path(&temp_dir).join("destination.txt");
147 let destination_paths: Vec<Box<dyn AsRef<Path>>> = vec![
148 Box::new(destination_path.clone()), // PathBuf
149 Box::new(destination_path.as_path()), // Path
150 Box::new(destination_path.to_str().unwrap().to_string()), // String
151 Box::new(destination_path.to_str().unwrap()), // &str
152 ];
153
154 // Create the source file.
155 save_string_to_file("Hello, world!", &source_path);
156
157 // Test with all different path formats.
158 for (source_path, destination_path) in source_paths.iter().zip(destination_paths) {
159 // Get a reference to these path representations (i.e. "unbox").
160 let source_path: &dyn AsRef<Path> = source_path.as_ref();
161 let destination_path: &dyn AsRef<Path> = destination_path.as_ref();
162
163 // The destination file shouldn't exist yet.
164 assert!(!to_path_buf(destination_path).exists());
165
166 // Copy the file.
167 copy_file(source_path, destination_path);
168
169 // The destination file should now exist.
170 assert!(to_path_buf(destination_path).exists());
171
172 // Check that the contents of the copied file are identical.
173 assert_eq!(load_file_as_string(destination_path), "Hello, world!");
174
175 // Delete the destination file.
176 delete_file(destination_path);
177
178 // Verify that the destination file no longer exists.
179 assert!(!to_path_buf(destination_path).exists());
180 }
181 }
182
183 #[test]
184 fn test_copy_file_with_existing_destination() {
185 // Create a temporary directory to work in.
186 let temp_dir = tempdir().unwrap();
187
188 // Define the source file path.
189 let source_path = get_temp_dir_path(&temp_dir).join("source.txt");
190
191 // Create the source file.
192 save_string_to_file("Hello, world!", &source_path);
193
194 // Define the destination file path.
195 let destination_path = get_temp_dir_path(&temp_dir).join("destination.txt");
196
197 // Create the destination file with different content.
198 save_string_to_file("Old content", &destination_path);
199
200 // Copy the source file to the destination file.
201 copy_file(&source_path, &destination_path);
202
203 // Verify that the contents of the destination file have been overwritten.
204 assert_eq!(load_file_as_string(&destination_path), "Hello, world!");
205 }
206
207 #[test]
208 fn test_copy_folder_flat() {
209 // Create a temporary directory to work in.
210 let temp_dir = tempdir().unwrap();
211
212 // Define the paths for the source folder.
213 let source_path = get_temp_dir_path(&temp_dir).join("source_folder");
214 let source_paths: Vec<Box<dyn AsRef<Path>>> = vec![
215 Box::new(source_path.to_str().unwrap()), // &str
216 Box::new(source_path.to_str().unwrap().to_string()), // String
217 Box::new(source_path.as_path()), // Path
218 Box::new(source_path.clone()), // PathBuf
219 ];
220
221 // Define the paths for the destination folder.
222 let destination_path = get_temp_dir_path(&temp_dir).join("destination_folder");
223 let destination_paths: Vec<Box<dyn AsRef<Path>>> = vec![
224 Box::new(destination_path.clone()), // PathBuf
225 Box::new(destination_path.as_path()), // Path
226 Box::new(destination_path.to_str().unwrap().to_string()), // String
227 Box::new(destination_path.to_str().unwrap()), // &str
228 ];
229
230 // Define the source folder path.
231 let source_path = get_temp_dir_path(&temp_dir).join("source_folder");
232
233 // Create files in the source folder.
234 save_string_to_file("Hello, world!", source_path.join("file_1.txt"));
235 save_string_to_file("hello world", source_path.join("file_2.txt"));
236
237 // Test with all different path formats.
238 for (source_path, destination_path) in source_paths.iter().zip(destination_paths) {
239 // Get a reference to these path representations (i.e. "unbox").
240 let source_path: &dyn AsRef<Path> = source_path.as_ref();
241 let destination_path: &dyn AsRef<Path> = destination_path.as_ref();
242
243 // The destination folder shouldn't exist yet.
244 assert!(!to_path_buf(destination_path).exists());
245
246 // Copy the source folder to the destination folder.
247 copy_folder(source_path, destination_path);
248
249 // The destination folder should now exist.
250 assert!(to_path_buf(destination_path).exists());
251
252 // Verify that the files were copied correctly.
253 assert_eq!(
254 load_file_as_string(to_path_buf(destination_path).join("file_1.txt")),
255 "Hello, world!"
256 );
257 assert_eq!(
258 load_file_as_string(to_path_buf(destination_path).join("file_2.txt")),
259 "hello world"
260 );
261
262 // Delete the desination folder.
263 delete_folder(destination_path);
264
265 // Verify that the destination folder no longer exists.
266 assert!(!to_path_buf(destination_path).exists());
267 }
268 }
269
270 #[test]
271 fn test_copy_folder_nested() {
272 // Create a temporary directory to work in.
273 let temp_dir = tempdir().unwrap();
274
275 // Define the source folder path.
276 let source_folder = get_temp_dir_path(&temp_dir).join("source_folder");
277
278 // Create a file in the source folder.
279 save_string_to_file("Hello, world!", source_folder.join("file.txt"));
280
281 // Create a file in the subfolder.
282 save_string_to_file(
283 "Hello from subfolder!",
284 source_folder.join("subfolder/subfile.txt"),
285 );
286
287 // Define the destination folder path.
288 let destination_folder = get_temp_dir_path(&temp_dir).join("destination_folder");
289
290 // Copy the source folder to the destination folder.
291 copy_folder(&source_folder, &destination_folder);
292
293 // Verify that the files were copied correctly.
294 assert_eq!(
295 load_file_as_string(destination_folder.join("file.txt")),
296 "Hello, world!"
297 );
298 assert_eq!(
299 load_file_as_string(destination_folder.join("subfolder/subfile.txt")),
300 "Hello from subfolder!"
301 );
302 }
303
304 #[test]
305 fn test_copy_folder_with_existing_destination() {
306 // Create a temporary directory to work in.
307 let temp_dir = tempdir().unwrap();
308
309 // Define the source folder path.
310 let source_folder = get_temp_dir_path(&temp_dir).join("source_folder");
311
312 // Create files in the source folder.
313 save_string_to_file("Hello, world!", source_folder.join("file.txt"));
314 save_string_to_file(
315 "Overwrite existing file",
316 source_folder.join("existing_file.txt"),
317 );
318
319 // Create a file in a subfolder.
320 save_string_to_file(
321 "Hello from subfolder!",
322 source_folder.join("subfolder/subfile.txt"),
323 );
324
325 // Define the destination folder path.
326 let destination_folder = get_temp_dir_path(&temp_dir).join("destination_folder");
327
328 // Create the destination folder and a file in it.
329 save_string_to_file(
330 "Existing file",
331 destination_folder.join("existing_file.txt"),
332 );
333
334 // Copy the source folder to the destination folder.
335 copy_folder(&source_folder, &destination_folder);
336
337 // Verify that the files were copied correctly. Note that the existing file should be
338 // overwritten.
339 assert_eq!(
340 load_file_as_string(destination_folder.join("file.txt")),
341 "Hello, world!"
342 );
343 assert_eq!(
344 load_file_as_string(destination_folder.join("existing_file.txt")),
345 "Overwrite existing file"
346 );
347 assert_eq!(
348 load_file_as_string(destination_folder.join("subfolder/subfile.txt")),
349 "Hello from subfolder!"
350 );
351 }
352}