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}