Skip to content

Commit

Permalink
Support date filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
dantleech committed Nov 11, 2024
1 parent ab866ab commit d8492a6
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type = "Run" and title ~ "Park"
- `title`: Activity title
- `elevation`: Elevation (in meters)
- `time`: Time (in seconds, 3600 = 1 hour)
- `date`: Date (YYYY-MM-DD)

### Operators

Expand Down
42 changes: 26 additions & 16 deletions src/expr/evaluator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::HashMap;

use super::parser::{Expr, Parser};
use chrono::NaiveDate;

use super::{parser::{Expr, Parser}, lexer::TokenKind};

pub type Vars = HashMap<String, Evalue>;

Expand All @@ -11,18 +13,21 @@ pub enum Evalue {
String(String),
Number(f64),
Bool(bool),
Date(NaiveDate),
}
impl Evalue {
fn to_bool(&self) -> bool {
match self {
Evalue::String(v) => v != "" && v != "0",
Evalue::Number(n) => *n != 0.0,
Evalue::Bool(b) => *b,
Evalue::Date(_) => true,
}
}

fn to_string(&self) -> String {
match self {
Evalue::Date(d) => d.to_string(),
Evalue::String(v) => v.clone(),
Evalue::Number(n) => format!("{}", *n),
Evalue::Bool(b) => match b {
Expand All @@ -49,7 +54,7 @@ impl Evaluator {

pub fn evaluate(&self, expr: &Expr, vars: &Vars) -> Result<bool, String> {
match self.evaluate_expr(&expr, vars)? {
Evalue::String(_) | Evalue::Number(_) => {
Evalue::Date(_) | Evalue::String(_) | Evalue::Number(_) => {
Err(format!("expression must evluate to a boolean, got: {:?}", expr).to_string())
}
Evalue::Bool(b) => Ok(b),
Expand All @@ -58,27 +63,28 @@ impl Evaluator {

fn evaluate_expr(&self, expr: &super::parser::Expr, vars: &Vars) -> Result<Evalue, String> {
match expr {
super::parser::Expr::Boolean(b) => Ok(Evalue::Bool(*b)),
super::parser::Expr::String(s) => Ok(Evalue::String(s.clone())),
super::parser::Expr::Binary(lexpr, op, rexpr) => {
Expr::Boolean(b) => Ok(Evalue::Bool(*b)),
Expr::String(s) => Ok(Evalue::String(s.clone())),
Expr::Date(s) => Ok(Evalue::Date(s.clone())),
Expr::Binary(lexpr, op, rexpr) => {
let lval = self.evaluate_expr(lexpr, vars)?;
let rval = self.evaluate_expr(rexpr, vars)?;
let eval = match op {
super::lexer::TokenKind::GreaterThan => Ok(lval > rval),
super::lexer::TokenKind::GreaterThanEqual => Ok(lval >= rval),
super::lexer::TokenKind::LessThanEqual => Ok(lval <= rval),
super::lexer::TokenKind::LessThan => Ok(lval < rval),
super::lexer::TokenKind::Equal => Ok(lval == rval),
super::lexer::TokenKind::FuzzyEqual => Ok(lval.to_string().contains(rval.to_string().as_str())),
super::lexer::TokenKind::NotEqual => Ok(lval != rval),
super::lexer::TokenKind::NotFuzzyEqual => Ok(!lval.to_string().contains(rval.to_string().as_str())),
super::lexer::TokenKind::Or => Ok(lval.to_bool() || rval.to_bool()),
super::lexer::TokenKind::And => Ok(lval.to_bool() && rval.to_bool()),
TokenKind::GreaterThan => Ok(lval > rval),
TokenKind::GreaterThanEqual => Ok(lval >= rval),
TokenKind::LessThanEqual => Ok(lval <= rval),
TokenKind::LessThan => Ok(lval < rval),
TokenKind::Equal => Ok(lval == rval),
TokenKind::FuzzyEqual => Ok(lval.to_string().contains(rval.to_string().as_str())),
TokenKind::NotEqual => Ok(lval != rval),
TokenKind::NotFuzzyEqual => Ok(!lval.to_string().contains(rval.to_string().as_str())),
TokenKind::Or => Ok(lval.to_bool() || rval.to_bool()),
TokenKind::And => Ok(lval.to_bool() && rval.to_bool()),
_ => Err(format!("unknown operator: {:?}", op)),
}?;
Ok(Evalue::Bool(eval))
}
super::parser::Expr::Number(n) => Ok(Evalue::Number(*n)),
Expr::Number(n) => Ok(Evalue::Number(*n)),
super::parser::Expr::Variable(v) => match vars.get(v) {
Some(v) => Ok(v.clone()),
None => Err(format!("Unknown variable `{}`", v)),
Expand Down Expand Up @@ -124,5 +130,9 @@ mod test {
assert_eq!(true, result.unwrap());
let result = Evaluator::new().parse_and_evaluate("type != 'Run'", &map);
assert_eq!(false, result.unwrap());
let result = Evaluator::new().parse_and_evaluate("2024-01-06 > 2020-01-06", &map);
assert_eq!(true, result.unwrap());
let result = Evaluator::new().parse_and_evaluate("2024-01-06 < 2020-01-06", &map);
assert_eq!(false, result.unwrap());
}
}
23 changes: 20 additions & 3 deletions src/expr/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
pub enum TokenKind {
True,
String,
Date,
False,
Number,
Contains,
Expand Down Expand Up @@ -63,7 +64,7 @@ impl Lexer<'_> {
'\0' => self.spawn_token(TokenKind::Eol, self.pos),
_ => {
if is_number(c) {
return self.parse_number();
return self.parse_number_or_date();
}

if is_name(c) {
Expand Down Expand Up @@ -114,15 +115,26 @@ impl Lexer<'_> {
}
}

fn parse_number(&mut self) -> Token {
fn parse_number_or_date(&mut self) -> Token {
if self.peek(4) == '-' {
return self.parse_date();
}
let start = self.pos;
while is_number(self.current()) {
while is_number(self.current()) || self.current() == '-' {
self.advance()
}

self.spawn_token(TokenKind::Number, start)
}

fn parse_date(&mut self) -> Token {
let start = self.pos;
while is_number(self.current()) || self.current() == '-' {
self.advance()
}
self.spawn_token(TokenKind::Date, start)
}

fn parse_name(&mut self) -> Token {
let mut length = 0;
while is_name(self.peek(length)) {
Expand Down Expand Up @@ -252,6 +264,11 @@ mod test {
assert_eq!(TokenKind::Unkown, Lexer::new("' ").next().kind);
}

#[test]
pub fn lex_date() {
assert_eq!(TokenKind::Date, Lexer::new("2024-01-01").next().kind);
}

#[test]
pub fn lex_expression() {
let mut l = Lexer::new("distance > 10m");
Expand Down
19 changes: 18 additions & 1 deletion src/expr/parser.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::str::FromStr;

use chrono::NaiveDate;

use super::lexer::{Lexer, Token, TokenKind};

#[derive(PartialEq, Debug, Clone)]
Expand All @@ -7,6 +11,7 @@ pub enum Expr {
Variable(String),
Boolean(bool),
String(String),
Date(NaiveDate),
}

pub struct Parser<'a> {
Expand Down Expand Up @@ -40,7 +45,14 @@ impl Parser<'_> {
let value = self.lexer.token_value(&token);
Ok(Expr::Variable(value.to_string()))
}
_ => Err(format!("unknown left token: {:?} at {}", token.kind, token.start)),
TokenKind::Date => match NaiveDate::from_str(self.lexer.token_value(&token)) {
Ok(d) => Ok(Expr::Date(d)),
Err(_) => Err("Could not parse date".to_string()),
},
_ => Err(format!(
"unknown left token: {:?} at {}",
token.kind, token.start
)),
}?;

let mut next_t = self.lexer.next();
Expand Down Expand Up @@ -128,5 +140,10 @@ mod test {
),
Parser::new("variable > 20 and 10 < 30").parse().unwrap()
);
assert_eq!(Expr::Number(10.0), Parser::new("10").parse().unwrap());
assert_eq!(
Expr::Date(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
Parser::new("2024-01-01").parse().unwrap(),
);
}
}
7 changes: 6 additions & 1 deletion src/store/activity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{cmp::Ordering, fmt::Display};

use chrono::NaiveDateTime;
use chrono::{NaiveDateTime, NaiveDate};
use crossterm::event::KeyCode;
use geo_types::LineString;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -216,6 +216,11 @@ impl Activities {
("title".to_string(), Evalue::String(a.title.clone())),
("elevation".to_string(), Evalue::Number(a.total_elevation_gain)),
("time".to_string(), Evalue::Number(a.moving_time as f64)),
("date".to_string(), Evalue::Date(
a.start_date.unwrap_or(
NaiveDateTime::default()
).try_into().unwrap()
)),
])) {
Ok(v) => v,
Err(_) => false,
Expand Down

0 comments on commit d8492a6

Please sign in to comment.