use derive_more::derive::{AsMut, AsRef, Debug, Deref, DerefMut, Display, From, FromStr};
use itertools::{Either, Itertools};
use log::{debug, trace};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{
collections::{BTreeMap, BTreeSet},
fmt::Display,
str::FromStr,
};
use crate::{
dnf::{BooleanLike, LitteralTrait, Sign, SignedLitteral},
state::{EffectiveState, StateTrait},
};
#[derive(
PartialEq,
Eq,
Clone,
Hash,
PartialOrd,
Ord,
Serialize,
Deserialize,
Deref,
DerefMut,
Display,
From,
FromStr,
Debug,
)]
#[display("{_0}")]
#[debug("tag:{_0}")]
pub struct Tag(String);
impl LitteralTrait for Tag {}
#[derive(Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub enum KnownTagState {
Present,
Absent,
}
impl BooleanLike for KnownTagState {
const TRUTHY: Self = KnownTagState::Present;
const FALSY: Self = KnownTagState::Absent;
}
impl From<(Tag, KnownTagState)> for SignedLitteral<Tag> {
fn from((tag, sign): (Tag, KnownTagState)) -> Self {
Self::Val {
litteral: tag,
sign: Sign::from_bool(sign.as_bool()),
}
}
}
#[derive(PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub enum TagKnowledge {
Unknown,
Known(KnownTagState),
}
impl From<KnownTagState> for TagKnowledge {
fn from(value: KnownTagState) -> Self {
Self::Known(value)
}
}
impl Default for TagKnowledge {
fn default() -> Self {
Self::Unknown
}
}
impl Imply for KnownTagState {
fn implies(&self, other: &Self) -> bool {
return self == other;
}
}
impl From<KnownTagState> for bool {
fn from(value: KnownTagState) -> Self {
match value {
KnownTagState::Absent => false,
KnownTagState::Present => true,
}
}
}
impl Imply for TagKnowledge {
fn implies(&self, other: &Self) -> bool {
match self {
Self::Unknown => self == other,
Self::Known(kts) => match other {
Self::Unknown => true,
Self::Known(kts2) => kts.implies(kts2),
},
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum TagAction {
Add,
Remove,
}
impl BooleanLike for TagAction {
const FALSY: Self = Self::Remove;
const TRUTHY: Self = Self::Add;
}
pub trait Imply {
fn implies(&self, other: &Self) -> bool;
}
pub trait AsQuery {
fn as_query(&self) -> String;
}
#[derive(
Debug,
PartialEq,
Eq,
Deref,
DerefMut,
Clone,
PartialOrd,
Ord,
From,
AsRef,
SerializeDisplay,
DeserializeFromStr,
)]
pub struct Vector(BTreeMap<Tag, TagAction>);
impl AsQuery for Vector {
fn as_query(&self) -> String {
EffectiveState::from(
self.0
.iter()
.map(|(t, ta)| (t.clone(), ta.as_other()))
.collect::<BTreeMap<Tag, KnownTagState>>(),
)
.as_query()
}
}
impl Vector {
pub fn new() -> Self {
Vector(BTreeMap::new())
}
pub fn translate(&self, point: &EffectiveState) -> EffectiveState {
let mut translated = point.clone();
translated.extend(self.iter().map(|(t, a)| (t.clone(), a.clone().into())));
translated
}
pub fn execute(&self, message: ¬much::Message, dry_run: bool) -> Result<(), notmuch::Error> {
trace!("Tagging message {}", message.id());
if !dry_run {
for (tag, action) in self.iter() {
match action {
TagAction::Add => message.add_tag(&tag)?,
TagAction::Remove => message.remove_tag(&tag)?,
}
}
}
Ok(())
}
fn try_insert_direction<Direction>(&mut self, direction: Direction) -> Result<(), VectorError>
where
Direction: Into<(Tag, TagAction)>,
{
let (tag, action) = direction.into();
match self.insert(tag.clone(), action) {
Some(_) => Err(VectorError::AmbiguousRule(tag)),
None => Ok(()),
}
}
fn try_from_iterator<I, VD>(iter: I) -> Result<Self, VectorError>
where
I: IntoIterator<Item = VD>,
VD: Into<(Tag, TagAction)>,
{
Self::try_from_failible_iterator(iter.into_iter().map(Result::Ok))
}
fn try_from_failible_iterator<I, VD>(iter: I) -> Result<Self, VectorError>
where
I: IntoIterator<Item = Result<VD, VectorError>>,
VD: Into<(Tag, TagAction)>,
{
let mut empty_transition = Self::new();
iter.into_iter()
.try_for_each(|trans| empty_transition.try_insert_direction(trans?))?;
Ok(empty_transition)
}
fn iter_elements(&self) -> impl Iterator<Item = VectorDirection> + use<'_> {
self.iter()
.map(|(t, a)| VectorDirection::from((t.clone(), *a)))
}
}
#[derive(Debug)]
pub enum VectorError {
ParseError(&'static str),
AmbiguousRule(Tag), }
impl std::fmt::Display for VectorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AmbiguousRule(tag) => write!(f, "Ambiguous transition for tag {}", tag),
Self::ParseError(msg) => write!(f, "Parse error: {msg}"),
}
}
}
impl Display for Vector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.iter_elements().map(|e| e.to_string()).join(" ")
)
}
}
impl FromStr for Vector {
type Err = VectorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Vector::try_from_failible_iterator(s.split_whitespace().map(VectorDirection::from_str))
}
}
#[derive(Debug, Eq, PartialEq, Clone, Display, SerializeDisplay, DeserializeFromStr)]
pub enum VectorDirection {
#[display("+{}", _0)]
Add(Tag),
#[display("-{}", _0)]
Remove(Tag),
}
impl VectorDirection {
fn get_tag(&self) -> &Tag {
match self {
VectorDirection::Add(t) => t,
VectorDirection::Remove(t) => t,
}
}
fn get_action(&self) -> TagAction {
match self {
VectorDirection::Add(_) => TagAction::Add,
VectorDirection::Remove(_) => TagAction::Remove,
}
}
}
impl From<(Tag, TagAction)> for VectorDirection {
fn from((t, action): (Tag, TagAction)) -> Self {
match action {
TagAction::Add => VectorDirection::Add(t),
TagAction::Remove => VectorDirection::Remove(t),
}
}
}
impl From<VectorDirection> for (Tag, TagAction) {
fn from(value: VectorDirection) -> Self {
(value.get_tag().clone(), value.get_action())
}
}
impl FromStr for VectorDirection {
type Err = VectorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
match (chars.next(), chars.as_str()) {
(_, "") => Err(VectorError::ParseError("Empty tag")),
(Some('+'), t) => Ok(Self::Add(t.to_string().into())),
(Some('-'), t) => Ok(Self::Remove(t.to_string().into())),
_ => Err(VectorError::ParseError("Not a valid transition string")),
}
}
}
impl From<VectorDirection> for Either<Tag, Tag> {
fn from(value: VectorDirection) -> Self {
match value {
VectorDirection::Add(t) => Either::Left(t),
VectorDirection::Remove(t) => Either::Right(t),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Arrow {
pub source: EffectiveState,
pub vector: Vector,
}
impl From<TagAction> for KnownTagState {
fn from(value: TagAction) -> Self {
match value {
TagAction::Add => Self::Present,
TagAction::Remove => Self::Absent,
}
}
}
impl AsQuery for Arrow {
fn as_query(&self) -> String {
format!(
"({}) and not ({})",
self.source.as_query(),
self.vector.as_query()
)
}
}
impl Arrow {
pub fn target(&self) -> EffectiveState {
self.vector.translate(&self.source)
}
pub fn execute(
&self,
db: ¬much::Database,
dry_run: bool,
lastmod: Option<u64>,
) -> Result<(), notmuch::Error> {
let query_txt = if let Some(last) = lastmod {
format!("({}) and lastmod:{}..", self.as_query(), last)
} else {
self.as_query()
};
debug!("Searching for query: {}", query_txt);
let query = db.create_query(&query_txt)?;
let count = query.count_messages()?;
let messages = query.search_messages()?;
debug!("Tagging {} messges with: {}", count, self.vector);
for message in messages {
self.vector.execute(&message, dry_run)?
}
Ok(())
}
}
impl PartialOrd for Arrow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
let self_level = self.source.height();
let other_level = other.source.height();
if self_level == other_level {
self.source.partial_cmp(&other.source)
} else {
self_level.partial_cmp(&other_level)
}
}
}
impl Ord for Arrow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let self_level = self.source.height();
let other_level = other.source.height();
if self_level == other_level {
self.source.cmp(&other.source)
} else {
self_level.cmp(&other_level)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct LabelledArrow {
pub name: String,
pub arrow: Arrow,
}
impl LabelledArrow {
pub fn eplison(source: EffectiveState, target: EffectiveState) -> Self {
let vector: Vector = Vector(
target
.iter()
.map(|(t, ts)| (t.clone(), ts.as_other()))
.collect(),
);
LabelledArrow {
name: "ε".to_string(),
arrow: Arrow { source, vector },
}
}
fn execute(
&self,
db: ¬much::Database,
dry_run: bool,
lastmod: Option<u64>,
) -> Result<(), notmuch::Error> {
debug!("Retagging with rule {}.", &self.name);
self.arrow.execute(db, dry_run, lastmod)
}
}
impl PartialOrd for LabelledArrow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.arrow.partial_cmp(&other.arrow)
}
}
impl Ord for LabelledArrow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.arrow.cmp(&other.arrow)
}
}
impl LabelledArrow {
pub fn move_base_point(&self, source: EffectiveState) -> LabelledArrow {
LabelledArrow {
name: self.name.clone(),
arrow: Arrow {
source: source,
vector: self.arrow.vector.clone(),
},
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Deref, DerefMut, AsRef, AsMut, From)]
pub struct Quiver(BTreeSet<LabelledArrow>);
impl Quiver {
pub fn execute(
&self,
db: ¬much::Database,
dry_run: bool,
lastmod: Option<u64>,
) -> Result<(), notmuch::Error> {
debug!("Retagging…");
for arr in self.iter() {
db.begin_atomic()?;
arr.execute(db, dry_run, lastmod)?;
db.end_atomic()?;
}
Ok(())
}
}