tudor-sql 0.2.0

Does sql stuff to todo.txt files
Documentation
use std::collections::BTreeSet;
use std::fmt::{Display, Formatter};

use pest_consume::Parser;

use crate::todo::body_token::TodoBodyToken;
use crate::todo::parser::TodoParserError;
use crate::todo::parser::{Rule, TodoParser};
use crate::TODO_DATE_FORMAT;

mod body_token;
mod builder;
mod parser;

#[derive(Eq, PartialEq, Debug)]
pub struct Todo {
    // TODO: make this read-only, getter-only?
    pub is_completed: bool,
    pub priority: Option<char>,
    pub creation_date: Option<time::Date>,
    pub completion_date: Option<time::Date>,
    body_tokens: Vec<TodoBodyToken>,
    pub threshold_date: Option<time::Date>,
    pub due_date: Option<time::Date>,
    pub contexts: BTreeSet<String>,
    pub projects: BTreeSet<String>,
    pub is_hidden: bool,
    // TODO: dependent tasks + NANO IDs
    // https://cdn.rawgit.com/bram85/topydo/master/docs/index.html#Dependencies
    // is_blocking_for: Vec<ID>,
    // is_blocked: bool,
    // id: Option<ID>,
}

#[allow(dead_code)]
impl Todo {
    pub fn has_context(&self, context: &str) -> bool {
        if !context.starts_with('@') {
            let c = "@".to_string() + context;
            return self.has_context(&c);
        }
        self.contexts.contains(context)
    }

    pub fn has_project(&self, project: &str) -> bool {
        if !project.starts_with('+') {
            let c = "+".to_string() + project;
            return self.has_project(&c);
        }
        self.projects.contains(project)
    }

    pub fn canonical_context(&self) -> Option<String> {
        self.contexts.first().map(|s| s.to_owned())
    }

    pub fn canonical_project(&self) -> Option<String> {
        self.projects.first().map(|s| s.to_owned())
    }

    pub fn parse(input: &str) -> std::result::Result<Todo, TodoParserError> {
        let input = input.trim();
        if input.is_empty() {
            return Err(TodoParserError::EmptyInput);
        }
        let nodes = TodoParser::parse(Rule::todo, input)?;
        let node = nodes.single()?;
        let todo = TodoParser::todo(node)?;
        Ok(todo)
    }
}

impl Display for Todo {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut tokens = Vec::new();
        if self.is_completed {
            tokens.push("x".to_string())
        };
        if let Some(d) = self.completion_date {
            tokens.push(d.format(TODO_DATE_FORMAT))
        };
        if let Some(p) = self.priority {
            tokens.push(format!("({})", p.to_uppercase()))
        };
        if let Some(d) = self.creation_date {
            tokens.push(d.format(TODO_DATE_FORMAT))
        };
        for t in &self.body_tokens {
            tokens.push(format!("{}", t))
        }
        write!(f, "{}", tokens.join(" "))?;
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn round_tripping_a_todo_with_display() {
        let input = "x 2021-01-03 (A) 2021-01-02 impl Display for Todos @pc +tudor due:2021-01-01 t:2021-01-01";
        let t = Todo::parse(input).unwrap();
        let got = format!("{}", t);
        assert_eq!(input, got);
    }
}