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}