From 544aae00aa8d589a2b2817373611d21aa70eec0e Mon Sep 17 00:00:00 2001 From: gennyble Date: Thu, 24 Aug 2023 21:45:25 -0500 Subject: whenwasignore? --- .whenwasit-ignore | 5 +++ Cargo.lock | 16 ++++++++ Cargo.toml | 1 + readme.md | 9 ++++- src/main.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 .whenwasit-ignore diff --git a/.whenwasit-ignore b/.whenwasit-ignore new file mode 100644 index 0000000..ad1130c --- /dev/null +++ b/.whenwasit-ignore @@ -0,0 +1,5 @@ +# it's line a .gitignore but it's dumber. + +# ignore the top-level target and .git directories. +target +.git diff --git a/Cargo.lock b/Cargo.lock index 6616951..3aa373f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,9 +8,25 @@ version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "whenwasit" version = "0.1.0" dependencies = [ "camino", + "getopts", ] diff --git a/Cargo.toml b/Cargo.toml index 5bdc231..351971e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] camino = "1.1.6" +getopts = "0.2.21" diff --git a/readme.md b/readme.md index 5345195..734e81f 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,15 @@ -`whenwasit path` +``` +Usage: whenwasit [options] PATH +``` + little thing meant to be called in a git-hook as to keep track of file creation and modification dates, which are important to me. dumps csv output on stdout, errors abort with a panic. +You can use the flag `--ignore` to stop it from descending into +directories. The flag takes a path to a file that contains a list +of directory to ignore, relative to the path given to whenwasit. + **CSV Output Defintion:** data is output on `stdout` in a CSV format. because there are varying ways to make a CSV, it's described in detail below. diff --git a/src/main.rs b/src/main.rs index 0dc9899..0d560e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,110 @@ use std::{ - fs::Metadata, + fs::{File, Metadata}, + io::{BufRead, BufReader}, time::{Duration, SystemTime}, }; use camino::{Utf8Path, Utf8PathBuf}; +use getopts::Options; + +struct Context { + root: Utf8PathBuf, + ignores: Vec, +} + +impl Context { + pub fn new(root: Utf8PathBuf) -> Self { + Self { + root, + ignores: vec![], + } + } + + pub fn ignore_file(&mut self, ignore_path: Utf8PathBuf) { + let file = File::open(ignore_path).unwrap(); + let mut bufread = BufReader::new(file); + + let mut line = String::new(); + loop { + if bufread.read_line(&mut line).unwrap() == 0 { + break; + } + + if line.starts_with("\\#") || line.starts_with("/") { + self.ignores.push(String::from((&line[1..]).trim())); + } else if line.starts_with("#") || line.is_empty() || line.trim().is_empty() { + () + } else { + self.ignores.push(line.trim().to_owned()); + } + + line.clear(); + } + } + + pub fn is_file_ignored>(&self, path: P) -> bool { + match path.as_ref().strip_prefix(&self.root) { + Ok(rel) => { + for ignore in &self.ignores { + if rel.starts_with(ignore) { + return true; + } + } + + false + } + Err(_) => false, + } + } +} fn main() { - // currently only accepts one argument, the path to the directory it will - // recurse through and make the csv for. - // spits the csv on stdout + let mut opts = Options::new(); + #[rustfmt::skip] + opts.optopt("", "ignore", "file of paths to ignore, one per line", "PATH"); + opts.optflag("h", "help", "print this help thing"); + + let matches = match opts.parse(std::env::args()) { + Ok(m) => m, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + if matches.opt_present("h") { + println!("{}", opts.usage("Usage: whenwasit [options] PATH")); + return; + } - let path: Utf8PathBuf = std::env::args().nth(1).unwrap().parse().unwrap(); - process(&path, &path) + let root = match matches.free.get(1) { + None => { + println!("expected path"); + println!("{}", opts.usage("Usage: whenwasit [options] PATH")); + return; + } + Some(p) => Utf8PathBuf::from(p), + }; + + let mut context = Context::new(root); + + if let Some(ignore) = matches.opt_str("ignore") { + let ignore = Utf8PathBuf::from(ignore); + context.ignore_file(ignore); + } + + process(&context, &context.root) } /// Loop through the provided directory printing CSV rows. The `root_path` is /// used to make printed paths relative to it. /// /// Does two passes: First pass prints files. Second pass recurs, printing directories. -fn process(root_path: &Utf8Path, path: &Utf8Path) { +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(root_path, &path, &this_times); + row(&ctx.root, &path, &this_times); for entry in path.read_dir_utf8().unwrap() { let entry = entry.unwrap(); @@ -30,9 +112,14 @@ fn process(root_path: &Utf8Path, path: &Utf8Path) { match entry.file_type() { Err(_) => panic!(), Ok(ft) if ft.is_file() => { + let path = entry.path(); + if ctx.is_file_ignored(path) { + continue; + } + let meta = entry.metadata().unwrap(); let times = Times::metadata(&meta); - row(root_path, entry.path(), ×) + row(&ctx.root, path, ×) } Ok(_) => {} } @@ -43,7 +130,14 @@ fn process(root_path: &Utf8Path, path: &Utf8Path) { match entry.file_type() { Err(_) => panic!(), - Ok(ft) if ft.is_dir() => process(root_path, entry.path()), + Ok(ft) if ft.is_dir() => { + let path = entry.path(); + if ctx.is_file_ignored(path) { + continue; + } + + process(ctx, entry.path()) + } Ok(_) => {} } } -- cgit 1.4.1-3-g733a5