about summary refs log tree commit diff
path: root/src/main.rs
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2023-08-25 17:57:51 -0500
committergennyble <gen@nyble.dev>2023-08-25 17:57:51 -0500
commit85052baf6ff0ce914ff44435cb8ba3edb8725947 (patch)
tree3812b173c1035a4090a110bf6dfb14fbc5afa065 /src/main.rs
parentd1c2d05fdb38c44bc5e92a53cc61183dc2982c7b (diff)
downloadwhenwasit-85052baf6ff0ce914ff44435cb8ba3edb8725947.tar.gz
whenwasit-85052baf6ff0ce914ff44435cb8ba3edb8725947.zip
add quiet flag, error handling
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs145
1 files changed, 109 insertions, 36 deletions
diff --git a/src/main.rs b/src/main.rs
index 0d560e6..a7c9df7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+use core::fmt;
 use std::{
 	fs::{File, Metadata},
 	io::{BufRead, BufReader},
@@ -9,6 +10,7 @@ use getopts::Options;
 
 struct Context {
 	root: Utf8PathBuf,
+	quiet: bool,
 	ignores: Vec<String>,
 }
 
@@ -16,18 +18,30 @@ impl Context {
 	pub fn new(root: Utf8PathBuf) -> Self {
 		Self {
 			root,
+			quiet: false,
 			ignores: vec![],
 		}
 	}
 
+	/// Reads the ignore file. Errors are fail and exit the process.
 	pub fn ignore_file(&mut self, ignore_path: Utf8PathBuf) {
-		let file = File::open(ignore_path).unwrap();
-		let mut bufread = BufReader::new(file);
+		let mut bufread = match File::open(&ignore_path) {
+			Ok(f) => BufReader::new(f),
+			Err(e) => {
+				self.print_err(format!("{ignore_path}: {e}"));
+				std::process::exit(1);
+			}
+		};
 
 		let mut line = String::new();
 		loop {
-			if bufread.read_line(&mut line).unwrap() == 0 {
-				break;
+			match bufread.read_line(&mut line) {
+				Ok(0) => break,
+				Ok(_) => (),
+				Err(e) => {
+					self.print_err(format!("{ignore_path}: {e}"));
+					std::process::exit(1);
+				}
 			}
 
 			if line.starts_with("\\#") || line.starts_with("/") {
@@ -56,6 +70,18 @@ impl Context {
 			Err(_) => false,
 		}
 	}
+
+	pub fn print_err<M: fmt::Display>(&self, message: M) {
+		if !self.quiet {
+			eprintln!("{message}")
+		}
+	}
+}
+
+fn print_usage(opts: &Options) {
+	let breif = "Usage: whenwasit [options] PATH";
+
+	println!("{}", opts.usage(&breif));
 }
 
 fn main() {
@@ -63,6 +89,7 @@ fn main() {
 	#[rustfmt::skip]
 	opts.optopt("", "ignore", "file of paths to ignore, one per line", "PATH");
 	opts.optflag("h", "help", "print this help thing");
+	opts.optflag("q", "quiet", "don't print errors to STDERR");
 
 	let matches = match opts.parse(std::env::args()) {
 		Ok(m) => m,
@@ -73,20 +100,21 @@ fn main() {
 	};
 
 	if matches.opt_present("h") {
-		println!("{}", opts.usage("Usage: whenwasit [options] PATH"));
+		print_usage(&opts);
 		return;
 	}
 
 	let root = match matches.free.get(1) {
 		None => {
-			println!("expected path");
-			println!("{}", opts.usage("Usage: whenwasit [options] PATH"));
+			println!("ERROR No path provided\n");
+			print_usage(&opts);
 			return;
 		}
 		Some(p) => Utf8PathBuf::from(p),
 	};
 
 	let mut context = Context::new(root);
+	context.quiet = matches.opt_present("quiet");
 
 	if let Some(ignore) = matches.opt_str("ignore") {
 		let ignore = Utf8PathBuf::from(ignore);
@@ -101,44 +129,85 @@ fn main() {
 ///
 /// Does two passes: First pass prints files. Second pass recurs, printing directories.
 fn process(ctx: &Context, path: &Utf8Path) {
-	//TODO: do not panic, please
-	let this_meta = std::fs::metadata(&path).unwrap();
-	let this_times = Times::metadata(&this_meta);
-	row(&ctx.root, &path, &this_times);
-
-	for entry in path.read_dir_utf8().unwrap() {
-		let entry = entry.unwrap();
-
-		match entry.file_type() {
-			Err(_) => panic!(),
-			Ok(ft) if ft.is_file() => {
-				let path = entry.path();
-				if ctx.is_file_ignored(path) {
+	match std::fs::metadata(&path) {
+		Err(e) => {
+			ctx.print_err(format!("{path}: {e}"));
+			return;
+		}
+		Ok(meta) => {
+			let this_times = Times::metadata(&meta);
+			row(&ctx.root, &path, &this_times);
+		}
+	}
+
+	let readdir = match path.read_dir_utf8() {
+		Err(e) => {
+			ctx.print_err(format!("{path}: {e}"));
+			return;
+		}
+		Ok(read) => read,
+	};
+
+	// 1st loop - print details about every file
+	for entry in readdir {
+		let entry = match entry {
+			Ok(e) => e,
+			Err(err) => {
+				ctx.print_err(format!("{path}: failed to read dir entry: {err}"));
+				continue;
+			}
+		};
+
+		let entry_path = entry.path();
+
+		match entry.metadata() {
+			Err(err) => {
+				ctx.print_err(format!("{entry_path}: {err}"));
+				continue;
+			}
+			Ok(meta) => {
+				if !meta.is_file() || ctx.is_file_ignored(entry_path) {
 					continue;
 				}
 
-				let meta = entry.metadata().unwrap();
 				let times = Times::metadata(&meta);
-				row(&ctx.root, path, &times)
+				row(&ctx.root, entry_path, &times)
 			}
-			Ok(_) => {}
-		}
+		};
 	}
 
-	for entry in path.read_dir_utf8().unwrap() {
-		let entry = entry.unwrap();
+	let readdir = match path.read_dir_utf8() {
+		Err(e) => {
+			ctx.print_err(format!("{path}: {e}"));
+			return;
+		}
+		Ok(read) => read,
+	};
 
-		match entry.file_type() {
-			Err(_) => panic!(),
-			Ok(ft) if ft.is_dir() => {
-				let path = entry.path();
-				if ctx.is_file_ignored(path) {
+	// 2nd loop - run resursivly on directories
+	for entry in readdir {
+		let entry = match entry {
+			Ok(e) => e,
+			Err(err) => {
+				ctx.print_err(format!("{path}: failed to read dir entry: {err}"));
+				continue;
+			}
+		};
+
+		let entry_path = entry.path();
+
+		match entry.metadata() {
+			Err(err) => {
+				ctx.print_err(format!("{entry_path}: {err}"));
+				continue;
+			}
+			Ok(meta) => {
+				if !meta.is_dir() || ctx.is_file_ignored(entry_path) {
 					continue;
 				}
 
-				process(ctx, entry.path())
+				process(ctx, entry_path)
 			}
-			Ok(_) => {}
 		}
 	}
 }
@@ -149,8 +218,6 @@ fn row<P: AsRef<Utf8Path>>(root: &Utf8Path, path: P, times: &Times) {
 		.strip_prefix(root)
 		.expect("row wasn't relative to the root; what happened?");
 
-	/// Returns an owned string with the duration formatted as seconds, or a
-	/// borrowed empty string
 	fn time_string(time: Option<Duration>) -> String {
 		time.map(|d| d.as_secs().to_string()).unwrap_or_default()
 	}
@@ -214,7 +281,13 @@ impl Times {
 		Self::from_epoch(self.accessed.as_ref())
 	}
 
+	/// SystemTime relative to the Unix Epoch.
 	fn from_epoch(maybe_time: Option<&SystemTime>) -> Option<Duration> {
-		maybe_time.map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap())
+		// As far as I know, a SystemTime can not be below UNIX_EPOCH. This
+		// unwrap should never panic, but we fal to a duration of 0 just in case
+		maybe_time.map(|st| {
+			st.duration_since(SystemTime::UNIX_EPOCH)
+				.unwrap_or(Duration::from_secs(0))
+		})
 	}
 }