1use std::{error::Error, fs, path::Path};
2
3#[derive(Debug, PartialEq)]
4pub struct FilePath {
5 pub input_path: String,
6 pub output_path: String,
7}
8
9#[derive(PartialEq)]
10enum PathType {
11 File,
12 Directory,
13}
14
15fn get_pathtype(input_path: &str) -> Result<PathType, Box<dyn Error>> {
16 let input_metadata = fs::metadata(input_path)?;
17
18 if input_metadata.is_file() {
19 return Ok(PathType::File);
20 } else if input_metadata.is_dir() || input_metadata.is_symlink() {
21 return Ok(PathType::Directory);
22 }
23
24 Err("input path is not an existing file or directory".into())
25}
26
27pub fn extend_dir_path<'a>(dir_path: &'a str, file_path: &'a str) -> String {
28 format!("{}/{}", dir_path, file_path)
29}
30
31pub fn get_filepaths(
32 input_path: String,
33 output_path: String,
34) -> Result<Vec<FilePath>, Box<dyn Error>> {
35 let mut filepaths = Vec::<FilePath>::new();
36 let pathtype = get_pathtype(&input_path)?;
37
38 if pathtype == PathType::File {
39 let path = FilePath {
40 input_path,
41 output_path,
42 };
43 filepaths.push(path);
44 return Ok(filepaths);
45 }
46
47 let dirpaths = fs::read_dir(&input_path)?;
48 for entry in dirpaths {
49 let filename = match entry?.file_name().into_string() {
50 Ok(filename) => filename,
51 Err(_) => return Err("could not parse file".into()),
52 };
53
54 let ext_input_path = extend_dir_path(&input_path, &filename);
55 let ext_output_path = extend_dir_path(&output_path, &filename);
56
57 let mut paths = get_filepaths(ext_input_path, ext_output_path)?;
58 filepaths.append(&mut paths);
59 }
60
61 Ok(filepaths)
62}
63
64pub fn read_file(filepath: &str) -> Result<Vec<u8>, Box<dyn Error>> {
65 let contents = fs::read(filepath)?;
66
67 Ok(contents)
68}
69
70pub fn enable_write_to_file(filepath: &str) -> Result<(), Box<dyn Error>> {
71 let mut permissions = fs::metadata(filepath)?.permissions();
72 permissions.set_readonly(false);
73
74 fs::set_permissions(filepath, permissions)?;
75 Ok(())
76}
77
78pub fn write_file(filepath: &str, contents: Vec<u8>) -> Result<(), Box<dyn Error>> {
79 let path = Path::new(filepath);
80
81 if let Some(parent) = path.parent() {
82 fs::create_dir_all(parent)?;
83 }
84
85 let _ = enable_write_to_file(filepath);
86
87 println!("Writing contents to {filepath}");
88 fs::write(path, contents)?;
89
90 Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_read_file_contents() {
99 let contents = read_file("./tests/sample/test_file.txt").unwrap();
100 let exp_contents = b"\
101my text
102second line\n"
103 .to_vec();
104
105 assert_eq!(contents, exp_contents);
106 }
107
108 #[test]
109 fn test_write_file_only() {
110 let contents = b"foo bar baz".to_vec();
111 let path = "./tests/sample/written_file.txt";
112 write_file(path, contents).unwrap();
113
114 fs::remove_file(path).unwrap();
115 }
116
117 #[test]
118 fn test_write_readonly_file() {
119 let original_contents = b"foo bar baz".to_vec();
120 let path = "./tests/sample/written_file.txt";
121 write_file(path, original_contents).unwrap();
122
123 let updated_contents = b"baz bar foo".to_vec();
124 let mut permissions = fs::metadata(path).unwrap().permissions();
125 permissions.set_readonly(true);
126 fs::set_permissions(path, permissions).unwrap();
127 write_file(path, updated_contents).unwrap();
128
129 fs::remove_file(path).unwrap();
130 }
131
132 #[test]
133 fn test_extend_dir_path() {
134 let dir_path = "./tests/directory";
135 let file_path = extend_dir_path(&dir_path, "foo/bar.txt");
136
137 assert_eq!(file_path, "./tests/directory/foo/bar.txt");
138 }
139
140 #[test]
141 fn test_write_file_with_dirs() {
142 let contents = b"file contents".to_vec();
143 let dir_path = "./tests/new_dir";
144 let file_path = extend_dir_path(&dir_path, "new_file.txt");
145
146 write_file(&file_path, contents.clone()).unwrap();
147
148 let read_contents = read_file(&file_path).unwrap();
149 assert_eq!(read_contents, contents);
150
151 fs::remove_dir_all(&dir_path).unwrap();
152 }
153
154 #[test]
155 fn test_get_filepaths() {
156 let contents = b"".to_vec();
157
158 let dir_path = String::from("./tests/filepaths");
159 let input_path = extend_dir_path(&dir_path, "input");
160 let output_path = extend_dir_path(&dir_path, "output");
161
162 let inner_file_path = extend_dir_path(&input_path, "dir1/dir2/inner_file.txt");
163 write_file(&inner_file_path, contents.clone()).unwrap();
164
165 let base_file_path = extend_dir_path(&input_path, "base.txt");
166 write_file(&base_file_path, contents.clone()).unwrap();
167
168 let filepaths = get_filepaths(input_path, output_path).unwrap();
169
170 assert_eq!(
171 filepaths[0],
172 FilePath {
173 input_path: String::from("./tests/filepaths/input/dir1/dir2/inner_file.txt"),
174 output_path: String::from("./tests/filepaths/output/dir1/dir2/inner_file.txt"),
175 }
176 );
177 assert_eq!(
178 filepaths[1],
179 FilePath {
180 input_path: String::from("./tests/filepaths/input/base.txt"),
181 output_path: String::from("./tests/filepaths/output/base.txt"),
182 }
183 );
184
185 fs::remove_dir_all(&dir_path).unwrap();
186 }
187
188 #[test]
189 #[should_panic]
190 fn test_nonexistent_input() {
191 let input_path = String::from("./tests/non/existent/file.txt");
192 let output_path = String::from("./tests");
193
194 get_filepaths(input_path, output_path).unwrap();
195 }
196}