notmuch-tagrewriter 0.1.0

Retag notmuch mails
Documentation
use std::{
    collections::{BTreeMap, BTreeSet},
    fmt::{Debug, Display},
};

use derive_more::derive::{AsRef, Deref, DerefMut, From};
use itertools::Itertools;

use crate::{
    common::{Arrow, Imply, KnownTagState, Tag, TagKnowledge, Vector},
    dnf::{BooleanLike, Conjunction, SignedLitteral},
};

/// A trait for containers of tag knowledge.
pub trait StateTrait {
    /// Get the current knowledge about a `Tag`.
    fn get_tag_knowledge(&self, tag: &Tag) -> TagKnowledge;
    /// Get the height of this element, i.e., the ammounts of states known.
    fn height(&self) -> usize;
}

#[derive(PartialEq, Eq, Deref, Clone, DerefMut, PartialOrd, Ord, AsRef, From)]
/// An effective state implemented as a `BTreeMap<Tag, KnownTagState>`.
pub struct EffectiveState(BTreeMap<Tag, KnownTagState>);

#[derive(Debug)]
pub enum EffectiveStateError {
    /// The state will never match any message. This is typically due to a query of the form
    /// 'tag:a and not tag:a'.
    FalseValue,
}

impl EffectiveState {
    pub fn try_from_conjunction(conj: Conjunction<Tag>) -> Result<Self, EffectiveStateError> {
        if conj.is_true() {
            Ok(BTreeMap::new().into())
        } else if conj.is_false() {
            Err(EffectiveStateError::FalseValue)
        } else {
            // Assertion: there are no constants and all tags have different signs
            // (but they may be repeated multiple times)
            Ok(conj
                .iter()
                .map(|x| match x {
                    SignedLitteral::Val { litteral, sign } => (litteral.clone(), sign.as_other()),
                    _ => unreachable!(),
                })
                .collect::<BTreeMap<_, _>>()
                .into())
        }
    }

    /// Compute the vector so that the translation of `self` is `other`.
    pub fn compute_vector(&self, other: &Self) -> Arrow {
        let vector = Vector::from(
            other
                .iter()
                .filter_map(|(t, s)| {
                    if self.get(t).is_some_and(|v| v == s) {
                        None
                    } else {
                        Some((t.clone(), s.as_other()))
                    }
                })
                .collect::<BTreeMap<_, _>>(),
        );
        Arrow {
            source: self.clone(),
            vector,
        }
    }

    /// Represent this state as a notmuch query
    pub fn as_query(&self) -> String {
        self.iter()
            .map(|(t, ts)| format!("({}tag:{})", if ts.as_bool() { "" } else { "not " }, t))
            .join(" and ")
    }
}
impl Debug for EffectiveState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(&Conjunction::from(self.clone()), f)
    }
}

impl From<EffectiveState> for Conjunction<Tag> {
    fn from(value: EffectiveState) -> Self {
        value
            .iter()
            .map(|(t, s)| <SignedLitteral<Tag>>::from((t.clone(), s.clone())))
            .collect::<BTreeSet<_>>()
            .into()
    }
}

impl StateTrait for EffectiveState {
    fn get_tag_knowledge(&self, tag: &Tag) -> TagKnowledge {
        self.get(tag).map(|s| (*s).into()).unwrap_or_default()
    }
    fn height(&self) -> usize {
        self.0.len()
    }
}

impl Display for EffectiveState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", Conjunction::from(self.clone()).to_string())
    }
}

impl Imply for EffectiveState {
    fn implies(&self, other: &EffectiveState) -> bool {
        other
            .iter()
            .all(|(t, s)| self.get(t).is_some_and(|ss| ss == s))
    }
}