lkml 0.1.2

Program to download and assort emails from mailing lists supported by `lei`
use std::{hash::Hash, path::PathBuf};

use maildir::{MailEntry, MailEntryError};
use mailparse::{MailHeaderMap, ParsedMail};
use thiserror::Error;

use crate::config::Config;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Type {
    New,
    Folder(usize),
}

pub struct Mail<'a> {
    pub typ: Type,
    pub id: String,
    pub maildir_id: String,
    pub parent: Option<String>,
    pub parsed: ParsedMail<'a>,
    pub path: PathBuf,
}

impl PartialEq for Mail<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.path == other.path
    }
}

impl Eq for Mail<'_> where PathBuf: Eq {}

impl Hash for Mail<'_> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.path.hash(state);
    }
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("`{0}` is missing an `Message-ID` header.")]
    MissingID(PathBuf),
    #[error("`{1}` has {0} `Message-ID` headers and none are preferred.")]
    MultipleIDs(usize, PathBuf),
    #[error("`{1}` has {0} `In-Reply-To` headers.")]
    MultiReply(usize, PathBuf),
    #[error("could not parse mail: {0}")]
    MailEntry(#[from] MailEntryError),
}

pub fn parse<'a>(mail: &'a mut MailEntry, typ: Type, cfg: &Config) -> Result<Mail<'a>, Error> {
    let path = mail.path().to_owned();
    let maildir_id = mail.id().to_owned();
    let parsed = mail.parsed()?;
    let id = parsed.headers.get_all_headers("Message-ID");
    let id = match id.len() {
        0 => return Err(Error::MissingID(path)),
        1 => id[0].get_value(),
        len => {
            if let Some(id) = id
                .iter()
                .find(|id| cfg.quirks.prefer.contains(&id.get_value()))
            {
                id.get_value()
            } else {
                return Err(Error::MultipleIDs(len, path));
            }
        }
    };
    let id = id
        .trim_start_matches(|c| c != '<')
        .trim_end_matches(|c| c != '>')
        .to_owned();
    let parent = parsed.headers.get_all_headers("In-Reply-To");
    let parent = match parent.len() {
        0 => None,
        1 => Some(parent[0].get_value()),
        len => return Err(Error::MultiReply(len, path)),
    };
    let parent = parent.map(|p| {
        p.trim_start_matches(|c| c != '<')
            .trim_end_matches(|c| c != '>')
            .to_owned()
    });
    Ok(Mail {
        maildir_id,
        id,
        parsed,
        typ,
        parent,
        path,
    })
}