hledger_parser/directive/
price.rs

1use chumsky::prelude::*;
2
3use crate::component::amount::{amount, Amount};
4use crate::component::commodity::commodity;
5use crate::component::date::simple::date;
6use crate::component::time::time;
7use crate::component::whitespace::whitespace;
8use crate::state::State;
9use crate::utils::end_of_line;
10
11#[derive(Clone, Debug, PartialEq)]
12pub struct Price {
13    pub date: chrono::NaiveDate,
14    pub commodity: String,
15    pub amount: Amount,
16}
17
18pub fn price<'a>() -> impl Parser<'a, &'a str, Price, extra::Full<Rich<'a, char>, State, ()>> {
19    just("P")
20        .ignore_then(whitespace().repeated().at_least(1))
21        .ignore_then(date())
22        .then_ignore(whitespace().repeated().at_least(1))
23        .then_ignore(time().then(whitespace().repeated().at_least(1)).or_not())
24        .then(commodity())
25        .then_ignore(whitespace().repeated().at_least(1))
26        .then(amount())
27        .then_ignore(end_of_line())
28        .map(|((date, commodity), amount)| Price {
29            date,
30            commodity,
31            amount,
32        })
33}
34
35#[cfg(test)]
36mod tests {
37    use rust_decimal::Decimal;
38
39    use super::*;
40
41    #[test]
42    fn simple() {
43        let result = price()
44            .then_ignore(end())
45            .parse("P 2009-01-01 € $1.35")
46            .into_result();
47        assert_eq!(
48            result,
49            Ok(Price {
50                date: chrono::NaiveDate::from_ymd_opt(2009, 1, 1).unwrap(),
51                commodity: String::from("€"),
52                amount: Amount {
53                    quantity: Decimal::new(135, 2),
54                    commodity: String::from("$"),
55                },
56            })
57        );
58    }
59
60    #[test]
61    fn with_time() {
62        let result = price()
63            .then_ignore(end())
64            .parse("P 2024-04-18 00:00:00 BTC 691747.70790400 SEK")
65            .into_result();
66        assert_eq!(
67            result,
68            Ok(Price {
69                date: chrono::NaiveDate::from_ymd_opt(2024, 4, 18).unwrap(),
70                commodity: String::from("BTC"),
71                amount: Amount {
72                    quantity: Decimal::new(69_174_770_790_400, 8),
73                    commodity: String::from("SEK"),
74                },
75            })
76        );
77    }
78
79    #[test]
80    fn comment() {
81        let result = price()
82            .then_ignore(end())
83            .parse("P 2009-01-01 € $1.35  ; with comment")
84            .into_result();
85        assert_eq!(
86            result,
87            Ok(Price {
88                date: chrono::NaiveDate::from_ymd_opt(2009, 1, 1).unwrap(),
89                commodity: String::from("€"),
90                amount: Amount {
91                    quantity: Decimal::new(135, 2),
92                    commodity: String::from("$"),
93                },
94            })
95        );
96    }
97}