diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b62247e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,126 @@ +use std::{ + fs::Metadata, + time::{Duration, SystemTime}, +}; + +use camino::{Utf8Path, Utf8PathBuf}; + +// CSV definition: +// a row may contain any data, but comma are escaped with a backslash +// and blackslash are escaped, too. there is no header. if a time +// cannot be attained due to an error, it is left blank +// data is in the format: path, creation-time, modification-time + +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(); + + // behavior: + // reads directories twice. we're trying to be memory efficient rather than + // computationally. i'm not quite sure why. + // first pass: read and output file information + // second pass: read and outut directory information + process(path) +} + +fn process(path: Utf8PathBuf) { + //TODO: do not panic, please + let root_meta = std::fs::metadata(&path).unwrap(); + let root_times = Times::metadata(&root_meta); + row(&path, &root_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(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(entry.into_path()), + Ok(_) => {} + } + } +} + +fn row<P: AsRef<Utf8Path>>(path: P, times: &Times) { + println!( + "{},{},{},{}", + escape(path.as_ref()), + times + .created() + .map(|d| d.as_secs().to_string()) + .unwrap_or_default(), + times + .modified() + .map(|d| d.as_secs().to_string()) + .unwrap_or_default(), + times + .accessed() + .map(|d| d.as_secs().to_string()) + .unwrap_or_default() + ); +} + +fn escape<S: AsRef<str>>(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<SystemTime>, + modified: Option<SystemTime>, + accessed: Option<SystemTime>, +} + +impl Times { + pub fn metadata(meta: &Metadata) -> Self { + Self { + created: meta.created().ok(), + modified: meta.modified().ok(), + accessed: meta.accessed().ok(), + } + } + + // EPOCH created + pub fn created(&self) -> Option<Duration> { + self.created + .map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap()) + } + + pub fn modified(&self) -> Option<Duration> { + self.modified + .map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap()) + } + + pub fn accessed(&self) -> Option<Duration> { + self.accessed + .map(|st| st.duration_since(SystemTime::UNIX_EPOCH).unwrap()) + } +} |