taskforge 0.2.0

Task management shared functions and structures for the taskforge family of tools.
Documentation
// Copyright 2018 Mathew Robinson <chasinglogic@gmail.com>. All rights reserved. Use of this source code is
// governed by the Apache-2.0 license that can be found in the LICENSE file.


//! Tokens and functions for the Taskforge Query Language

use chrono::prelude::*;
use std::fmt;

/// Operator is any token which indicates a query operator
///
/// Examples include '=', 'or', '~'.
#[derive(Debug, PartialEq, Clone)]
pub enum Operator {
    GT,
    LT,
    GTE,
    LTE,
    EQ,
    NE,
    LIKE,
    NLIKE,

    AND,
    OR,
}

impl fmt::Display for Operator {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Operator::GT => ">",
                Operator::GTE => ">=",
                Operator::LT => "<",
                Operator::LTE => "<=",
                Operator::EQ => "=",
                Operator::NE => "!=",
                Operator::LIKE => "~",
                Operator::NLIKE => "!~",
                Operator::AND => "AND",
                Operator::OR => "OR",
            }
        )
    }
}

/// Token's are literaly values and operators for the TFQL
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Operator(Operator),

    LP,
    RP,

    EOF,

    Bool(bool),
    Str(String),
    Float(f64),
    Date(DateTime<Local>),

    Invalid(String),
}

impl From<String> for Token {
    fn from(s: String) -> Token {
        Token::from(s.as_ref())
    }
}

impl From<char> for Token {
    fn from(c: char) -> Token {
        Token::from(c.to_string())
    }
}

impl From<f64> for Token {
    fn from(num: f64) -> Token {
        Token::Float(num)
    }
}

impl From<bool> for Token {
    fn from(b: bool) -> Token {
        Token::Bool(b)
    }
}

impl<'a> From<&'a str> for Token {
    fn from(s: &str) -> Token {
        match s {
            "(" => Token::LP,
            ")" => Token::RP,
            ">" => Token::Operator(Operator::GT),
            ">=" => Token::Operator(Operator::GTE),
            "<" => Token::Operator(Operator::LT),
            "<=" => Token::Operator(Operator::LTE),
            "=" => Token::Operator(Operator::EQ),
            "!=" => Token::Operator(Operator::NE),
            "^=" => Token::Operator(Operator::NE),
            "~" => Token::Operator(Operator::LIKE),
            "^" => Token::Operator(Operator::LIKE),
            "!~" => Token::Operator(Operator::NLIKE),
            "^^" => Token::Operator(Operator::NLIKE),
            "AND" | "and" => Token::Operator(Operator::AND),
            "OR" | "or" => Token::Operator(Operator::OR),
            "True" | "true" => Token::Bool(true),
            "False" | "false" => Token::Bool(false),
            "" => Token::EOF,
            _ => {
                if let Ok(num) = s.parse::<f64>() {
                    return Token::Float(num);
                }

                if let Ok(date) = Local.datetime_from_str(s, "%Y-%m-%d %r") {
                    return Token::Date(date);
                }

                Token::Str(s.to_string())
            }
        }
    }
}

impl From<DateTime<Local>> for Token {
    fn from(dte: DateTime<Local>) -> Token {
        Token::Date(dte)
    }
}

impl Into<String> for Token {
    fn into(self) -> String {
        match self {
            Token::Str(s) => format!("(String, {})", s),
            Token::Invalid(s) => format!("(Invalid, {})", s),
            Token::Date(d) => format!("(Date, {})", d),
            Token::Float(num) => format!("(Float, {})", num),
            Token::Bool(b) => format!("(Bool, {})", b),

            Token::Operator(Operator::GT) => "(GT, >)".to_string(),
            Token::Operator(Operator::LT) => "(LT, <)".to_string(),
            Token::Operator(Operator::GTE) => "(GTE, >=)".to_string(),
            Token::Operator(Operator::LTE) => "(LTE, <=)".to_string(),
            Token::Operator(Operator::EQ) => "(EQ, =)".to_string(),
            Token::Operator(Operator::NE) => "(NE, !=)".to_string(),
            Token::Operator(Operator::LIKE) => "(LIKE, ~)".to_string(),
            Token::Operator(Operator::NLIKE) => "(NLIKE, !~)".to_string(),

            Token::Operator(Operator::AND) => "(AND, AND)".to_string(),
            Token::Operator(Operator::OR) => "(OR, OR)".to_string(),

            Token::LP => "(LP, '(')".to_string(),
            Token::RP => "(RP, ')')".to_string(),

            Token::EOF => "(EOF, EOF)".to_string(),
        }
    }
}

impl fmt::Display for Token {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Token: {}", self.to_string())
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;

    macro_rules! token_tests {
        ($($name:ident: $literal:expr, $expected:expr,)*) => {
            $(
                #[test]
                fn $name() {
                    let token = Token::from($literal);
                    assert_eq!(token, $expected)
                }
            )*
        }
    }

    token_tests! {
        left_paren: "(", Token::LP,
        right_paren: ")", Token::RP,
        greater_than: ">", Token::Operator(Operator::GT),
        greater_than_or_eq: ">=", Token::Operator(Operator::GTE),
        less_than: "<", Token::Operator(Operator::LT),
        less_than_or_eq: "<=", Token::Operator(Operator::LTE),
        eq: "=", Token::Operator(Operator::EQ),
        ne_bang: "!=", Token::Operator(Operator::NE),
        ne_caret: "^=", Token::Operator(Operator::NE),
        like_tilde: "~", Token::Operator(Operator::LIKE),
        like_caret: "^", Token::Operator(Operator::LIKE),
        nlike_bang_tilde: "!~", Token::Operator(Operator::NLIKE),
        nlike_caret: "^^", Token::Operator(Operator::NLIKE),
        and_upper: "AND", Token::Operator(Operator::AND),
        and_lower: "and", Token::Operator(Operator::AND),
        or_upper: "OR", Token::Operator(Operator::OR),
        or_lower: "or", Token::Operator(Operator::OR),
        true_upper: "True",  Token::Bool(true),
        true_lower: "true",  Token::Bool(true),
        false_upper: "False",  Token::Bool(false),
        false_lower: "false",  Token::Bool(false),
        num_dot: "1.0", Token::Float(1.0),
        num_no_dot: "1", Token::Float(1.0),
        eof: "", Token::EOF,
    }
}