m2 0.0.0

Set of Unix tools to work with m2dirs
Documentation
use std::{
    error::Error,
    io::{IsTerminal, Read, Write, stdin, stdout},
};

use mail_parser::{HeaderValue, Message, MessageParser, MessagePart};

fn collect_parts<'a>(
    msg: &'a Message<'a>,
    part: &'a MessagePart<'a>,
    depth: usize,
    out: &mut Vec<(&'a MessagePart<'a>, usize)>,
) {
    out.push((part, depth));
    if let Some(subs) = part.sub_parts() {
        for &id in subs {
            collect_parts(msg, &msg.parts[id as usize], depth + 1, out);
        }
    }
}

fn get_type<'a>(part: &'a MessagePart<'a>) -> String {
    if let Some(ct) = part
        .headers()
        .iter()
        .find_map(|header| match &header.value {
            HeaderValue::ContentType(ctype) => Some(ctype),
            _ => None,
        })
    {
        let mut s = ct.ctype().to_string();

        if let Some(sub) = ct.subtype() {
            s.push('/');
            s.push_str(sub);
        }

        return s;
    }

    match &part.body {
        mail_parser::PartType::Text(_) => "text".to_string(),
        mail_parser::PartType::Html(_) => "html".to_string(),
        mail_parser::PartType::Binary(_) => "binary".to_string(),
        mail_parser::PartType::InlineBinary(_) => "inline-binary".to_string(),
        mail_parser::PartType::Multipart(_) => "multipart".to_string(),
        _ => "other".to_string(),
    }
}

fn run() -> Result<(), Box<dyn Error>> {
    let mut args = std::env::args().skip(1);

    let mut index: Option<usize> = None;
    let mut tree = false;
    let mut headers_only = false;
    let mut body_only = false;

    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--tree" => {
                tree = true;
                break;
            }
            "--headers" => {
                headers_only = true;
                body_only = false
            }
            "--body" => {
                body_only = true;
                headers_only = false
            }
            _ if index.is_none() => {
                index = Some(arg.parse()?);
                break;
            }
            _ => {}
        }
    }

    let msg = match args.find_map(|path| {
        let bytes = std::fs::read(path).ok()?;
        MessageParser::new().parse(&bytes).map(Message::into_owned)
    }) {
        Some(m) => m,
        None if !stdin().is_terminal() => {
            let mut buf = Vec::new();
            stdin().read_to_end(&mut buf)?;
            MessageParser::new()
                .parse(&buf)
                .map(Message::into_owned)
                .ok_or("missing message")?
        }
        _ => return Err("missing message".into()),
    };

    let mut parts = Vec::new();
    collect_parts(&msg, msg.root_part(), 0, &mut parts);

    if tree {
        for (i, (part, depth)) in parts.iter().enumerate() {
            println!("{:<4}{}{}", i, "  ".repeat(*depth), get_type(part));
        }
        return Ok(());
    }

    let idx = index.ok_or("missing index")?;
    let (part, _) = parts.get(idx).ok_or("index out of range")?;

    let raw = msg.raw_message();
    let h = part.raw_header_offset() as usize;
    let b = part.raw_body_offset() as usize;
    let e = part.raw_end_offset() as usize;

    let slice = if headers_only {
        &raw[h..b]
    } else if body_only {
        &raw[b..e]
    } else {
        &raw[h..e]
    };

    stdout().write_all(slice.trim_ascii())?;

    Ok(())
}

fn main() {
    if let Err(e) = run() {
        eprintln!("m2part: {e}");
        std::process::exit(1);
    }
}