Skip to content

Commit

Permalink
Merge pull request #48 from frosklis/fix_44
Browse files Browse the repository at this point in the history
Fix 44. Correct currency conversion.
  • Loading branch information
frosklis authored Feb 28, 2021
2 parents 5fa1761 + 2471fba commit 6c8cb75
Show file tree
Hide file tree
Showing 27 changed files with 272 additions and 131 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Cargo.lock
.idea
.vscode
examples/personal.ledger
personal.ledger
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Changelog
Changelog file for dinero-rs project, a command line application for managing finances.

## [0.14.0]
## [0.15.0] -
### Added
- complete transaction grammar
## [0.14.0] - 2021-02-27
### Fixed
- speed bump, from 7 seconds to 4 seconds in my personal ledger (still room to improve)
- ability to add tags from automated transactions
Expand Down
15 changes: 15 additions & 0 deletions scripts/compare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

cd $DIR

ledger commodities | sort > commodities_ledger.txt &
dinero commodities | sort > commodities_dinero.txt &

ledger payees | sort > payees_ledger.txt &
dinero payees | sort > payees_dinero.txt &

ledger bal stockplan -X eur > bal_stockplan_ledger.txt &
dinero bal stockplan -X eur > bal_stockplan_dinero.txt &

12 changes: 6 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,9 @@ mod tests {
"testing",
"bal",
"-f",
"examples/demo.ledger",
"tests/example_files/demo.ledger",
"--init-file",
"examples/example_ledgerrc",
"tests/example_files/example_ledgerrc",
"--real",
]
.iter()
Expand All @@ -326,14 +326,14 @@ mod tests {

#[test]
#[should_panic(
expected = "Bad config file \"examples/example_bad_ledgerrc\"\nThis line should be a comment but isn\'t, it is bad on purpose."
expected = "Bad config file \"tests/example_files/example_bad_ledgerrc\"\nThis line should be a comment but isn\'t, it is bad on purpose."
)]
fn bad_ledgerrc() {
let args: Vec<String> = vec![
"testing",
"bal",
"--init-file",
"examples/example_bad_ledgerrc",
"tests/example_files/example_bad_ledgerrc",
]
.iter()
.map(|x| x.to_string())
Expand All @@ -342,14 +342,14 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Bad config file \"examples/example_bad_ledgerrc2\"\n- This does not parse either. And it shouldn't."
expected = "Bad config file \"tests/example_files/example_bad_ledgerrc2\"\n- This does not parse either. And it shouldn't."
)]
fn other_bad_ledgerrc() {
let args: Vec<String> = vec![
"testing",
"bal",
"--init-file",
"examples/example_bad_ledgerrc2",
"tests/example_files/example_bad_ledgerrc2",
]
.iter()
.map(|x| x.to_string())
Expand Down
9 changes: 8 additions & 1 deletion src/commands/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,20 @@ pub fn execute(options: &CommonOpts, flat: bool, show_total: bool) -> Result<(),
vec_balances.sort_by(|a, b| a.0.cmp(b.0));
let num_bal = vec_balances.len();
let mut index = 0;
let mut showed_balances = 0;
while index < num_bal {
let (account, bal) = &vec_balances[index];
if let Some(depth) = depth {
if account.split(":").count() > depth {
index += 1;
continue;
}
}
if bal.is_zero() {
index += 1;
continue;
}
showed_balances += 1;

let mut first = true;
for (_, money) in bal.balance.iter() {
Expand Down Expand Up @@ -186,7 +193,7 @@ pub fn execute(options: &CommonOpts, flat: bool, show_total: bool) -> Result<(),
}

// Print the total
if show_total & (vec_balances.len() > 1) {
if show_total & (showed_balances > 1) {
// Calculate it
let mut total_balance = balances
.iter()
Expand Down
2 changes: 1 addition & 1 deletion src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::models::{Currency, Posting, PostingType, Transaction};
use crate::parser::value_expr::{eval, eval_expression, EvalResult, Node};
use crate::parser::value_expr::{eval, EvalResult, Node};
use crate::{CommonOpts, Error, List};
use colored::Colorize;
use regex::Regex;
Expand Down
17 changes: 13 additions & 4 deletions src/grammar/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
//

// Directives
directive = { ( price ) ~ end }
directive = { price | commodity }
price = {"P" ~ ws* ~ date ~ (ws ~ time)? ~ ws+ ~ currency ~ ws* ~number ~ ws* ~ currency ~ws* ~ comment? ~ end}
transaction = {date ~ (ws ~ time)? ~ ws* ~ status? ~ ws* ~ status? ~ ws* ~ description ~ ws* ~ ("|" ~ ws*~payee)? ~ws* ~ comment? ~ end }
commodity = { "commodity" ~ ws+ ~ currency ~ ws* ~ comment? ~ end}
transaction = {
date ~ (ws ~ time)? ~ // date
("=" ~ date ~ (ws ~ time)?)? ~ // effective_date
ws+ ~ status? ~ // status
ws* ~ code? // code
~ ws* ~ description // description
~ ws* ~ ("|" ~ ws*~payee)? // payee
~ws* ~ comment? // comment
~ end }

code = { "(" ~ string ~ ")" }
status = { "*"| "!" }
Expand Down Expand Up @@ -56,8 +65,8 @@ string = {
("\"" ~ (("\\\"") | (!"\"" ~ ANY))* ~ "\"") |
("'" ~ (("\\'") | (!"'" ~ ANY))* ~ "'")
}
reserved = _{"+" | "*" | "/" | "\\" | "|" | "%" | "&" | "<" | ">" | ":" | "?" | "(" | ")" | ";"}
unquoted = { !reserved ~ !"=" ~ !"-" ~
reserved = _{"+" | "*" | "/" | "\\" | "|" | "%" | "<" | ">" | ":" | "?" | "(" | ")" | ";"}
unquoted = { !reserved ~ !"=" ~ !"-" ~ !"&" ~
(!reserved ~ !SEPARATOR ~ ANY)+ }
variable = {
"account" |
Expand Down
9 changes: 9 additions & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ pub struct Ledger {
pub(crate) payees: List<Payee>,
}

impl Ledger {
pub fn get_commodities(&self) -> &List<Currency> {
&self.commodities
}
pub fn get_prices(&self) -> &Vec<Price> {
&self.prices
}
}

impl ParsedLedger {
/// Creates a proper ledger from a parsed ledger
pub fn to_ledger(mut self, no_checks: bool) -> Result<Ledger, Error> {
Expand Down
103 changes: 76 additions & 27 deletions src/models/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use chrono::{Duration, NaiveDate};
use num::rational::BigRational;
use num::BigInt;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
Expand Down Expand Up @@ -62,26 +62,38 @@ pub fn conversion(

// Initialize distances
for node in graph.nodes.iter() {
// println!("{} {} ", node.currency.get_name(), node.date);
if node.currency == currency {
distances.insert(node.clone(), Some(date - node.date));
// distances.insert(node.clone(), Some(date - date));
// println!("{}", date - node.date);
} else {
distances.insert(node.clone(), None);
// println!("None");
}
queue.push(node.clone());
}
while !queue.is_empty() {
// Sort largest to smallest
queue.sort_by(|a, b| cmp(distances.get(b).unwrap(), distances.get(a).unwrap()));
let v = queue.pop().unwrap();

// Take the closest node
let v = queue.pop().unwrap();
// This means there is no path to the node
if distances.get(v.as_ref()).unwrap().is_none() {
break;
}

// The path from the starting currency to the node
let current_path = if let Some(path) = paths.get(v.as_ref()) {
path.clone()
} else {
Vec::new()
};

// Update the distances
for (u, e) in graph.get_neighbours(v.as_ref()).iter() {
// println!("Neighbour: {} {}", u.currency.get_name(), u.date);
let alt = distances.get(v.as_ref()).unwrap().unwrap() + e.length();
let distance = distances.get(u.as_ref()).unwrap();
let mut update = distance.is_none();
Expand All @@ -97,18 +109,36 @@ pub fn conversion(
paths.insert(u.clone(), u_path);
}
}

// Return not the paths but the multipliers
let mut multipliers = HashMap::new();
let mut inserted = HashMap::new();
for (k, v) in paths.iter() {
// println!("{} {} ~{:?}", k.currency.get_name(), k.date, v.len());
let mut mult = BigRational::new(BigInt::from(1), BigInt::from(1));
let mut currency = k.currency.clone();
match inserted.get(&k.currency) {
Some(x) => {
if *x > k.date {
continue;
}
}
None => {
inserted.insert(currency.clone(), k.date);
}
}
for edge in v.iter().rev() {
if currency == edge.from.currency {
mult *= edge.price.get_price().get_amount();
currency = edge.to.currency.clone();
} else {
mult /= edge.price.get_price().get_amount();
currency = edge.from.currency.clone();
match edge.as_ref().price.as_ref() {
None => (), // do nothing, multiply by one and keep the same currency
Some(price) => {
if currency == edge.from.currency {
mult *= price.get_price().get_amount();
currency = edge.to.currency.clone();
} else {
mult /= price.get_price().get_amount();
currency = edge.from.currency.clone();
}
}
}
}
multipliers.insert(k.currency.clone(), mult);
Expand All @@ -124,7 +154,7 @@ pub struct Node {

#[derive(Debug, Clone)]
pub struct Edge {
price: Price,
price: Option<Price>,
from: Rc<Node>,
to: Rc<Node>,
}
Expand All @@ -150,8 +180,8 @@ impl Graph {
fn from_prices(prices: &Vec<Price>, source: Node) -> Self {
let mut nodes = HashMap::new();
let mut edges = Vec::new();
let mut currency_dates = HashMap::new();
currency_dates.insert(source.currency.clone(), source.date);
let mut currency_dates = HashSet::new();
currency_dates.insert((source.currency.clone(), source.date));
// Remove redundant prices and create the nodes
let mut prices_nodup = HashMap::new();
for p in prices.iter() {
Expand All @@ -174,44 +204,63 @@ impl Graph {
}
}
}
}
for (_, p) in prices_nodup.iter() {
let commodities =
if p.price.get_commodity().unwrap().get_name() < p.commodity.as_ref().get_name() {
(p.price.get_commodity().unwrap(), p.commodity.clone())
} else {
(p.commodity.clone(), p.price.get_commodity().unwrap())
};
let c_vec = vec![commodities.0.clone(), commodities.1.clone()];
for c in c_vec {
match currency_dates.get(c.as_ref()) {
Some(v) => {
if v < &p.date {
currency_dates.insert(c.clone(), p.date);
}
}
None => {
currency_dates.insert(c.clone(), p.date);
}
}
currency_dates.insert((c.clone(), p.date));
}
}

// Create the nodes
for (c, d) in currency_dates.iter() {
nodes.insert(
c.clone(),
(c.clone(), d.clone()),
Rc::new(Node {
currency: c.clone(),
date: d.clone(),
}),
);
}
// Edges from the prices
for (_, p) in prices_nodup.iter() {
let from = nodes.get(p.commodity.as_ref()).unwrap().clone();
let from = nodes
.get(&(p.commodity.clone(), p.date.clone()))
.unwrap()
.clone();
let to = nodes
.get(p.price.get_commodity().unwrap().as_ref())
.get(&(p.price.get_commodity().unwrap(), p.date.clone()))
.unwrap()
.clone();
edges.push(Rc::new(Edge {
price: p.clone(),
price: Some(p.clone()),
from: from.clone(),
to: to.clone(),
}));
}

// println!("Nodes: {}", nodes.len());
// println!("Edges: {}", edges.len());
let vec_node: Vec<Rc<Node>> = nodes.iter().map(|x| x.1.clone()).collect();
let n = vec_node.len();
for i in 0..n {
for j in i..n {
if vec_node[i].currency == vec_node[j].currency {
edges.push(Rc::new(Edge {
price: None,
from: vec_node[i].clone(),
to: vec_node[j].clone(),
}))
}
}
}
// println!("Edges: {}", edges.len());

Graph {
nodes: nodes.iter().map(|x| x.1.clone()).collect(),
edges,
Expand Down Expand Up @@ -263,7 +312,7 @@ mod tests {
#[test]
fn test_graph() {
// Copy from balance command
let path = PathBuf::from("examples/demo.ledger");
let path = PathBuf::from("tests/example_files/demo.ledger");
let mut tokenizer = Tokenizer::from(&path);
let items = tokenizer.tokenize().unwrap();
let ledger = items.to_ledger(false).unwrap();
Expand Down
Loading

0 comments on commit 6c8cb75

Please sign in to comment.