summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cli/src/main.rs27
-rw-r--r--cli/src/search.rs117
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,
+ }
+ }
}