[go: up one dir, main page]

fsio/
file.rs

1//! # file
2//!
3//! File utility functions.
4//!
5
6#[cfg(test)]
7#[path = "./file_test.rs"]
8mod file_test;
9
10use crate::directory;
11use crate::error::FsIOError;
12use crate::path::as_path::AsPath;
13use crate::types::FsIOResult;
14use std::fs::{read, read_to_string, remove_file, File, OpenOptions};
15use std::io;
16use std::io::Write;
17
18/// Ensures the provided path leads to an existing file.
19/// If the file does not exist, this function will create an emtpy file.
20///
21/// # Arguments
22///
23/// * `path` - The file path
24///
25/// # Example
26///
27/// ```
28/// use crate::fsio::file;
29/// use std::path::Path;
30///
31/// fn main() {
32///     let result = file::ensure_exists("./target/__test/file_test/dir1/dir2/file.txt");
33///     assert!(result.is_ok());
34///
35///     let path = Path::new("./target/__test/file_test/dir1/dir2/file.txt");
36///     assert!(path.exists());
37/// }
38/// ```
39pub fn ensure_exists<T: AsPath + ?Sized>(path: &T) -> FsIOResult<()> {
40    let file_path = path.as_path();
41
42    if file_path.exists() {
43        if file_path.is_file() {
44            Ok(())
45        } else {
46            Err(FsIOError::PathAlreadyExists(
47                format!("Unable to create file: {:?}", &file_path).to_string(),
48            ))
49        }
50    } else {
51        directory::create_parent(path)?;
52
53        match File::create(&file_path) {
54            Ok(_) => Ok(()),
55            Err(error) => Err(FsIOError::IOError(
56                format!("Unable to create file: {:?}", &file_path).to_string(),
57                Some(error),
58            )),
59        }
60    }
61}
62
63/// Creates and writes the text to the requested file path.
64/// If a file exists at that path, it will be overwritten.
65///
66/// # Arguments
67///
68/// * `path` - The file path
69/// * `text` - The file text content
70///
71/// # Example
72///
73/// ```
74/// use crate::fsio::file;
75/// use std::path::Path;
76///
77/// fn main() {
78///     let file_path = "./target/__test/file_test/write_text_file/file.txt";
79///     let result = file::write_text_file(file_path, "some content");
80///     assert!(result.is_ok());
81///
82///     let text = file::read_text_file(file_path).unwrap();
83///
84///     assert_eq!(text, "some content");
85/// }
86/// ```
87pub fn write_text_file<T: AsPath + ?Sized>(path: &T, text: &str) -> FsIOResult<()> {
88    write_file(path, text.as_bytes())
89}
90
91/// Appends (or creates) and writes the text to the requested file path.
92/// If a file exists at that path, the content will be appended.
93///
94/// # Arguments
95///
96/// * `path` - The file path
97/// * `text` - The file text content
98///
99/// # Example
100///
101/// ```
102/// use crate::fsio::file;
103/// use std::path::Path;
104///
105/// fn main() {
106///     let file_path = "./target/__test/file_test/append_text_file/file.txt";
107///     let mut result = file::write_text_file(file_path, "some content");
108///     assert!(result.is_ok());
109///     result = file::append_text_file(file_path, "\nmore content");
110///     assert!(result.is_ok());
111///
112///     let text = file::read_text_file(file_path).unwrap();
113///
114///     assert_eq!(text, "some content\nmore content");
115/// }
116/// ```
117pub fn append_text_file<T: AsPath + ?Sized>(path: &T, text: &str) -> FsIOResult<()> {
118    append_file(path, text.as_bytes())
119}
120
121/// Creates and writes the raw data to the requested file path.
122/// If a file exists at that path, it will be overwritten.
123///
124/// # Arguments
125///
126/// * `path` - The file path
127/// * `data` - The file raw content
128///
129/// # Example
130///
131/// ```
132/// use crate::fsio::file;
133/// use std::path::Path;
134/// use std::str;
135///
136/// fn main() {
137///     let file_path = "./target/__test/file_test/write_file/file.txt";
138///     let mut result = file::write_file(file_path, "some content".as_bytes());
139///     assert!(result.is_ok());
140///     result = file::append_file(file_path, "\nmore content".as_bytes());
141///     assert!(result.is_ok());
142///
143///     let data = file::read_file(file_path).unwrap();
144///
145///     assert_eq!(str::from_utf8(&data).unwrap(), "some content\nmore content");
146/// }
147/// ```
148pub fn write_file<T: AsPath + ?Sized>(path: &T, data: &[u8]) -> FsIOResult<()> {
149    modify_file(path, &move |file: &mut File| file.write_all(data), false)
150}
151
152/// Appends (or creates) and writes the raw data to the requested file path.
153/// If a file exists at that path, the content will be appended.
154///
155/// # Arguments
156///
157/// * `path` - The file path
158/// * `data` - The file raw content
159///
160/// # Example
161///
162/// ```
163/// use crate::fsio::file;
164/// use std::path::Path;
165/// use std::str;
166///
167/// fn main() {
168///     let file_path = "./target/__test/file_test/append_file/file.txt";
169///     let mut result = file::write_file(file_path, "some content".as_bytes());
170///     assert!(result.is_ok());
171///     result = file::append_file(file_path, "\nmore content".as_bytes());
172///     assert!(result.is_ok());
173///
174///     let data = file::read_file(file_path).unwrap();
175///
176///     assert_eq!(str::from_utf8(&data).unwrap(), "some content\nmore content");
177/// }
178/// ```
179pub fn append_file<T: AsPath + ?Sized>(path: &T, data: &[u8]) -> FsIOResult<()> {
180    modify_file(path, &move |file: &mut File| file.write_all(data), true)
181}
182
183/// Overwrites or appends the requested file and triggers the provided write_content function to
184/// enable custom writing.
185///
186/// # Arguments
187///
188/// * `path` - The file path
189/// * `write_content` - The custom writing function
190/// * `append` - True to append false to overwrite
191///
192/// # Example
193///
194/// ```
195/// use crate::fsio::file;
196/// use std::fs::File;
197/// use std::io::Write;
198/// use std::str;
199///
200/// fn main() {
201///     let file_path = "./target/__test/file_test/modify_file/file.txt";
202///     let mut result = file::modify_file(
203///         file_path,
204///         &move |file: &mut File| file.write_all("some content".as_bytes()),
205///         false,
206///     );
207///     assert!(result.is_ok());
208///     result = file::modify_file(
209///         file_path,
210///         &move |file: &mut File| file.write_all("\nmore content".as_bytes()),
211///         true,
212///     );
213///     assert!(result.is_ok());
214///
215///     let data = file::read_file(file_path).unwrap();
216///
217///     assert_eq!(str::from_utf8(&data).unwrap(), "some content\nmore content");
218/// }
219/// ```
220pub fn modify_file<T: AsPath + ?Sized>(
221    path: &T,
222    write_content: &dyn Fn(&mut File) -> io::Result<()>,
223    append: bool,
224) -> FsIOResult<()> {
225    directory::create_parent(path)?;
226
227    let file_path = path.as_path();
228
229    // create or open
230    let result = if append && file_path.exists() {
231        OpenOptions::new().append(true).open(file_path)
232    } else {
233        File::create(&file_path)
234    };
235
236    match result {
237        Ok(mut fd) => match write_content(&mut fd) {
238            Ok(_) => match fd.sync_all() {
239                Ok(_) => Ok(()),
240                Err(error) => Err(FsIOError::IOError(
241                    format!("Error finish up writing to file: {:?}", &file_path).to_string(),
242                    Some(error),
243                )),
244            },
245            Err(error) => Err(FsIOError::IOError(
246                format!("Error while writing to file: {:?}", &file_path).to_string(),
247                Some(error),
248            )),
249        },
250        Err(error) => Err(FsIOError::IOError(
251            format!("Unable to create/open file: {:?} for writing.", &file_path).to_string(),
252            Some(error),
253        )),
254    }
255}
256
257/// Reads the requested text file and returns its content.
258///
259/// # Arguments
260///
261/// * `path` - The file path
262///
263/// # Example
264///
265/// ```
266/// use crate::fsio::file;
267/// use std::path::Path;
268///
269/// fn main() {
270///     let file_path = "./target/__test/file_test/write_text_file/file.txt";
271///     let result = file::write_text_file(file_path, "some content");
272///     assert!(result.is_ok());
273///
274///     let text = file::read_text_file(file_path).unwrap();
275///
276///     assert_eq!(text, "some content");
277/// }
278/// ```
279pub fn read_text_file<T: AsPath + ?Sized>(path: &T) -> FsIOResult<String> {
280    let file_path = path.as_path();
281
282    match read_to_string(&file_path) {
283        Ok(content) => Ok(content),
284        Err(error) => Err(FsIOError::IOError(
285            format!("Unable to read file: {:?}", &file_path).to_string(),
286            Some(error),
287        )),
288    }
289}
290
291/// Reads the requested file and returns its content.
292///
293/// # Arguments
294///
295/// * `path` - The file path
296///
297/// # Example
298///
299/// ```
300/// use crate::fsio::file;
301/// use std::path::Path;
302/// use std::str;
303///
304/// fn main() {
305///     let file_path = "./target/__test/file_test/read_file/file.txt";
306///     let mut result = file::write_file(file_path, "some content".as_bytes());
307///     assert!(result.is_ok());
308///     result = file::append_file(file_path, "\nmore content".as_bytes());
309///     assert!(result.is_ok());
310///
311///     let data = file::read_file(file_path).unwrap();
312///
313///     assert_eq!(str::from_utf8(&data).unwrap(), "some content\nmore content");
314/// }
315/// ```
316pub fn read_file<T: AsPath + ?Sized>(path: &T) -> FsIOResult<Vec<u8>> {
317    let file_path = path.as_path();
318
319    match read(&file_path) {
320        Ok(content) => Ok(content),
321        Err(error) => Err(FsIOError::IOError(
322            format!("Unable to read file: {:?}", &file_path).to_string(),
323            Some(error),
324        )),
325    }
326}
327
328/// Deletes the requested file.
329/// If the file does not exist, this function will return valid response.
330///
331/// # Arguments
332///
333/// * `path` - The file path
334///
335/// # Example
336///
337/// ```
338/// use crate::fsio::file;
339/// use std::path::Path;
340/// use std::str;
341///
342/// fn main() {
343///     let file_path = "./target/__test/file_test/delete_file/file.txt";
344///     let mut result = file::ensure_exists(file_path);
345///     assert!(result.is_ok());
346///
347///     let path = Path::new(file_path);
348///     assert!(path.exists());
349///
350///     result = file::delete(file_path);
351///     assert!(result.is_ok());
352///
353///     assert!(!path.exists());
354/// }
355/// ```
356pub fn delete<T: AsPath + ?Sized>(path: &T) -> FsIOResult<()> {
357    let file_path = path.as_path();
358
359    if file_path.exists() {
360        if file_path.is_file() {
361            match remove_file(file_path) {
362                Ok(_) => Ok(()),
363                Err(error) => Err(FsIOError::IOError(
364                    format!("Unable to delete file: {:?}", &file_path).to_string(),
365                    Some(error),
366                )),
367            }
368        } else {
369            Err(FsIOError::NotFile(
370                format!("Path: {:?} is not a file.", &file_path).to_string(),
371            ))
372        }
373    } else {
374        Ok(())
375    }
376}
377
378/// Deletes the requested file.
379/// If the file does not exist, this function will return true.
380///
381/// # Arguments
382///
383/// * `path` - The file path
384///
385/// # Example
386///
387/// ```
388/// use crate::fsio::file;
389/// use std::path::Path;
390/// use std::str;
391///
392/// fn main() {
393///     let file_path = "./target/__test/file_test/delete_file/file.txt";
394///     let result = file::ensure_exists(file_path);
395///     assert!(result.is_ok());
396///
397///     let path = Path::new(file_path);
398///     assert!(path.exists());
399///
400///     let deleted = file::delete_ignore_error(file_path);
401///     assert!(deleted);
402///
403///     assert!(!path.exists());
404/// }
405/// ```
406pub fn delete_ignore_error<T: AsPath + ?Sized>(path: &T) -> bool {
407    match delete(path) {
408        Ok(_) => true,
409        Err(_) => false,
410    }
411}