file_access/
lib.rs

1//! This crate is a collection of utilities to make performing certain
2//! file manipulations more convenient.
3//!
4//! # Examples
5//! ```
6//! use file_access::AsFile;
7//!
8//! fn main() -> std::io::Result<()> {
9//!     Ok({
10//!         let text = "Cargo.toml".as_file().read_string()?;
11//!         println!("{}", text);
12//!
13//!         "Cargo.toml".as_file().read_lines()?
14//!             .iter()
15//!             .for_each(|line| {
16//!                 println!("{}", line);
17//!             });
18//!
19//!         "file.1".as_file().write_string(&"Hello, World!")?;
20//!
21//!         let file = "file.1".as_file();
22//!         file.append_lines(&vec!["hello", "world"])?;
23//!         file.copy_to(&"file.2")?; // copies ./file.1 to ./file.2
24//!
25//!         "file.2".as_file().rename_to(&"file.1")?; // replace
26//!         "file.1".as_file().delete()?; // clean-up
27//!     })
28//! }
29//! ```
30
31pub use as_file::*; // re-export AsFile
32pub use file_path::*; // re-export FilePath
33use internal::{traits::to_vec_string::*, types::*};
34use std::{
35    fs::{self, File, Metadata},
36    io::{Error, ErrorKind, Read, Result},
37    path::PathBuf,
38};
39
40pub mod as_file;
41pub mod file_path;
42mod internal;
43
44// Gets a File::open handle from AsRef<str> such as String or &str
45fn get_file<Path: AsRef<str>>(file_path: &Path) -> Result<File> {
46    File::open(file_path.as_ref())
47}
48
49// Converts AsRef<str> such as String or &str to PathBuf
50fn path_of<Path: AsRef<str>>(file_path: &Path) -> PathBuf {
51    PathBuf::from(file_path.as_ref())
52}
53
54// Creates a file and its full directory path if they don't exist
55fn mk_file<Path: AsRef<str>>(file_path: &Path) -> Result<File> {
56    if let Some(path) = path_of(file_path).parent() {
57        fs::create_dir_all(path)?;
58    }
59    return File::create(file_path.as_ref());
60}
61
62/// Reads the contents of a file.
63///
64/// # Returns
65/// Result<`String`>
66///
67/// # Examples
68/// ```
69/// fn main() -> std::io::Result<()> {
70///     Ok({
71///         let file_path: &str = "Cargo.toml";
72///         let file_path: String = String::from(file_path);
73///
74///         let text: String = file_access::read_string(&file_path)?;
75///         println!("{}", text);
76///     })
77/// }
78/// ```
79pub fn read_string<Path: AsRef<str>>(file_path: &Path) -> Result<String> {
80    let mut buf = String::new();
81    get_file(file_path)?.read_to_string(&mut buf)?;
82
83    return Ok(buf);
84}
85
86/// Reads the contents of a file and returns it as lines.
87///
88/// # Returns
89/// Result<`Vec<String>`>
90///
91/// # Examples
92/// ```
93/// fn main() -> std::io::Result<()> {
94///     Ok({
95///         let file_path: &str = "Cargo.toml";
96///         let file_path: String = String::from(file_path);
97///
98///         let lines: Vec<String> = file_access::read_lines(&file_path)?;
99///         lines.iter().for_each(|line| println!("{}", line));
100///     })
101/// }
102/// ```
103pub fn read_lines<Path: AsRef<str>>(file_path: &Path) -> Result<Lines> {
104    Ok(read_string(file_path)?
105        .lines()
106        .map(ToString::to_string)
107        .collect())
108}
109
110/// Writes text to a file. This function will create the file **and its full directory path** if they don't exist,
111/// and will entirely replace the contents.
112///
113/// # Parameters
114/// - `file_path`: **borrowed** `AsRef<str>` such as `String` or `&str`
115/// - `text`: **borrowed** `AsRef<str>` such as `String` or `&str`
116///
117/// # Returns
118/// Result<`()`>
119///
120/// # Examples
121/// ```
122/// fn main() -> std::io::Result<()> {
123///     Ok({
124///         let file_path: &str = "write_to/absolute_or_relative.path";
125///         let file_path: String = String::from(file_path);
126///
127///         let text: &str = "Hello, World!";
128///         let text: String = String::from(text);
129///
130///         file_access::write_string(&file_path, &text)?;
131///
132///         // Clean-up:
133///         file_access::delete(&"write_to")?; // ./write_to/
134///     })
135/// }
136/// ```
137pub fn write_string<Path: AsRef<str>, Text: AsRef<str>>(
138    file_path: &Path,
139    text: &Text,
140) -> Result<()> {
141    let path = path_of(file_path);
142    if !path.exists() {
143        mk_file(file_path)?;
144    }
145    return fs::write(path, text.as_ref());
146}
147
148/// Writes a list of text as lines to a file. This function will create the file **and its full directory path** if they don't exist,
149/// and will entirely replace the contents with the provided strings each on its own line.
150///
151/// # Parameters
152/// - `file_path`: **borrowed** `AsRef<str>` such as `String` or `&str`
153/// - `lines`: **borrowed** `Vec<AsRef<str>>` such as `Vec<String>` or `Vec<&str>`
154///
155/// # Returns
156/// Result<`()`>
157///
158/// # Examples
159/// ```
160/// fn main() -> std::io::Result<()> {
161///     Ok({
162///         let file_path: &str = "lines_to/absolute_or_relative.path";
163///         let file_path: String = String::from(file_path);
164///
165///         let lines: Vec<&str> = "Hello, World!".split_whitespace().collect();
166///         let lines: Vec<String> = lines.iter().map(ToString::to_string).collect();
167///
168///         file_access::write_lines(&file_path, &lines)?;
169///
170///         // Clean-up:
171///         file_access::delete(&"lines_to"); // ./lines_to/
172///     })
173/// }
174/// ```
175pub fn write_lines<Path: AsRef<str>, Line: AsRef<str>>(
176    file_path: &Path,
177    lines: &Vec<Line>,
178) -> Result<()> {
179    write_string(file_path, &lines.to_vec_string().join("\n"))
180}
181
182/// Appends text to a file. This function will append the contents of the file,
183/// or write a new one **and its full directory path** if they don't exist yet.
184///
185/// # Parameters
186/// - `file_path`: **borrowed** `AsRef<str>` such as `String` or `&str`
187/// - `text`: **borrowed** `AsRef<str>` such as `String` or `&str`
188///
189/// # Returns
190/// Result<`()`>
191///
192/// # Examples
193/// ```
194/// fn main() -> std::io::Result<()> {
195///     Ok({
196///         let file_path: &str = "append_to/absolute_or_relative.path";
197///         let file_path: String = String::from(file_path);
198///
199///         let text: &str = "Hello, World!";
200///         let text: String = String::from(text);
201///
202///         file_access::append_string(&file_path, &text)?;
203///
204///         // Clean-up:
205///         file_access::delete(&"append_to"); // ./append_to/
206///     })
207/// }
208/// ```
209pub fn append_string<Path: AsRef<str>, Text: AsRef<str>>(
210    file_path: &Path,
211    text: &Text,
212) -> Result<()> {
213    write_string(
214        file_path,
215        &match read_string(file_path) {
216            Ok(file) => format!("{}{}", file, text.as_ref()),
217            Err(_) => text.as_ref().to_string(),
218        },
219    )
220}
221
222/// Appends a list of text as lines to a file. This function will append the contents of the file,
223/// or write a new one **and its full directory path** if they don't exist yet.
224///
225/// # Parameters
226/// - `file_path`: **borrowed** `AsRef<str>` such as `String` or `&str`
227/// - `lines`: **borrowed** `Vec<AsRef<str>>` such as `Vec<String>` or `Vec<&str>`
228///
229/// # Returns
230/// Result<`()`>
231///
232/// # Examples
233/// ```
234/// fn main() -> std::io::Result<()> {
235///     Ok({
236///         let file_path: &str = "append_lines_to/absolute_or_relative.path";
237///         let file_path: String = String::from(file_path);
238///
239///         let lines: Vec<&str> = "Hello, World!".split_whitespace().collect();
240///         let lines: Vec<String> = lines.iter().map(ToString::to_string).collect();
241///
242///         file_access::append_lines(&file_path, &lines)?;
243///
244///         // Clean-up:
245///         file_access::delete(&"append_lines_to"); // ./append_lines_to/
246///     })
247/// }
248/// ```
249pub fn append_lines<Path: AsRef<str>, Line: AsRef<str>>(
250    file_path: &Path,
251    lines: &Vec<Line>,
252) -> Result<()> {
253    let mut file = match read_lines(file_path) {
254        Ok(lines) => lines,
255        Err(_) => vec![],
256    };
257    file.extend_from_slice(&lines.to_vec_string());
258
259    return write_lines(file_path, &file);
260}
261
262/// Deletes a file, or a directory **recursively**.
263///
264/// # Parameters
265/// - `file_path`: **borrowed** `AsRef<str>` such as `String` or `&str`
266///
267/// # Returns
268/// Result<`()`>
269///
270/// # Examples
271/// ```
272/// fn main() -> std::io::Result<()> {
273///     Ok({
274///         let file_path: &str = "absolute_or_relative_path/to_a_file/or_a_directory";
275///         let file_path: String = String::from(file_path);
276///
277///         file_access::write_string(&file_path, &"Hello, World!");
278///         file_access::delete(&file_path)?; // delete file
279///
280///         // Delete directory:
281///         file_access::delete(&"absolute_or_relative_path")?;
282///     })
283/// }
284/// ```
285pub fn delete<Path: AsRef<str>>(file_path: &Path) -> Result<()> {
286    let path = path_of(file_path);
287
288    if path.is_file() {
289        return fs::remove_file(path);
290    }
291
292    if path.is_dir() {
293        return fs::remove_dir_all(path);
294    }
295
296    return Err(Error::new(ErrorKind::InvalidInput, file_path.as_ref()));
297}
298
299/// Copies the contents of a file and write it to a destination.
300/// This function will entirely replace the contents of the destination if it already exists.
301///
302/// # Parameters
303/// - `from`: **borrowed** `AsRef<str>` such as `String` or `&str`
304/// - `to`: **borrowed** `AsRef<str>` such as `String` or `&str`
305///
306/// # Returns
307/// Result<`()`>
308///
309/// # Examples
310/// ```
311/// fn main() -> std::io::Result<()> {
312///     Ok({
313///         let source: &str = "Cargo.toml";
314///         let source: String = String::from(source);
315///
316///         let destination: &str = "Cargo.toml.2";
317///         let destination: String = String::from(destination);
318///
319///         file_access::copy(&source, &destination)?;
320///
321///         // Delete file:
322///         file_access::delete(&destination);
323///     })
324/// }
325/// ```
326pub fn copy<From: AsRef<str>, To: AsRef<str>>(from: &From, to: &To) -> Result<()> {
327    write_string(to, &read_string(from)?)
328}
329
330/// Copies the contents of a file, writes it to a destination and then deletes the source.
331/// This function will entirely replace the contents of the destination if it already exists.
332///
333/// # Parameters
334/// - `from`: **borrowed** `AsRef<str>` such as `String` or `&str`
335/// - `to`: **borrowed** `AsRef<str>` such as `String` or `&str`
336///
337/// # Returns
338/// Result<`()`>
339///
340/// # Examples
341/// ```
342/// fn main() -> std::io::Result<()> {
343///     Ok({
344///         let source: &str = "file.1";
345///         let source: String = String::from(source);
346///
347///         let destination: &str = "file.2";
348///         let destination: String = String::from(destination);
349///
350///         file_access::write_string(&source, &"Hello, World!")?;
351///         file_access::rename(&source, &destination)?;
352///
353///         // Clean-up:
354///         file_access::delete(&destination)?;
355///     })
356/// }
357/// ```
358pub fn rename<From: AsRef<str>, To: AsRef<str>>(from: &From, to: &To) -> Result<()> {
359    copy(from, to)?;
360
361    return delete(from);
362}
363
364/// Queries metadata about the underlying file.
365///
366/// # Returns
367/// Result<`Metadata`>
368///
369/// # Examples
370/// ```
371/// fn main() -> std::io::Result<()> {
372///     Ok({
373///         let file_path: &str = "Cargo.toml";
374///         let file_path: String = String::from(file_path);
375///
376///         let metadata: std::fs::Metadata = file_access::get_metadata(&file_path)?;
377///         println!("{:#?}", metadata);
378///     })
379/// }
380/// ```
381pub fn get_metadata<Path: AsRef<str>>(file_path: &Path) -> Result<Metadata> {
382    get_file(file_path)?.metadata()
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use std::io::Result;
389
390    // cargo test -- --show-output --test-threads=1
391    // cargo test <TESTNAME> --show-output
392
393    #[test]
394    fn read_string() -> Result<()> {
395        Ok({
396            // Arrange
397            let file = "Cargo.toml";
398
399            // Action
400            let text = super::read_string(&file)?;
401            println!("{text}");
402
403            // Assert
404            assert_ne!(text.len(), 0);
405        })
406    }
407
408    #[test]
409    fn read_lines() -> Result<()> {
410        Ok({
411            // Arrange
412            let file = "Cargo.toml";
413
414            // Action
415            let lines = super::read_lines(&file)?;
416            for line in &lines {
417                println!("{line}");
418            }
419
420            // Assert
421            assert_ne!(lines.len(), 0);
422        })
423    }
424
425    #[test]
426    fn write_string() -> Result<()> {
427        Ok({
428            // Arrange
429            let file = "write_string/file_access.txt";
430            let text = "Hello, World!";
431
432            // Action
433            super::write_string(&file, &text)?;
434
435            // Assert
436            assert_eq!(super::read_string(&file)?, text);
437
438            // Clean-up
439            super::delete(&"write_string")?;
440        })
441    }
442
443    #[test]
444    fn write_lines() -> Result<()> {
445        Ok({
446            // Arrange
447            let file = "write_lines/file_access.txt";
448            let lines = "Hello, World!"
449                .split_whitespace()
450                .map(ToString::to_string)
451                .collect();
452
453            // Action
454            super::write_lines(&file, &lines)?;
455
456            // Assert
457            assert_eq!(super::read_lines(&file)?, lines);
458
459            // Clean-up
460            super::delete(&"write_lines")?;
461        })
462    }
463
464    #[test]
465    fn append_string() -> Result<()> {
466        Ok({
467            // Arrange
468            let file = "append_string/file_access.txt";
469            let text = "Hello, World!";
470            super::write_string(&file, &text)?;
471
472            // Action
473            super::append_string(&file, &text)?;
474
475            // Assert
476            assert_eq!(super::read_string(&file)?, format!("{text}{text}"));
477
478            // Clean-up
479            super::delete(&"append_string")?;
480        })
481    }
482
483    #[test]
484    fn append_lines() -> Result<()> {
485        Ok({
486            // Arrange
487            let file = "append_lines/file_access.txt";
488            let lines1 = vec!["1", "2"]; // .to_vec_string();
489            super::write_lines(&file, &lines1)?;
490
491            // Action
492            let lines2 = vec!["3", "4"]; //.to_vec_string();
493            super::append_lines(&file, &lines2)?;
494
495            // Assert
496            assert_eq!(super::read_lines(&file)?, vec!["1", "2", "3", "4"]); // .to_vec_string());
497
498            // Clean-up
499            super::delete(&"append_lines")?;
500        })
501    }
502
503    #[test]
504    fn delete() -> Result<()> {
505        Ok({
506            // Arrange
507            let file = "delete/file_access.txt";
508            mk_file(&file)?;
509
510            // Action
511            super::delete(&file)?;
512
513            // Assert
514            assert!(!path_of(&file).exists(), "{file} should no longer exist");
515
516            // Clean-up
517            super::delete(&"delete")?;
518        })
519    }
520
521    #[test]
522    fn copy() -> Result<()> {
523        Ok({
524            // Arrange
525            let from = "copy_from/file_access.txt";
526            let to = "copy_to/file_access.txt";
527            super::write_string(&from, &"Hello, World!")?;
528
529            // Action
530            super::copy(&from, &to)?;
531
532            // Assert
533            assert_eq!(
534                super::read_string(&from)?,
535                super::read_string(&to)?,
536                "{from} and {to} should contain the same text"
537            );
538
539            // Clean-up
540            super::delete(&"copy_from")?;
541            super::delete(&"copy_to")?;
542        })
543    }
544
545    #[test]
546    fn rename() -> Result<()> {
547        Ok({
548            // Arrange
549            let from = "rename_from/file_access.txt";
550            let to = "rename_to/file_access.txt";
551            let text = "Hello, World!";
552            super::write_string(&from, &text)?;
553
554            // Action
555            super::rename(&from, &to)?;
556
557            // Assert
558            assert!(!path_of(&from).exists(), "{from} should no longer exist");
559            assert_eq!(
560                super::read_string(&to)?,
561                text,
562                "{to} should contain: {text}"
563            );
564
565            // Clean-up
566            super::delete(&"rename_from")?;
567            super::delete(&"rename_to")?;
568        })
569    }
570}