use chrono::{naive::NaiveDate, Duration}; use crate::transaction::{Category, Transaction}; pub struct Search<'t> { filtered: Vec, transactions: Vec<&'t Transaction>, } pub enum DateIsh { Absolute(NaiveDate), Relative(Duration), } impl DateIsh { pub fn parse(s: &str) -> Self { DateIsh::Absolute(NaiveDate::parse_from_str(s, "%Y-%m-%d").unwrap()) } } pub enum Constraint { Category(Category), Before(DateIsh, bool), After(DateIsh, bool), } impl Constraint { fn satisfies(&self, t: &Transaction) -> bool { match self { Constraint::Category(category) => category == &t.category, Constraint::Before(DateIsh::Absolute(date), inclusive) => if *inclusive { &t.date <= date } else { &t.date < date }, Constraint::After(DateIsh::Absolute(date), inclusive) => if *inclusive { &t.date >= date } else { &t.date > date } Constraint::Before(DateIsh::Relative(diff), inclusive) => if *inclusive { t.date <= chrono::offset::Local::today().naive_utc() + *diff } else { t.date < chrono::offset::Local::today().naive_utc() + *diff } Constraint::After(DateIsh::Relative(diff), inclusive) => if *inclusive { t.date >= chrono::offset::Local::today().naive_utc() + *diff } else { t.date > chrono::offset::Local::today().naive_utc() + *diff } } } } impl<'t> Search<'t> { pub fn new(transactions: Vec<&'t Transaction>) -> Self { Self { filtered: std::iter::successors(Some(0_usize), |n| Some(n.checked_add(1).unwrap())) .take(transactions.len()) .collect(), transactions, } } pub fn get(&self) -> Vec<&'t Transaction> { self .filtered .iter() .map(|idx| self.transactions[*idx]) .collect() } pub fn parse(self, rules: String) -> Self { let (sub, rules) = { if rules.chars().nth(0).unwrap() == '-' { (true, &rules[1..]) } else { (false, &rules[..]) } }; //TODO lexing // +category:a //TODO: category:"foo bar" // before:2021-01-01 => + (* -> 2020-12-31) n-incl // after:2021-01-01 => + (2021-01-01 -> *) incl // -before:2021-01-01 => - (2021-01-01 -> *) incl // -after:2021-01-01 => - (* -> 2021-12-31) n-incl //TODO: // today is 2021-01-01: // before:-1d => + (* -> 2020-12-31) n-incl // after:-1d => + (2021-01-01 -> *) incl // -before:-1d => - (2021-01-01 -> *) incl // -after:-1d => - (* -> 2020-12-31) n-incl let constraint = match rules.split_once(':').unwrap() { ("category", category) => Constraint::Category(category.to_string()), ("before", date_ish) => Constraint::Before(DateIsh::parse(date_ish), sub), ("after", date_ish) => Constraint::After(DateIsh::parse(date_ish), !sub), _ => panic!(), }; if sub { self.subtract(constraint) } else { self.filter(constraint) } } pub fn subtract(self, constraint: Constraint) -> Self { Self { filtered: self .filtered .iter() .copied() .filter(|idx| !constraint.satisfies(self.transactions[*idx])) .collect(), transactions: self.transactions, } } pub fn filter(self, constraint: Constraint) -> Self { Self { filtered: self .filtered .iter() .copied() .filter(|idx| constraint.satisfies(self.transactions[*idx])) .collect(), transactions: self.transactions, } } }