hledger_parser/directive/
price.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use chumsky::prelude::*;

use crate::component::amount::{amount, Amount};
use crate::component::commodity::commodity;
use crate::component::date::simple::date;
use crate::component::time::time;
use crate::component::whitespace::whitespace;
use crate::state::State;
use crate::utils::end_of_line;

#[derive(Clone, Debug, PartialEq)]
pub struct Price {
    pub date: chrono::NaiveDate,
    pub commodity: String,
    pub amount: Amount,
}

pub fn price<'a>() -> impl Parser<'a, &'a str, Price, extra::Full<Rich<'a, char>, State, ()>> {
    just("P")
        .ignore_then(whitespace().repeated().at_least(1))
        .ignore_then(date())
        .then_ignore(whitespace().repeated().at_least(1))
        .then_ignore(time().then(whitespace().repeated().at_least(1)).or_not())
        .then(commodity())
        .then_ignore(whitespace().repeated().at_least(1))
        .then(amount())
        .then_ignore(end_of_line())
        .map(|((date, commodity), amount)| Price {
            date,
            commodity,
            amount,
        })
}

#[cfg(test)]
mod tests {
    use rust_decimal::Decimal;

    use super::*;

    #[test]
    fn simple() {
        let result = price()
            .then_ignore(end())
            .parse("P 2009-01-01 € $1.35")
            .into_result();
        assert_eq!(
            result,
            Ok(Price {
                date: chrono::NaiveDate::from_ymd_opt(2009, 1, 1).unwrap(),
                commodity: String::from("€"),
                amount: Amount {
                    quantity: Decimal::new(135, 2),
                    commodity: String::from("$"),
                },
            })
        );
    }

    #[test]
    fn with_time() {
        let result = price()
            .then_ignore(end())
            .parse("P 2024-04-18 00:00:00 BTC 691747.70790400 SEK")
            .into_result();
        assert_eq!(
            result,
            Ok(Price {
                date: chrono::NaiveDate::from_ymd_opt(2024, 4, 18).unwrap(),
                commodity: String::from("BTC"),
                amount: Amount {
                    quantity: Decimal::new(69_174_770_790_400, 8),
                    commodity: String::from("SEK"),
                },
            })
        );
    }

    #[test]
    fn comment() {
        let result = price()
            .then_ignore(end())
            .parse("P 2009-01-01 € $1.35  ; with comment")
            .into_result();
        assert_eq!(
            result,
            Ok(Price {
                date: chrono::NaiveDate::from_ymd_opt(2009, 1, 1).unwrap(),
                commodity: String::from("€"),
                amount: Amount {
                    quantity: Decimal::new(135, 2),
                    commodity: String::from("$"),
                },
            })
        );
    }
}