diff options
Diffstat (limited to 'cli/src')
| -rw-r--r-- | cli/src/main.rs | 27 | ||||
| -rw-r--r-- | cli/src/search.rs | 117 |
2 files changed, 102 insertions, 42 deletions
diff --git a/cli/src/main.rs b/cli/src/main.rs index d1e793d..6b8da59 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,8 @@ -use chrono::{naive::NaiveDate, Duration}; +use chrono::naive::NaiveDate; use rust_decimal::Decimal; use std::path::PathBuf; use std::str::FromStr; +use structopt::clap::AppSettings; use structopt::StructOpt; use tabled::{Style, Table}; @@ -9,7 +10,7 @@ mod search; mod store; mod transaction; -use search::{Constraint, DateFilter, Search}; +use search::Search; use store::Store; use transaction::{Transaction, TransactionKind}; @@ -41,7 +42,10 @@ enum Command { List { target: ListTarget, }, - Show, + #[structopt(setting = AppSettings::AllowLeadingHyphen)] + Show { + filters: Vec<String>, + }, } #[derive(Debug)] @@ -71,12 +75,11 @@ struct Mn { fn main() { let mut store = Store::open(PathBuf::from("store")).unwrap(); - let search = Search::new(store.transactions()); // let search = search.subtract(Constraint::Category("a".to_string())); - let search = search.subtract(Constraint::Date(DateFilter::Relative { - start: None, - end: Some(Duration::days(-2)), - })); + // let search = search.subtract(Constraint::Date(DateFilter::Relative { + // start: None, + // end: Some(Duration::days(-2)), + // })); let args = Mn::from_args(); eprintln!("{:?}", args); @@ -111,7 +114,13 @@ fn main() { } => { println!("{}", store.categories().join("\n")); } - Command::Show => { + Command::Show { + filters + } => { + let mut search = Search::new(store.transactions()); + if !filters.is_empty() { + search = search.parse(filters.join(" ")); + } let mut transactions = search.get(); transactions.sort_by(|t1, t2| t1.date.cmp(&t2.date)); println!("{}", Table::new(transactions).with(Style::psql())); diff --git a/cli/src/search.rs b/cli/src/search.rs index 54eea67..fd7407f 100644 --- a/cli/src/search.rs +++ b/cli/src/search.rs @@ -7,50 +7,47 @@ pub struct Search<'t> { transactions: Vec<&'t Transaction>, } -pub enum DateFilter { - Absolute { - start: Option<NaiveDate>, - end: Option<NaiveDate>, - }, - Relative { - start: Option<Duration>, - end: Option<Duration>, +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), - Date(DateFilter), + Before(DateIsh, bool), + After(DateIsh, bool), } impl Constraint { fn satisfies(&self, t: &Transaction) -> bool { match self { Constraint::Category(category) => category == &t.category, - Constraint::Date(DateFilter::Relative { - start, - end, - }) => { - if let (Some(start), Some(end)) = (start, end) { - assert!(start < end); - } - let now = chrono::offset::Local::today().naive_utc(); - let start_valid = start.map(|start| t.date > now + start).unwrap_or(true); - let end_valid = end.map(|end| t.date < now + end).unwrap_or(true); - start_valid && end_valid - }, - Constraint::Date(DateFilter::Absolute { - start, - end, - }) => { - if let (Some(start), Some(end)) = (start, end) { - assert!(start < end); - } - let now = chrono::offset::Local::today().naive_utc(); - let start_valid = start.map(|start| t.date > start).unwrap_or(true); - let end_valid = end.map(|end| t.date < end).unwrap_or(true); - start_valid && end_valid + 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 + } } } } @@ -58,7 +55,9 @@ impl Constraint { 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(), + filtered: std::iter::successors(Some(0_usize), |n| Some(n.checked_add(1).unwrap())) + .take(transactions.len()) + .collect(), transactions, } } @@ -71,6 +70,46 @@ impl<'t> Search<'t> { .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 @@ -82,4 +121,16 @@ impl<'t> Search<'t> { 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, + } + } } |
