Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 28 tags #29

Merged
merged 9 commits into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/demo.ledger
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
# or like this

2021-01-01 * Groceries
; :fruit:
Expenses:Groceries $100
Assets:Checking account

2021-01-03 * Clothing
; :skirt:
Expenses:Clothes $69.37
Assets:Checking account $-69.37

2021-01-15 * Flights
; destination: spain
Expenses:Travel 200 EUR
Assets:Checking account $-210.12

Expand Down
2 changes: 1 addition & 1 deletion src/lib/commands/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub fn execute(options: &CommonOpts) -> Result<(), Error> {
width = w_amount
),
}
let mut more_than_one_line:bool = false;
let mut more_than_one_line: bool = false;
for (_, money) in balance.iter() {
if more_than_one_line {
print!(
Expand Down
16 changes: 13 additions & 3 deletions src/lib/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,19 @@ pub fn filter(options: &CommonOpts, transaction: &Transaction<Posting>, posting:
return true;
}
for p in predicate {
match name.find(&p.to_lowercase()) {
None => continue,
Some(_) => return true,
if p.starts_with("%") {
// look in the posting tags
for tag in posting.tags.iter() {
match tag.name.to_lowercase().find(&p.to_lowercase()[1..]) {
None => continue,
Some(_) => return true,
}
}
} else {
match name.find(&p.to_lowercase()) {
None => continue,
Some(_) => return true,
}
}
}
false
Expand Down
130 changes: 130 additions & 0 deletions src/lib/models/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use crate::models::Tag;
use lazy_static::lazy_static;
use regex::Regex;
#[derive(Debug, Clone)]
pub struct Comment {
pub comment: String,
}

impl Comment {
pub fn get_tags(&self) -> Vec<Tag> {
lazy_static! {
static ref RE_FLAGS: Regex = Regex::new(format!("{}",
r"(:.+:) *$" , // the tags
).as_str()).unwrap();
static ref RE_TAG_VALUE: Regex = Regex::new(format!("{}",
r" *(.*): *(.*) *$"
).as_str()).unwrap();
}

match RE_FLAGS.is_match(&self.comment) {
true => {
let value = RE_FLAGS
.captures(&self.comment)
.unwrap()
.iter()
.nth(1)
.unwrap()
.unwrap()
.as_str();
let mut tags: Vec<Tag> = value
.split(":")
.map(|x| Tag {
name: x.clone().to_string(),
check: vec![],
assert: vec![],
value: None,
})
.collect();
tags.pop();
tags.remove(0);
tags
}
false => match RE_TAG_VALUE.is_match(&self.comment) {
true => {
let captures = RE_TAG_VALUE.captures(&self.comment).unwrap();
let name: String = captures
.iter()
.nth(1)
.unwrap()
.unwrap()
.as_str()
.to_string();
if name.contains(":") {
return vec![];
}
vec![Tag {
name,
check: vec![],
assert: vec![],
value: Some(
captures
.iter()
.nth(2)
.unwrap()
.unwrap()
.as_str()
.to_string(),
),
}]
}
false => Vec::new(),
},
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::models::HasName;
#[test]
fn multi_tag() {
let comment = Comment {
comment: ":tag_1:tag_2:tag_3:".to_string(),
};
let tags = comment.get_tags();
assert_eq!(tags.len(), 3, "There should be three tags");
assert_eq!(tags[0].get_name(), "tag_1");
assert_eq!(tags[1].get_name(), "tag_2");
assert_eq!(tags[2].get_name(), "tag_3");
}
#[test]
fn no_tag() {
let comment = Comment {
comment: ":tag_1:tag_2:tag_3: this is not valid".to_string(),
};
let tags = comment.get_tags();
assert_eq!(tags.len(), 0, "There should no tags");
}
#[test]
fn not_a_tag() {
let comment = Comment {
comment: "not a tag whatsoever".to_string(),
};
let tags = comment.get_tags();
assert_eq!(tags.len(), 0, "There should no tags");
}
#[test]
fn tag_value() {
let comment = Comment {
comment: "tag: value".to_string(),
};
let tags = comment.get_tags();
assert_eq!(tags.len(), 1, "There should be one tag");
let tag = tags[0].clone();
assert_eq!(tag.get_name(), "tag");
assert_eq!(tag.value.unwrap(), "value".to_string());
}
#[test]
fn tag_value_spaces() {
let comment = Comment {
comment: " tag: value with spaces".to_string(),
};
let tags = comment.get_tags();
assert_eq!(tags.len(), 1, "There should be one tag");
let tag = tags[0].clone();
assert_eq!(tag.get_name(), "tag");
assert_eq!(tag.value.unwrap(), "value with spaces".to_string());
}
}
19 changes: 10 additions & 9 deletions src/lib/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::collections::{HashMap, HashSet};

use num::rational::BigRational;
use std::collections::{HashMap, HashSet};

pub use account::Account;
pub use balance::Balance;
pub use comment::Comment;
pub use currency::Currency;
pub use models::{ParsedPrice, Tag};
pub use money::Money;
Expand All @@ -22,6 +22,7 @@ use std::rc::Rc;

mod account;
mod balance;
mod comment;
mod currency;
mod models;
mod money;
Expand Down Expand Up @@ -140,13 +141,18 @@ impl ParsedLedger {
transaction.note = parsed.note.clone();
transaction.date = parsed.date;
transaction.effective_date = parsed.effective_date;

for comment in parsed.comments.iter() {
transaction.tags.append(&mut comment.get_tags());
}
// Go posting by posting
for p in parsed.postings.iter() {
let account = self.accounts.get(&p.account)?;

let mut posting: Posting = Posting::new(account, p.kind);

posting.tags = transaction.tags.clone();
for comment in p.comments.iter() {
posting.tags.append(&mut comment.get_tags());
}
// Modify posting with amounts
if let Some(c) = &p.money_currency {
posting.amount = Some(Money::from((
Expand Down Expand Up @@ -255,11 +261,6 @@ impl ParsedLedger {
}
}

#[derive(Debug, Clone)]
pub struct Comment {
pub comment: String,
}

#[derive(Copy, Clone, Debug)]
pub enum Origin {
FromDirective,
Expand Down
1 change: 1 addition & 0 deletions src/lib/models/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct Tag {
pub name: String,
pub check: Vec<String>,
pub assert: Vec<String>,
pub value: Option<String>,
}

impl HasName for Tag {
Expand Down
9 changes: 9 additions & 0 deletions src/lib/models/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use num::BigInt;
use std::fmt;
use std::fmt::{Display, Formatter};

use super::Tag;

#[derive(Debug, Clone)]
pub struct Transaction<PostingType> {
pub status: TransactionStatus,
Expand All @@ -28,6 +30,7 @@ pub struct Transaction<PostingType> {
pub virtual_postings_balance: Vec<PostingType>,
pub comments: Vec<Comment>,
pub transaction_type: TransactionType,
pub tags: Vec<Tag>,
}

#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -65,6 +68,7 @@ pub struct Posting {
pub balance: Option<Money>,
pub cost: Option<Cost>,
pub kind: PostingType,
pub tags: Vec<Tag>,
}

impl Posting {
Expand All @@ -75,6 +79,7 @@ impl Posting {
balance: None,
cost: None,
kind: kind,
tags: vec![],
}
}
pub fn set_amount(&mut self, money: Money) {
Expand Down Expand Up @@ -103,6 +108,7 @@ impl<PostingType> Transaction<PostingType> {
virtual_postings_balance: vec![],
comments: vec![],
transaction_type: t_type,
tags: vec![],
}
}
/// Iterator over all the postings, including the virtual ones
Expand Down Expand Up @@ -261,6 +267,7 @@ impl Transaction<Posting> {
balance: p.balance.clone(),
cost: p.cost.clone(),
kind: PostingType::Real,
tags: self.tags.clone(),
});
} else if &p.balance.is_some() & !skip_balance_check {
// There is a balance
Expand All @@ -279,6 +286,7 @@ impl Transaction<Posting> {
balance: p.balance.clone(),
cost: p.cost.clone(),
kind: PostingType::Real,
tags: p.tags.clone(),
});
} else {
// We do nothing, but this is the account for the empty post
Expand Down Expand Up @@ -312,6 +320,7 @@ impl Transaction<Posting> {
balance: None,
cost: None,
kind: PostingType::Real,
tags: self.tags.clone(),
});
}
self.postings = postings;
Expand Down
10 changes: 6 additions & 4 deletions src/lib/parser/tokenizers/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use crate::parser::{chars, Tokenizer};
/// ```rust
/// # use dinero::parser::{Tokenizer};
/// # use dinero::models::Comment;
/// # let content = "; This is a comment\n".to_string();
/// # let content = "; This is a comment\n; This is another comment\n".to_string();
/// let mut tokenizer = Tokenizer::from(content);
/// let parsed_ledger = tokenizer.tokenize().unwrap();
/// assert_eq!(parsed_ledger.len(), 1, "Should have parsed one item");
/// let comment = parsed_ledger.comments.get(0).unwrap();
/// assert_eq!(comment.comment, "This is a comment".to_string());
/// assert_eq!(parsed_ledger.len(), 2, "Should have parsed two items");
/// let comment_1 = parsed_ledger.comments.get(0).unwrap();
/// assert_eq!(comment_1.comment, "This is a comment".to_string());
/// let comment_2 = parsed_ledger.comments.get(1).unwrap();
/// assert_eq!(comment_2.comment, "This is another comment".to_string());
/// ```
/// ```rust
/// # use dinero::parser::{Tokenizer};
Expand Down
3 changes: 2 additions & 1 deletion src/lib/parser/tokenizers/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::ParserError;
pub(crate) fn parse(tokenizer: &mut Tokenizer) -> Result<Tag, ParserError> {
lazy_static! {
static ref RE: Regex = Regex::new(format!("{}{}",
r"(tag) +" , // directive commodity
r"(tag) +" , // tag directive
r"(.*)" , // description
).as_str()).unwrap();
}
Expand Down Expand Up @@ -67,6 +67,7 @@ pub(crate) fn parse(tokenizer: &mut Tokenizer) -> Result<Tag, ParserError> {
name,
check,
assert,
value: None,
})
}

Expand Down
33 changes: 24 additions & 9 deletions src/lib/parser/tokenizers/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,38 @@ pub(crate) fn parse_generic<'a>(
}
}

// Have a flag so that it can be known whether a comment belongs to the transaction or to the
// posting
let mut parsed_posting = false;
while let LineType::Indented = chars::consume_whitespaces_and_lines(tokenizer) {
match tokenizer.get_char().unwrap() {
';' => transaction.comments.push(comment::parse(tokenizer)),
';' => {
let comment = comment::parse(tokenizer);
match parsed_posting {
true => {
let len = transaction.postings.len();
transaction.postings[len - 1].comments.push(comment);
}
false => transaction.comments.push(comment),
}
}
c if c.is_numeric() => {
return Err(tokenizer.error(ParserError::UnexpectedInput(Some(
"Expecting account name".to_string(),
))));
}
_ => match parse_posting(tokenizer, transaction.transaction_type) {
// Although here we already know the kind of the posting (virtual, real),
// we deal with that in the next phase of parsing
Ok(posting) => transaction.postings.push(posting),
Err(e) => {
eprintln!("Error while parsing posting.");
return Err(tokenizer.error(e));
_ => {
match parse_posting(tokenizer, transaction.transaction_type) {
// Although here we already know the kind of the posting (virtual, real),
// we deal with that in the next phase of parsing
Ok(posting) => transaction.postings.push(posting),
Err(e) => {
eprintln!("Error while parsing posting.");
return Err(tokenizer.error(e));
}
}
},
parsed_posting = true;
}
}
}

Expand Down
Loading