use std::{ fs::Metadata, time::{Duration, SystemTime}, }; use camino::{Utf8Path, Utf8PathBuf}; 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 path: Utf8PathBuf = std::env::args().nth(1).unwrap().parse().unwrap(); process(&path, &path) } /// 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) { //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); 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 meta = entry.metadata().unwrap(); let times = Times::metadata(&meta); row(root_path, entry.path(), ×) } Ok(_) => {} } } for entry in path.read_dir_utf8().unwrap() { let entry = entry.unwrap(); match entry.file_type() { Err(_) => panic!(), Ok(ft) if ft.is_dir() => process(root_path, entry.path()), Ok(_) => {} } } } fn row>(root: &Utf8Path, path: P, times: &Times) { let relative = path .as_ref() .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) -> String { time.map(|d| d.as_secs().to_string()).unwrap_or_default() } println!( "{},{},{},{}", escape(relative), time_string(times.created()), time_string(times.modified()), time_string(times.accessed()) ); } /// Blackslash-escape comma and backslash fn escape>(raw: S) -> String { let raw = raw.as_ref(); let mut escaped = String::with_capacity(raw.len()); for c in raw.chars() { match c { ',' | '\\' => { escaped.push('\\'); escaped.push(c); } _ => escaped.push(c), } } escaped } struct Times { created: Option, modified: Option, accessed: Option, } impl Times { /// Get the btime, mtime, atime from the Metadata. If a time cannot /// be attained, leave a None in it's place pub fn metadata(meta: &Metadata) -> Self { Self { created: meta.created().ok(), modified: meta.modified().ok(), accessed: meta.accessed().ok(), } } /// The time since the Unix Epoch the file was created pub fn created(&self) -> Option { Self::from_epoch(self.created.as_ref()) } /// The time since the Unix Epoch the file was last modified pub fn modified(&self) -> Option { Self::from_epoch(self.modified.as_ref()) } /// The time since the Unix Epoch the file was last accessed pub fn accessed(&self) -> Option { Self::from_epoch(self.accessed.as_ref()) } fn from_epoch(maybe_time: Option<&SystemTime>) -> Option { maybe_time.map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap()) } }