use std::{
cmp::Ordering,
error::Error,
fs,
io::{BufRead, IsTerminal, stdin},
process::exit,
time::UNIX_EPOCH,
};
use mail_parser::{Address, MessageParser};
#[derive(Copy, Clone)]
enum Key {
Date,
Size,
From,
Mtime,
}
pub fn run() -> Result<(), Box<dyn Error>> {
let mut keys = Vec::new();
let mut paths = Vec::new();
for arg in std::env::args().skip(1) {
match arg.as_str() {
"-h" | "--help" => {
eprint!("{}", include_str!("./help.txt"));
exit(0)
}
"-v" | "--version" => {
eprintln!("{} v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION"));
exit(0);
}
_ if arg.starts_with('-') && arg.len() > 1 => {
for c in arg.chars().skip(1) {
match c {
'd' => keys.push(Key::Date),
's' => keys.push(Key::Size),
'f' => keys.push(Key::From),
'm' => keys.push(Key::Mtime),
_ => {
paths.push(arg);
break;
}
}
}
}
_ => paths.push(arg),
}
}
if keys.is_empty() {
keys.push(Key::Date);
}
if !stdin().is_terminal() {
paths.extend(stdin().lock().lines().filter_map(Result::ok));
}
let mut items: Vec<_> = paths
.into_iter()
.filter_map(|path| {
let bytes = fs::read(&path).ok()?;
let msg = MessageParser::new().parse_headers(&bytes)?.into_owned();
let meta = fs::metadata(&path).ok();
let size = meta.as_ref().map(|m| m.len()).unwrap_or(bytes.len() as u64);
let mtime = meta
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_secs());
let from = msg
.from()
.and_then(Address::first)
.and_then(|f| f.address.clone())
.map(|addr| addr.to_lowercase());
Some((path, msg, size, mtime, from))
})
.collect();
items.sort_by(|a, b| {
keys.iter()
.map(|k| match k {
Key::Date => a.1.date().cmp(&b.1.date()),
Key::Size => a.2.cmp(&b.2),
Key::From => a.4.cmp(&b.4),
Key::Mtime => a.3.cmp(&b.3),
})
.find(|&ord| ord != Ordering::Equal)
.unwrap_or(Ordering::Equal)
});
for (path, ..) in &items {
println!("{path}");
}
Ok(())
}
pub fn main() {
if let Err(e) = run() {
eprintln!("m2sort: {e}");
}
}