fsutils/
lib.rs

1// Copyright 2020 Jared Forth.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Utilities for common filesystem operations.
10//!
11//! **fsutils** provides an API based on Bash commands and includes a number
12//! of utility functions to make interacting with the filesystem simpler and more
13//! ergonomic.
14
15#[macro_use]
16extern crate log;
17
18use std::{fs, process, io};
19use std::path::{Path, PathBuf};
20use std::io::{Write, Read};
21use std::fs::{File, OpenOptions};
22
23/// Creates a directory recursively at passed path
24/// and returns a boolean based on success or failure.
25///
26/// ## Usage:
27///
28/// ```
29/// assert_eq!(fsutils::mkdir("testdir"), true);
30///
31/// # // Cleanup
32/// # fsutils::rmdir("testdir");
33/// ```
34pub fn mkdir(path: &str) -> bool {
35    if !path_exists(path) {
36        match fs::create_dir_all(path) {
37            Ok(_) => {
38                info!("Created {}", path);
39                true
40            }
41            Err(e) => {
42                error!("Error creating file: {}", e);
43                false
44            }
45        }
46    } else {
47        false
48    }
49}
50
51/// Removes a file at passed path
52/// and returns a boolean based on success or failure.
53///
54/// ## Usage:
55///
56/// ```
57/// fsutils::create_file("testfile.txt");
58/// assert_eq!(fsutils::rm("testfile.txt"), true);
59/// ```
60pub fn rm(path: &str) -> bool {
61    // str to Path
62    let new_path = Path::new(path);
63    if new_path.exists() {
64        match fs::remove_file(path) {
65            Ok(_) => {
66                info!("Removed file {}", path);
67                true
68            },
69            Err(e) => {
70                error!("Error removing {} {}", path, e);
71                false
72            }
73        }
74    } else {
75        false
76    }
77}
78
79/// Removes an empty directory
80/// and returns a boolean based on success or failure.
81///
82/// This does not remove a directory recursively. Use `fsutils::rm_r`
83/// if you need recursive directory deletion.
84///
85/// # Usage:
86///
87/// ```
88/// use fsutils::rmdir;
89/// fsutils::mkdir("testdir");
90/// assert_eq!(rmdir("testdir"), true);
91/// ```
92pub fn rmdir(path: &str) -> bool {
93    // Turn str path into Path
94    let new_path = Path::new(path);
95    if new_path.exists() {
96        match fs::remove_dir(path) {
97            Ok(_) => {
98                info!("Removed directory at {}", path);
99                true
100            },
101            Err(e) => {
102                error!("The directory {} is not empty. {}", path, e);
103                false
104            }
105        }
106    } else {
107        error!("Directory {} does not exist", path);
108        true
109    }
110}
111
112/// Removes a directory recursively
113/// and returns a boolean based on success or failure.
114///
115/// You should use this carefully.
116///
117/// ## Usage:
118///
119/// ```
120/// fsutils::mkdir("testdir");
121/// fsutils::create_file("testdir/testfile");
122///
123/// assert_eq!(fsutils::rm_r("testdir"), true);
124/// ```
125pub fn rm_r(path: &str) -> bool {
126    // Turn str path into Path
127    let new_path = Path::new(path);
128    if new_path.exists() {
129        match fs::remove_dir_all(path) {
130            Ok(_) => {
131                info!("Removed directory at {}", path);
132                true
133            },
134            Err(e) => {
135                error!("The directory {} is not empty. {}", path, e);
136                false
137            }
138        }
139    } else {
140        error!("Directory does not exist");
141        true
142    }
143}
144
145/// Checks if a path exists
146/// and returns a boolean based on success or failure.
147///
148/// # Usage:
149///
150/// ```
151/// fsutils::create_file("testfile");
152/// assert_eq!(fsutils::path_exists("testfile"), true);
153/// assert_eq!(fsutils::path_exists("a_very_1234_unlikely_9876_filename"), false);
154/// 
155/// # // Cleanup
156/// # fsutils::rm("testfile");
157/// ```
158pub fn path_exists(path: &str) -> bool {
159    // Turn str path into Path
160    let new_path = Path::new(path);
161    if new_path.exists() {
162        info!("{} exists", path);
163        true
164    } else {
165        info!("{} does not exist", path);
166        false
167    }
168}
169
170/// Checks if a directory is empty
171/// and returns a boolean based on success or failure.
172///
173/// # Usage:
174///
175/// ```
176/// fsutils::mkdir("empty_directory");
177/// fsutils::mkdir("full_directory");
178/// fsutils::create_file("full_directory/a_file.txt");
179/// fsutils::create_file("full_directory/another_file.txt");
180///
181/// assert_eq!(fsutils::directory_is_empty("full_directory"), false);
182/// assert_eq!(fsutils::directory_is_empty("empty_directory"), true);
183///
184/// # // Cleanup
185/// # fsutils::rmdir("empty_directory");
186/// # fsutils::rm_r("full_directory");
187/// ```
188pub fn directory_is_empty(path: &str) -> bool {
189    // Turn str path into Path
190    let new_path = Path::new(path);
191    if new_path.exists() {
192        if new_path.is_dir() {
193            let mut i = 0;
194            // iterate through entries and count them
195            // `fs::read_dir` returns type `ReadDir`
196            for entry in fs::read_dir(path) {
197                // Iterating over `ReadDir` returns a Result<DirEntry>`
198                // which is what we want to give us the count.
199                for _ in entry {
200                    i += 1;
201                }
202            }
203            // if the count of directory entries is 1 (it counts itself), it is empty
204            if i == 0 {
205                true
206            } else {
207                false
208            }
209        } else {
210            error!("The path {} passed is not a directory", path);
211            false
212        }
213    } else {
214        error!("The path {} passed does not exist.", path);
215        false
216    }
217}
218
219/// Moves a file from `path_one` to `path_two`
220/// and returns a boolean based on success or failure.
221///
222/// # Usage:
223///
224/// ```
225/// fsutils::mkdir("directory_one");
226/// fsutils::mkdir("directory_two");
227/// fsutils::create_file("directory_one/the_file");
228///
229/// assert_eq!(fsutils::mv("directory_one/the_file", "directory_two/the_file"), true);
230///
231/// # // Cleanup
232/// # fsutils::rm_r("directory_one");
233/// # fsutils::rm_r("directory_two");
234/// ```
235pub fn mv(path_one: &str, path_two: &str) -> bool {
236    let p1 = Path::new(path_one);
237    if p1.exists() {
238        match fs::rename(path_one, path_two) {
239            Ok(_) => {
240                info!("Moved from {} to {}.", path_one, path_two);
241                true
242            },
243            Err(e) => {
244                error!("File moving error: {}", e);
245                false
246            }
247        }
248    } else {
249        false
250    }
251}
252
253/// Creates a file and returns a boolean based on success or failure.
254///
255/// ## Usage:
256///
257/// ```
258/// assert_eq!(fsutils::create_file("the_file"), true);
259///
260/// # // Cleanup
261/// # fsutils::rm("the_file");
262/// ```
263pub fn create_file(path: &str) -> bool {
264    match fs::File::create(path) {
265        Ok(_f) => {
266            info!("Successfully wrote file to {}", path);
267            true
268        }
269        Err(e) => {
270            error!("{}", e);
271            false
272        }
273    }
274}
275
276/// Creates a file from bytes
277/// and returns a boolean based on success or failure.
278///
279/// # Usage:
280///
281/// ```
282/// let binary_file: &'static [u8] = b"01001000 01100101 01101100 01101100 01101111 00100001";
283///
284/// assert_eq!(fsutils::create_file_bytes("a_binary_file", binary_file), true);
285///
286/// # // Cleanup
287/// # fsutils::rm("a_binary_file");
288/// ```
289pub fn create_file_bytes(path: &str, bytes_to_write: &[u8]) -> bool {
290    match fs::File::create(path) {
291        Ok(mut buffer) => {
292            match buffer.write_all(bytes_to_write) {
293                Ok(_) => {
294                    info!("Wrote buffer to {}", path);
295                    true
296                },
297                Err(e) => {
298                    error!("{}", e);
299                    false
300                }
301            }
302        }
303        Err(e) => {
304            error!("{}", e);
305            false
306        }
307    }
308}
309
310/// Reads data to a file
311/// and returns a `bool` on success
312///
313/// ## Usage:
314///
315/// ```
316/// fsutils::write_file("text.txt", "Hello, world!");
317///
318/// assert_eq!(fsutils::read_file("text.txt"), "Hello, world!");
319///
320/// # // Cleanup
321/// # fsutils::rm("text.txt");
322/// ```
323pub fn write_file(path: &str, contents: &str) -> bool {
324    match File::create(path) {
325        Ok(mut f) => {
326            f.write_all(contents.as_ref()).unwrap();
327            true
328        }
329        Err(e) => {
330            error!("Cannot write file to location '{}' {}", path, e);
331            false
332        }
333    }
334}
335
336/// Appends data to a file
337/// and returns a `bool` on success
338///
339/// ## Usage:
340///
341/// ```
342/// fsutils::write_file_append("text.txt", "Hello, world! ");
343/// fsutils::write_file_append("text.txt", "Hi Again!");
344///
345/// assert_eq!(fsutils::read_file("text.txt"), "Hello, world! Hi Again!");
346///
347/// # // Cleanup
348/// # fsutils::rm("text.txt");
349/// ```
350pub fn write_file_append(path: &str, contents: &str) -> bool {
351    match OpenOptions::new()
352        .write(true)
353        .create(true)
354        .append(true)
355        .open(path) {
356        Ok(mut f) => {
357            f.write_all(contents.as_ref()).unwrap();
358            true
359        }
360        Err(e) => {
361            error!("Cannot write file {}", e);
362            false
363        }
364    }
365}
366
367/// Reads data from a file
368/// and returns a `String` with the files's contents
369///
370/// ## Usage:
371///
372/// ```
373/// fsutils::write_file("text.txt", "Hello, world!");
374///
375/// assert_eq!(fsutils::read_file("text.txt"), "Hello, world!");
376///
377/// # // Cleanup
378/// # fsutils::rm("text.txt");
379/// ```
380pub fn read_file(path: &str) -> String {
381    let mut contents = String::new();
382    match File::open(path) {
383        Ok(mut f) => {
384            f.read_to_string(&mut contents).unwrap();
385        }
386        Err(e) => error!("Cannot read file {}", e)
387    }
388    contents
389}
390
391/// Change the current working directory
392///
393/// ## Usage:
394///
395/// ```
396/// use fsutils::cd;
397/// use std::path::Path;
398///
399/// assert_eq!(cd(Path::new("target")).is_some(), true);
400/// assert_eq!(cd(Path::new("does_not_exist")).is_none(), true)
401/// ```
402pub fn cd(cd_path: &Path) -> Option<()> {
403    // Change working directory to directory
404    match std::env::set_current_dir(&cd_path) {
405        Ok(_) => {
406            info!("Changed current dir to {}", cd_path.display());
407            Some(())
408        },
409        Err(e) => {
410            error!("Could not set current dir to {}: {}", cd_path.display(), e);
411            None
412        }
413    }
414}
415
416/// Execute an arbitrary system command
417///
418/// ## Usage
419///
420/// ```
421/// use fsutils::run_command;
422///
423/// assert!(run_command("ls", ["-l"].to_vec()).is_some());
424/// ```
425pub fn run_command(program: &str, args: Vec<&str>) -> Option<i32> {
426    match process::Command::new(program).args(args).status() {
427        Ok(s) => {
428            s.code()
429        }
430        Err(e) => {
431            error!("There was an error {}", e);
432            None
433        }
434    }
435}
436
437/// List directory contents
438///
439/// ## Usage
440///
441/// ```
442/// use fsutils::ls;
443///
444/// assert!(ls(".").is_some());
445/// ```
446pub fn ls(path: &str) -> Option<Vec<PathBuf>> {
447    match fs::read_dir(path) {
448        Ok(p) => {
449            let results = p.map(|res| res.map(|e| e.path()))
450                .collect::<Result<Vec<_>, io::Error>>();
451            match results {
452                Ok(r) => {
453                    println!("{:?}", r);
454                    Some(r)
455                },
456                Err(e) => {
457                    println!("{}", e);
458                    None
459                }
460            }
461        }
462        Err(e) => {
463            println!("{:?}", e);
464            None
465        }
466    }
467}