From 88bdc1410a6b5c5c7dbe3cbf882cc0c5cd60fe47 Mon Sep 17 00:00:00 2001 From: Claudio Noguera Date: Sat, 27 Feb 2021 13:34:20 +0100 Subject: [PATCH 1/6] Add cache for regex match in lists #40 --- src/list.rs | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/list.rs b/src/list.rs index b970345..95f5f44 100644 --- a/src/list.rs +++ b/src/list.rs @@ -21,13 +21,19 @@ use crate::LedgerError; pub struct List { aliases: HashMap, list: HashMap>, + matches: HashMap>, } impl<'a, T: Eq + Hash + HasName + Clone + FromDirective + HasAliases + Debug> List { pub fn new() -> Self { let aliases: HashMap = HashMap::new(); let list: HashMap> = HashMap::new(); - List { aliases, list } + let matches: HashMap> = HashMap::new(); + List { + aliases, + list, + matches, + } } /// Inserts an ```element``` in the list @@ -77,20 +83,36 @@ impl<'a, T: Eq + Hash + HasName + Clone + FromDirective + HasAliases + Debug> Li } } /// Gets an element from the regex - pub fn get_regex(&self, regex: Regex) -> Option<&Rc> { - // Try the list - for (_alias, value) in self.list.iter() { - if regex.is_match(value.get_name()) { - return Some(value); - } - } - for (alias, value) in self.aliases.iter() { - if regex.is_match(alias) { - return self.list.get(value); + pub fn get_regex(&mut self, regex: Regex) -> Option<&Rc> { + let alias = self.matches.get(regex.as_str()); + match alias { + Some(x) => match x { + Some(alias) => Some(self.get(alias).unwrap()), + None => None, + }, + None => { + // cache miss + for (_alias, value) in self.list.iter() { + if regex.is_match(value.get_name()) { + self.matches + .insert(regex.as_str().to_string(), Some(_alias.clone())); + return Some(value); + } + } + for (alias, value) in self.aliases.iter() { + if regex.is_match(alias) { + self.matches + .insert(regex.as_str().to_string(), Some(value.clone())); + return self.list.get(value); + } + } + self.matches.insert(regex.as_str().to_string(), None); + None } } + // // Try the list - None + // None } pub fn iter(&self) -> Iter<'_, String, Rc> { From 8d47e91d44f3cbc3e5bff28a041715cb0cef9117 Mon Sep 17 00:00:00 2001 From: Claudio Noguera Date: Sat, 27 Feb 2021 17:53:06 +0100 Subject: [PATCH 2/6] Made it twice as fast #40 Still not as fast as ``ledger``` though. - Little impact: cache the payee regexes - Big impact: parse the abstract syntax trees only once per expression. --- Cargo.toml | 2 +- scripts/bench.sh | 2 +- src/filter.rs | 21 +++- src/models/account.rs | 166 +++++++++++++++++++------------ src/models/mod.rs | 20 +++- src/models/payee.rs | 66 ++++++++---- src/models/transaction.rs | 14 +-- src/parser/tokenizers/account.rs | 10 +- src/parser/tokenizers/payee.rs | 6 +- src/parser/value_expr.rs | 15 +++ 10 files changed, 220 insertions(+), 102 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33e9d69..1c39493 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,4 @@ two_timer = "2.2.0" assert_cmd = "1.0.3" terminal_size = "0.1.16" pest = "2.0" -pest_derive = "2.0" +pest_derive = "2.0" \ No newline at end of file diff --git a/scripts/bench.sh b/scripts/bench.sh index 1adcc4d..8827257 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -1,6 +1,6 @@ #!/bin/sh echo "This benchmark needs ledgerrc to be properly set up for it to be meaningful" -hyperfine 'dinero bal' hyperfine 'ledger bal' +hyperfine 'dinero bal' diff --git a/src/filter.rs b/src/filter.rs index c80b5c3..a80be84 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,5 +1,5 @@ use crate::models::{Currency, Posting, PostingType, Transaction}; -use crate::parser::value_expr::{eval_expression, EvalResult}; +use crate::parser::value_expr::{eval, eval_expression, EvalResult, Node}; use crate::{CommonOpts, Error, List}; use colored::Colorize; use regex::Regex; @@ -64,6 +64,25 @@ pub fn filter_predicate( } } +pub fn filter_expression( + predicate: &Node, + posting: &Posting, + transaction: &Transaction, + commodities: &mut List, + regexes: &mut HashMap, +) -> Result { + let result = eval(predicate, posting, transaction, commodities, regexes); + match result { + EvalResult::Boolean(b) => Ok(b), + _ => Err(Error { + message: vec![ + format!("{:?}", predicate).red().bold(), + "should return a boolean".normal(), + ], + }), + } +} + /// Create search expression from Strings /// /// The command line arguments provide syntactic sugar which save time when querying the journal. diff --git a/src/models/account.rs b/src/models/account.rs index 67118b9..9af35a9 100644 --- a/src/models/account.rs +++ b/src/models/account.rs @@ -1,24 +1,117 @@ use crate::models::{FromDirective, HasAliases, HasName, Origin}; use regex::Regex; +use std::cell::RefCell; use std::collections::hash_map::RandomState; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fmt; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; #[derive(Debug, Clone)] pub struct Account { - pub(crate) name: String, - pub(crate) origin: Origin, - pub(crate) note: Option, - pub(crate) isin: Option, - pub(crate) aliases: HashSet, - pub(crate) check: Vec, - pub(crate) assert: Vec, - pub(crate) payee: Vec, - pub(crate) default: bool, + name: String, + origin: Origin, + note: Option, + isin: Option, + aliases: HashSet, + check: Vec, + assert: Vec, + payee: Vec, + default: bool, + matches: RefCell>, } +impl Account { + pub fn new( + name: String, + origin: Origin, + note: Option, + isin: Option, + aliases: HashSet, + check: Vec, + assert: Vec, + payee: Vec, + default: bool, + ) -> Account { + Account { + name, + origin, + note, + isin, + aliases, + check, + assert, + payee, + default, + matches: RefCell::new(HashMap::new()), + } + } + pub fn is_default(&self) -> bool { + self.default + } + pub fn payees(&self) -> &Vec { + &self.payee + } + + /// Depth of the account, useful for filters and other + pub fn depth(&self) -> usize { + self.name + .chars() + .filter(|c| *c == ':') + .collect::>() + .len() + + 1 + } + + /// Parent name + /// + /// Returns the name of the parent account should have + /// ```rust + /// use dinero::models::Account; + /// let mut account = Account::from("Expenses:Groceries"); + /// let mut parent = account.parent_name(); + /// assert_eq!(parent, Some("Expenses".to_string())); + /// + /// account = Account::from("Expenses:Groceries:Supermarket"); + /// parent = account.parent_name(); + /// assert_eq!(parent, Some("Expenses:Groceries".to_string())); + /// + /// account = Account::from("Expenses"); + /// parent = account.parent_name(); + /// assert_eq!(parent, None); + /// ``` + pub fn parent_name(&self) -> Option { + match self.depth() { + 1 => None, + _ => { + let split = self.name.split(":").collect::>(); + match split.split_last() { + None => panic!("Could not get parent of {}", self.name), + Some((_, elements)) => Some( + elements + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(":"), + ), + } + } + } + } + + pub fn is_match(&self, regex: Regex) -> bool { + let mut list = self.matches.borrow_mut(); + match list.get(regex.as_str()) { + Some(x) => *x, + + None => { + let value = regex.is_match(self.get_name()); + list.insert(regex.as_str().to_string(), value); + value + } + } + } +} impl Display for Account { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name) @@ -57,6 +150,7 @@ impl From<&str> for Account { assert: vec![], payee: vec![], default: false, + matches: RefCell::new(HashMap::new()), } } } @@ -75,55 +169,3 @@ impl HasName for Account { self.name.as_str() } } - -impl Account { - /// Depth of the account, useful for filters and other - pub fn depth(&self) -> usize { - self.name - .chars() - .filter(|c| *c == ':') - .collect::>() - .len() - + 1 - } - - /// Parent name - /// - /// Returns the name of the parent account should have - /// ```rust - /// use dinero::models::Account; - /// let mut account = Account::from("Expenses:Groceries"); - /// let mut parent = account.parent_name(); - /// assert_eq!(parent, Some("Expenses".to_string())); - /// - /// account = Account::from("Expenses:Groceries:Supermarket"); - /// parent = account.parent_name(); - /// assert_eq!(parent, Some("Expenses:Groceries".to_string())); - /// - /// account = Account::from("Expenses"); - /// parent = account.parent_name(); - /// assert_eq!(parent, None); - /// ``` - pub fn parent_name(&self) -> Option { - match self.depth() { - 1 => None, - _ => { - let split = self.name.split(":").collect::>(); - match split.split_last() { - None => panic!("Could not get parent of {}", self.name), - Some((_, elements)) => Some( - elements - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(":"), - ), - } - } - } - } - - pub fn is_match(&self, regex: Regex) -> bool { - regex.is_match(self.get_name()) - } -} diff --git a/src/models/mod.rs b/src/models/mod.rs index 6224932..67bf9db 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -14,6 +14,8 @@ pub use transaction::{ Cleared, Posting, PostingType, Transaction, TransactionStatus, TransactionType, }; +use crate::filter::filter_expression; +use crate::parser::value_expr::build_root_node_from_expression; use crate::parser::ParsedLedger; use crate::parser::{tokenizers, value_expr}; use crate::{filter::filter_predicate, models::transaction::Cost}; @@ -101,7 +103,7 @@ impl ParsedLedger { let mut alias_to_add = "".to_string(); let mut payee_to_add = None; 'outer: for (_, p) in payees_copy.iter() { - for p_alias in p.alias_regex.iter() { + for p_alias in p.get_aliases().iter() { // println!("{:?}", p_alias); // todo delete if p_alias.is_match(alias.as_str()) { // self.payees.add_alias(alias.to_string(), p); @@ -189,16 +191,26 @@ impl ParsedLedger { // 5. Go over the transactions again and see if there is something we need to do with them if automated_transactions.len() > 0 { + // Build a cache of abstract value trees, it takes time to parse expressions, so better do it only once + let mut root_nodes = HashMap::new(); let mut regexes = HashMap::new(); + for automated in automated_transactions.iter_mut() { + let query = automated.get_filter_query(); + let node = build_root_node_from_expression(query.as_str(), &mut regexes); + root_nodes.insert(query, node); + } + for t in transactions.iter_mut() { for automated in automated_transactions.iter_mut() { let mut extra_postings = vec![]; let mut extra_virtual_postings = vec![]; let mut extra_virtual_postings_balance = vec![]; let mut matched = false; + for p in t.postings_iter() { - if filter_predicate( - automated.get_filter_query().as_str(), + let node = root_nodes.get(automated.get_filter_query().as_str()); + if filter_expression( + node.unwrap(), // automated.get_filter_query().as_str(), p, t, &mut self.commodities, @@ -366,7 +378,7 @@ impl ParsedLedger { let account = if p.account.to_lowercase().ends_with("unknown") { let mut account = None; for (_, acc) in self.accounts.iter() { - for alias in acc.payee.iter() { + for alias in acc.payees().iter() { if alias.is_match(payee.get_name()) { account = Some(acc.clone()); break; diff --git a/src/models/payee.rs b/src/models/payee.rs index cad17a1..6f6e25e 100644 --- a/src/models/payee.rs +++ b/src/models/payee.rs @@ -1,20 +1,55 @@ use crate::models::{FromDirective, HasAliases, HasName, Origin}; use regex::Regex; +use std::cell::RefCell; use std::collections::hash_map::RandomState; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fmt; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; #[derive(Debug, Clone)] pub struct Payee { - pub name: String, - pub note: Option, - pub alias: HashSet, - pub alias_regex: Vec, - pub(crate) origin: Origin, + name: String, + note: Option, + alias: HashSet, + alias_regex: Vec, + origin: Origin, + matches: RefCell>, } +impl Payee { + pub fn new( + name: String, + note: Option, + alias: HashSet, + alias_regex: Vec, + origin: Origin, + ) -> Payee { + Payee { + name, + note, + alias, + alias_regex, + origin, + matches: RefCell::new(HashMap::new()), + } + } + pub fn is_match(&self, regex: Regex) -> bool { + let mut list = self.matches.borrow_mut(); + match list.get(regex.as_str()) { + Some(x) => *x, + + None => { + let value = regex.is_match(self.get_name()); + list.insert(regex.as_str().to_string(), value); + value + } + } + } + pub fn get_aliases(&self) -> &Vec { + &self.alias_regex + } +} impl Eq for Payee {} impl PartialEq for Payee { @@ -55,20 +90,15 @@ impl Hash for Payee { self.name.hash(state); } } -impl Payee { - pub fn is_match(&self, regex: Regex) -> bool { - regex.is_match(self.get_name()) - } -} impl From<&str> for Payee { fn from(name: &str) -> Self { - Payee { - name: String::from(name), - note: None, - alias: Default::default(), - alias_regex: Default::default(), - origin: Origin::FromTransaction, - } + Payee::new( + String::from(name), + None, + Default::default(), + Default::default(), + Origin::FromTransaction, + ) } } diff --git a/src/models/transaction.rs b/src/models/transaction.rs index 4f3f6e2..47c6982 100644 --- a/src/models/transaction.rs +++ b/src/models/transaction.rs @@ -71,13 +71,13 @@ impl Transaction { match payees.get(&self.description) { Ok(x) => x.clone(), Err(_) => { - let payee = Payee { - name: self.description.clone(), - note: None, - alias: Default::default(), - alias_regex: vec![], - origin: Origin::FromTransaction, - }; + let payee = Payee::new( + self.description.clone(), + None, + Default::default(), + vec![], + Origin::FromTransaction, + ); payees.insert(payee); self.get_payee(payees) } diff --git a/src/parser/tokenizers/account.rs b/src/parser/tokenizers/account.rs index a6f0ec1..90119d0 100644 --- a/src/parser/tokenizers/account.rs +++ b/src/parser/tokenizers/account.rs @@ -81,9 +81,9 @@ pub(crate) fn parse(tokenizer: &mut Tokenizer) -> Result { }, } } - let account = Account { + let account = Account::new( name, - origin: Origin::FromDirective, + Origin::FromDirective, note, isin, aliases, @@ -91,7 +91,7 @@ pub(crate) fn parse(tokenizer: &mut Tokenizer) -> Result { assert, payee, default, - }; + ); Ok(account) } @@ -122,7 +122,7 @@ mod tests { .to_string(), ); let account = parse(&mut tokenizer).unwrap(); - assert!(!account.default, "Not a default account"); + assert!(!account.is_default(), "Not a default account"); assert_eq!(account.get_name(), "Assets:Checking account"); } @@ -135,7 +135,7 @@ mod tests { .to_string(), ); let account = parse(&mut tokenizer).unwrap(); - assert!(!account.default, "Not a default account"); + assert!(!account.is_default(), "Not a default account"); assert_eq!(account.get_name(), "Assets:MyAccount"); let mut accounts = List::::new(); diff --git a/src/parser/tokenizers/payee.rs b/src/parser/tokenizers/payee.rs index ee84a29..a0c0ff0 100644 --- a/src/parser/tokenizers/payee.rs +++ b/src/parser/tokenizers/payee.rs @@ -74,13 +74,13 @@ pub(crate) fn parse(tokenizer: &mut Tokenizer) -> Result { .iter() .map(|x| Regex::new(x.clone().as_str()).unwrap()) .collect(); - Ok(Payee { + Ok(Payee::new( name, note, alias, alias_regex, - origin: Origin::FromDirective, - }) + Origin::FromDirective, + )) } #[cfg(test)] diff --git a/src/parser/value_expr.rs b/src/parser/value_expr.rs index 74fda3f..9b6b425 100644 --- a/src/parser/value_expr.rs +++ b/src/parser/value_expr.rs @@ -4,12 +4,27 @@ use crate::app; use crate::models::{Account, Currency, Money, Payee, Posting, Transaction}; use crate::List; use chrono::NaiveDate; + use num::{abs, BigRational}; use pest::Parser; use regex::Regex; use std::collections::HashMap; use std::rc::Rc; +pub fn build_root_node_from_expression( + expression: &str, + regexes: &mut HashMap, +) -> Node { + let parsed = GrammarParser::parse(Rule::value_expr, expression) + .expect("unsuccessful parse") // unwrap the parse result + .next() + .unwrap() + .into_inner() + .next() + .unwrap(); + // Build the abstract syntax tree + build_ast_from_expr(parsed, regexes) +} pub fn eval_expression( expression: &str, posting: &Posting, From c6ba11eaeadf81457c8dc6806dda3ecf5bfdbd85 Mon Sep 17 00:00:00 2001 From: Claudio Noguera Date: Sat, 27 Feb 2021 17:56:22 +0100 Subject: [PATCH 3/6] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0af90fb..7bc7035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog Changelog file for dinero-rs project, a command line application for managing finances. +## [0.14.0] +### Fixed +- speed bump, from 7 seconds to 4 seconds in my personal ledger (still room to improve) ## [0.13.1] - 2021-02-27 ### Fixed - Fixed issue when there is no specified payee From ad7fc29942997207262c39544e75590af62e2a0b Mon Sep 17 00:00:00 2001 From: Claudio Noguera Date: Sat, 27 Feb 2021 18:15:28 +0100 Subject: [PATCH 4/6] add test that shows the failure #42 --- CHANGELOG.md | 1 + examples/automated.ledger | 1 + tests/test_commands.rs | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc7035..177e093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog file for dinero-rs project, a command line application for managing fi ## [0.14.0] ### Fixed - speed bump, from 7 seconds to 4 seconds in my personal ledger (still room to improve) +- ability to add tags from automated transactions ## [0.13.1] - 2021-02-27 ### Fixed - Fixed issue when there is no specified payee diff --git a/examples/automated.ledger b/examples/automated.ledger index 6773f5f..c51331d 100644 --- a/examples/automated.ledger +++ b/examples/automated.ledger @@ -19,5 +19,6 @@ Expenses:Rent 705.43 EUR Assets:Checking account 2021-01-06 * Meal | My favorite restaurant + ; :one_tag: Expenses:Restaurants 58.33 EUR Assets:Cash diff --git a/tests/test_commands.rs b/tests/test_commands.rs index 3db772d..b112b23 100644 --- a/tests/test_commands.rs +++ b/tests/test_commands.rs @@ -203,3 +203,13 @@ fn automated_value_expression() { test_args(args); } + +#[test] +fn automated_add_tag() { + let args = &["reg", "-f", "examples/automated.ledger", "%yummy"]; + let assert_1 = Command::cargo_bin("dinero").unwrap().args(args).assert(); + let output = String::from_utf8(assert_1.get_output().to_owned().stdout).unwrap(); + assert_eq!(output.lines().into_iter().count(), 2); + + test_args(args); +} From 6cdc324729283e95d53e7fa7071aa869aeb72b45 Mon Sep 17 00:00:00 2001 From: Claudio Noguera Date: Sat, 27 Feb 2021 18:32:45 +0100 Subject: [PATCH 5/6] fix #42 --- src/models/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/models/mod.rs b/src/models/mod.rs index 67bf9db..0217597 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -207,6 +207,12 @@ impl ParsedLedger { let mut extra_virtual_postings_balance = vec![]; let mut matched = false; + for comment in automated.comments.iter() { + t.tags.append(&mut comment.get_tags()); + for p in t.postings.iter_mut() { + p.tags.append(&mut comment.get_tags()); + } + } for p in t.postings_iter() { let node = root_nodes.get(automated.get_filter_query().as_str()); if filter_expression( @@ -217,12 +223,7 @@ impl ParsedLedger { &mut regexes, )? { matched = true; - for comment in t.comments.iter() { - p.to_owned().tags.append(&mut comment.get_tags()); - } - for comment in p.comments.iter() { - p.to_owned().tags.append(&mut comment.get_tags()); - } + for auto_posting in automated.postings_iter() { let account_alias = auto_posting.account.clone(); match self.accounts.get(&account_alias) { @@ -292,7 +293,6 @@ impl ParsedLedger { } } } - // todo!("Need to work on transaction automation"); } } t.postings.append(&mut extra_postings); From 1f17a5c1aeb5e7f6738225d6585dc18bf780bc4d Mon Sep 17 00:00:00 2001 From: Claudio Noguera Date: Sat, 27 Feb 2021 19:18:08 +0100 Subject: [PATCH 6/6] more caching #41, #40 --- src/commands/balance.rs | 15 ++++++++++++++- src/commands/register.rs | 17 ++++++++++++++++- src/filter.rs | 31 ++++--------------------------- src/models/mod.rs | 2 +- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/commands/balance.rs b/src/commands/balance.rs index 5eeb17d..e0ce5dc 100644 --- a/src/commands/balance.rs +++ b/src/commands/balance.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use colored::Colorize; use crate::models::{conversion, Account, Balance, Currency, HasName, Money}; +use crate::parser::value_expr::build_root_node_from_expression; use crate::parser::Tokenizer; use crate::Error; use crate::{filter, CommonOpts}; @@ -24,9 +25,21 @@ pub fn execute(options: &CommonOpts, flat: bool, show_total: bool) -> Result<(), let mut balances: HashMap, Balance> = HashMap::new(); + // Build a cache of abstract value trees, it takes time to parse expressions, so better do it only once + let mut regexes = HashMap::new(); + let query = filter::preprocess_query(&options.query); + let node = if query.len() > 2 { + Some(build_root_node_from_expression( + query.as_str(), + &mut regexes, + )) + } else { + None + }; + for t in ledger.transactions.iter() { for p in t.postings_iter() { - if !filter::filter(&options, t, p, &mut ledger.commodities)? { + if !filter::filter(&options, &node, t, p, &mut ledger.commodities)? { continue; } let mut cur_bal = balances diff --git a/src/commands/register.rs b/src/commands/register.rs index e9bb350..68afe67 100644 --- a/src/commands/register.rs +++ b/src/commands/register.rs @@ -1,8 +1,10 @@ use crate::models::{Balance, Money}; +use crate::parser::value_expr::build_root_node_from_expression; use crate::parser::Tokenizer; use crate::Error; use crate::{filter, CommonOpts}; use colored::Colorize; +use std::collections::HashMap; use terminal_size::{terminal_size, Width}; /// Register report @@ -33,10 +35,23 @@ pub fn execute(options: &CommonOpts) -> Result<(), Error> { } else { width - w_date - w_description - w_amount - w_balance }; + + // Build a cache of abstract value trees, it takes time to parse expressions, so better do it only once + let mut regexes = HashMap::new(); + let query = filter::preprocess_query(&options.query); + let node = if query.len() > 2 { + Some(build_root_node_from_expression( + query.as_str(), + &mut regexes, + )) + } else { + None + }; + for t in ledger.transactions.iter() { let mut counter = 0; for p in t.postings_iter() { - if !filter::filter(&options, t, p, &mut ledger.commodities)? { + if !filter::filter(&options, &node, t, p, &mut ledger.commodities)? { continue; } counter += 1; diff --git a/src/filter.rs b/src/filter.rs index a80be84..366971b 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -7,12 +7,12 @@ use std::collections::HashMap; pub fn filter( options: &CommonOpts, + node: &Option, transaction: &Transaction, posting: &Posting, commodities: &mut List, ) -> Result { // Get what's needed - let predicate = preprocess_query(&options.query); let real = options.real; // Check for real postings @@ -35,32 +35,9 @@ pub fn filter( return Ok(false); } } - - filter_predicate( - predicate.as_str(), - posting, - transaction, - commodities, - &mut HashMap::new(), - ) -} - -pub fn filter_predicate( - predicate: &str, - posting: &Posting, - transaction: &Transaction, - commodities: &mut List, - regexes: &mut HashMap, -) -> Result { - if (predicate.len() == 0) | (predicate == "()") { - return Ok(true); - } - let result = eval_expression(predicate, posting, transaction, commodities, regexes); - match result { - EvalResult::Boolean(b) => Ok(b), - _ => Err(Error { - message: vec![predicate.red().bold(), "should return a boolean".normal()], - }), + match node { + Some(x) => filter_expression(x, posting, transaction, commodities, &mut HashMap::new()), + None => Ok(true), } } diff --git a/src/models/mod.rs b/src/models/mod.rs index 0217597..8cf71f0 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -15,10 +15,10 @@ pub use transaction::{ }; use crate::filter::filter_expression; +use crate::models::transaction::Cost; use crate::parser::value_expr::build_root_node_from_expression; use crate::parser::ParsedLedger; use crate::parser::{tokenizers, value_expr}; -use crate::{filter::filter_predicate, models::transaction::Cost}; use crate::{Error, List}; use num::BigInt; use std::rc::Rc;