m2 0.0.0

Set of Unix tools to work with m2dirs
Documentation
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}");
    }
}