hypertask_engine 0.2.0

The core logic engine used to run hypertask
Documentation
use crate::engine::{HyperTaskEngineContext, Mutation, Query};
use crate::error::*;
use crate::id::Id;
use crate::prop::Prop;
use crate::recur::Recur;
use crate::rpn::StackMachine;
use crate::tag::{Sign, Tag};
use chrono::prelude::*;
use serde::{Deserialize, Serialize, Serializer};
use std::cmp::Ordering;
use std::collections::HashSet;
use time::Duration;

#[derive(Serialize, Deserialize, Debug)]
pub struct Task {
    created_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    done: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    due: Option<DateTime<Utc>>,
    id: Id,
    #[serde(skip_serializing_if = "Option::is_none")]
    recur: Option<Recur>,
    #[serde(skip_serializing_if = "Option::is_none")]
    snooze: Option<DateTime<Utc>>,
    updated_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    wait: Option<DateTime<Utc>>,

    #[serde(serialize_with = "ordered_set")]
    #[serde(skip_serializing_if = "HashSet::is_empty")]
    #[serde(default)]
    tags: HashSet<String>,
}

fn ordered_set<S>(value: &HashSet<String>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut vec = value.iter().collect::<Vec<&String>>();

    vec.sort();

    vec.serialize(serializer)
}

impl Task {
    pub fn generate<
        InputIterator: Iterator<Item = HyperTaskResult<Task>>,
        Context: HyperTaskEngineContext<InputIterator>,
    >(
        context: &mut Context,
    ) -> Self {
        Self {
            created_at: context.get_now(),
            description: None,
            done: None,
            due: None,
            id: Id::generate(context),
            recur: None,
            snooze: None,
            tags: HashSet::new(),
            updated_at: context.get_now(),
            wait: None,
        }
    }

    pub fn get_created_at(&self) -> &DateTime<Utc> {
        &self.created_at
    }
    pub fn get_description(&self) -> &Option<String> {
        &self.description
    }
    pub fn get_done(&self) -> &Option<DateTime<Utc>> {
        &self.done
    }
    pub fn get_due(&self) -> &Option<DateTime<Utc>> {
        &self.due
    }
    pub fn get_id(&self) -> &Id {
        &self.id
    }
    pub fn get_recur(&self) -> &Option<Recur> {
        &self.recur
    }
    pub fn get_snooze(&self) -> &Option<DateTime<Utc>> {
        &self.snooze
    }
    pub fn get_tags(&self) -> &HashSet<String> {
        &self.tags
    }
    pub fn get_updated_at(&self) -> &DateTime<Utc> {
        &self.updated_at
    }
    pub fn get_wait(&self) -> &Option<DateTime<Utc>> {
        &self.wait
    }

    pub fn satisfies_queries(&self, queries: &[Query]) -> bool {
        if queries.is_empty() {
            return false;
        }

        let mut default = false;

        for query in queries {
            match query {
                Query::Id(id) => {
                    if id.sub_eq(&self.id) {
                        return true;
                    } else {
                        continue;
                    }
                }

                Query::Tag(Tag {
                    sign: Sign::Plus,
                    name,
                }) => {
                    if self.tags.contains(name) {
                        return true;
                    } else {
                        continue;
                    }
                }

                Query::Tag(Tag {
                    sign: Sign::Minus,
                    name,
                }) => {
                    if self.tags.contains(name) {
                        return false;
                    } else {
                        default = true;
                        continue;
                    }
                }
            }
        }

        default
    }

    pub fn apply_mutations(&mut self, mutations: &[Mutation], now: &DateTime<Utc>) -> &Self {
        for m in mutations {
            self.apply_mutation(m, now);
        }

        self
    }

    pub fn apply_mutation(&mut self, mutation: &Mutation, now: &DateTime<Utc>) -> &Self {
        match mutation {
            Mutation::SetTag(Tag {
                sign: Sign::Plus,
                name,
            }) => {
                self.tags.insert(name.to_string());
            }
            Mutation::SetTag(Tag {
                sign: Sign::Minus,
                name,
            }) => {
                self.tags.remove(name);
            }
            Mutation::SetProp(Prop::Description(description)) => {
                self.description = Some(description.to_string());
            }
            Mutation::SetProp(Prop::Done(done)) => {
                if let Some(recur) = &self.recur {
                    let dt: Duration = Duration::from(recur);

                    if let Some(due) = self.due {
                        self.due = Some(due + dt);
                    }
                    if let Some(wait) = self.wait {
                        self.wait = Some(wait + dt);
                    }
                } else {
                    self.done = Some(*done);
                }
            }
            Mutation::SetProp(Prop::Due(due)) => {
                self.due = *due;
            }
            Mutation::SetProp(Prop::Snooze(snooze)) => {
                self.snooze = *snooze;
            }
            Mutation::SetProp(Prop::Wait(wait)) => {
                self.wait = *wait;
            }
            Mutation::SetProp(Prop::Recur(recur)) => self.recur = recur.clone(),
        }

        self.updated_at = *now;

        self
    }

    pub fn finalise(self, stack_machine: &mut StackMachine) -> HyperTaskResult<FinalisedTask> {
        Ok(FinalisedTask {
            score: self.get_score(stack_machine)?,
            task: self,
        })
    }

    fn get_score(&self, stack_machine: &mut StackMachine) -> HyperTaskResult<f64> {
        stack_machine.run_on(self)
    }

    pub fn is_overdue(&self, now: &DateTime<Utc>) -> bool {
        if let Some(due) = self.due {
            due < *now
        } else {
            false
        }
    }

    pub fn is_soon_due(&self, now: &DateTime<Utc>) -> bool {
        if let Some(due) = self.due {
            due < (*now + Duration::days(3))
        } else {
            false
        }
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct FinalisedTask {
    task: Task,
    score: f64,
}

impl FinalisedTask {
    pub fn get_task(&self) -> &Task {
        &self.task
    }
    pub fn get_score(&self) -> &f64 {
        &self.score
    }
}

impl PartialOrd for FinalisedTask {
    fn partial_cmp(&self, other: &FinalisedTask) -> Option<Ordering> {
        self.score.partial_cmp(&other.score).map(|x| x.reverse())
    }
}

impl Ord for FinalisedTask {
    fn cmp(&self, other: &FinalisedTask) -> Ordering {
        self.partial_cmp(other).unwrap_or(Ordering::Equal)
    }
}

impl Eq for FinalisedTask {}
impl PartialEq for FinalisedTask {
    fn eq(&self, other: &FinalisedTask) -> bool {
        self.score == other.score
    }
}