dinero/models/
comment.rs

1use crate::models::Tag;
2use crate::parser::utils::parse_str_as_date;
3use chrono::NaiveDate;
4use lazy_static::lazy_static;
5use regex::Regex;
6use std::cell::RefCell;
7
8use super::HasName;
9#[derive(Debug, Clone)]
10pub struct Comment {
11    pub comment: String,
12    calculated_tags: RefCell<bool>,
13    tags: RefCell<Vec<Tag>>,
14    calculated_payee: RefCell<bool>,
15    payee: RefCell<Option<String>>,
16}
17
18impl From<String> for Comment {
19    fn from(comment: String) -> Self {
20        Comment {
21            comment,
22            calculated_tags: RefCell::new(false),
23            tags: RefCell::new(vec![]),
24            calculated_payee: RefCell::new(false),
25            payee: RefCell::new(None),
26        }
27    }
28}
29
30impl From<&str> for Comment {
31    fn from(comment: &str) -> Self {
32        Comment::from(comment.to_string())
33    }
34}
35
36impl Comment {
37    pub fn get_tags(&self) -> Vec<Tag> {
38        lazy_static! {
39            // the tags
40            static ref RE_FLAGS: Regex = Regex::new(r"(:.+:) *$").unwrap();
41            // the value
42            static ref RE_VALUE: Regex = Regex::new(" *(.*): *(.*) *$").unwrap();
43        }
44        let calculated_tags = *self.calculated_tags.borrow_mut();
45        let tags = {
46            if !calculated_tags {
47                self.calculated_tags.replace(true);
48                self.tags
49                    .borrow_mut()
50                    .append(&mut match RE_FLAGS.is_match(&self.comment) {
51                        true => {
52                            let value = RE_FLAGS
53                                .captures(&self.comment)
54                                .unwrap()
55                                .iter()
56                                .nth(1)
57                                .unwrap()
58                                .unwrap()
59                                .as_str();
60                            let mut tags: Vec<Tag> = value
61                                .split(':')
62                                .map(|x| Tag {
63                                    name: x.into(),
64                                    check: vec![],
65                                    assert: vec![],
66                                    value: None,
67                                })
68                                .collect();
69                            tags.pop();
70                            tags.remove(0);
71                            tags
72                        }
73                        false => match RE_VALUE.is_match(&self.comment) {
74                            true => {
75                                let re_captures = RE_VALUE.captures(&self.comment).unwrap();
76                                let mut captures = re_captures.iter();
77                                captures.next();
78                                let name: String =
79                                    captures.next().unwrap().unwrap().as_str().to_string();
80                                if name.contains(':') {
81                                    vec![]
82                                } else {
83                                    vec![Tag {
84                                        name,
85                                        check: vec![],
86                                        assert: vec![],
87                                        value: Some(
88                                            captures
89                                                .next()
90                                                .unwrap()
91                                                .unwrap()
92                                                .as_str()
93                                                .trim()
94                                                .to_string(),
95                                        ),
96                                    }]
97                                }
98                            }
99                            false => vec![],
100                        },
101                    });
102            }
103            self.tags.borrow().clone()
104        };
105        tags
106    }
107
108    pub fn get_payee_str(&self) -> Option<String> {
109        let calculated_payee = *self.calculated_payee.borrow_mut();
110        if calculated_payee {
111            return self.payee.borrow().clone();
112        }
113        self.calculated_payee.replace(true);
114        for tag in self.get_tags().iter() {
115            if tag.value.is_none() | (tag.get_name().to_lowercase() != "payee") {
116                continue;
117            }
118            self.payee.replace(tag.value.clone());
119            return tag.value.clone();
120        }
121        None
122    }
123
124    /// Gets the date of a comment
125    ///
126    /// This function is not cached, as in practice it is called only once
127    pub fn get_date(&self) -> Option<NaiveDate> {
128        lazy_static! {
129            // the value
130            static ref RE_VALUE: Regex = Regex::new(r" *\[=(\d{4}.\d{2}.\d{2})\] *$").unwrap();
131        }
132        match RE_VALUE.is_match(&self.comment) {
133            true => Some(parse_str_as_date(
134                self.comment.as_str().trim().split_at(2).1,
135            )),
136            false => None,
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::models::HasName;
145    #[test]
146    fn multi_tag() {
147        let comment = Comment::from(":tag_1:tag_2:tag_3:");
148        let tags = comment.get_tags();
149        assert_eq!(tags.len(), 3, "There should be three tags");
150        assert_eq!(tags[0].get_name(), "tag_1");
151        assert_eq!(tags[1].get_name(), "tag_2");
152        assert_eq!(tags[2].get_name(), "tag_3");
153    }
154    #[test]
155    fn no_tag() {
156        let comment = Comment::from(":tag_1:tag_2:tag_3: this is not valid");
157        let tags = comment.get_tags();
158        assert_eq!(tags.len(), 0, "There should no tags");
159    }
160    #[test]
161    fn not_a_tag() {
162        let comment = Comment::from("not a tag whatsoever");
163        let tags = comment.get_tags();
164        assert_eq!(tags.len(), 0, "There should no tags");
165    }
166    #[test]
167    fn tag_value() {
168        let comment = Comment::from("tag: value");
169        let tags = comment.get_tags();
170        assert_eq!(tags.len(), 1, "There should be one tag");
171        let tag = tags[0].clone();
172        assert_eq!(tag.get_name(), "tag");
173        assert_eq!(tag.value.unwrap(), "value".to_string());
174    }
175    #[test]
176    fn tag_value_spaces() {
177        let comment = Comment::from("tag: value with spaces");
178        let tags = comment.get_tags();
179        assert_eq!(tags.len(), 1, "There should be one tag");
180        let tag = tags[0].clone();
181        assert_eq!(tag.get_name(), "tag");
182        assert_eq!(tag.value.unwrap(), "value with spaces".to_string());
183    }
184    #[test]
185    fn date_in_comment() {
186        let comment = Comment::from("  [=2021/03/02]  ");
187        let date = comment.get_date().unwrap();
188        assert_eq!(date, NaiveDate::from_ymd(2021, 3, 2));
189    }
190    #[test]
191    fn payee_in_comment() {
192        let comment = Comment::from("  payee: claudio  ");
193        let payee = comment.get_payee_str().unwrap();
194        assert_eq!(payee, "claudio".to_string());
195    }
196}