about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs126
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(), &times)
+			}
+			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())
+	}
+}