1use std::cmp::Ordering;
2
3use log::debug;
4use pest::iterators::Pairs;
5use pest::Parser;
6
7use crate::data::{self, DebugLine};
8use crate::data::{Directive, Options};
9use crate::error::{BeanError, ErrorType};
10use crate::grammar::{BeanParser, Rule};
11use crate::ledger::Ledger;
12use crate::utils;
13
14pub fn load(data: &str) -> Pairs<'_, Rule> {
16 let mut entries = BeanParser::parse(Rule::root, data).unwrap();
19
20 let entry = entries.next().unwrap();
22 utils::debug_pair(&entry, 0);
23 entry.into_inner()
24}
25
26pub fn consume(entries: Pairs<'_, Rule>) -> Ledger {
28 let mut errs: Vec<BeanError> = Vec::with_capacity(entries.len());
29 let mut dirs: Vec<Directive> = Vec::new();
30 let mut opts = Options::default();
31 for entry in entries {
32 debug!("{:?}\t{:?}", entry.as_rule(), entry.as_span(),);
33 match entry.as_rule() {
34 Rule::option => {
35 opts.update_from_entry(entry);
36 }
37 Rule::custom => {
38 dirs.push(Directive::ConfigCustom(data::ConfigCustom::from_entry(
39 entry,
40 )));
41 }
42 Rule::query => {
43 dirs.push(Directive::Query(data::Query::from_entry(entry)));
44 }
45 Rule::commodity => {
46 dirs.push(Directive::Commodity(data::Commodity::from_entry(entry)));
47 }
48 Rule::open => {
49 dirs.push(Directive::Open(data::Open::from_entry(entry)));
50 }
51 Rule::close => {
52 dirs.push(Directive::Close(data::Close::from_entry(entry)));
53 }
54 Rule::balance => {
55 dirs.push(Directive::Balance(data::Balance::from_entry(entry)));
56 }
57 Rule::pad => {
58 dirs.push(Directive::Pad(data::Pad::from_entry(entry)));
59 }
60 Rule::price => {
61 dirs.push(Directive::Price(data::Price::from_entry(entry)));
62 }
63 Rule::document => {
64 dirs.push(Directive::Document(data::Document::from_entry(entry)));
65 }
66 Rule::note => {
67 dirs.push(Directive::Note(data::Note::from_entry(entry)));
68 }
69 Rule::transaction => {
70 dirs.push(Directive::Transaction(data::Transaction::from_entry(entry)));
71 }
72 Rule::EOI => {
73 debug!("Hit EOI");
74 }
75 Rule::badline => {
76 let (line, _) = entry.line_col();
77 let debug = DebugLine::new(line);
78 let err =
79 BeanError::new(ErrorType::Badline, &debug, "Found unparseable line", None);
80 errs.push(err);
81 }
82 _ => {
83 let (l, _) = entry.line_col();
84 let debug = DebugLine::new(l);
85 unreachable!("Found unexpected entry in file, abort.\n{debug}");
86 }
87 };
88 }
89 Ledger { dirs, errs, opts }
90}
91
92pub fn sort(dirs: &mut [Directive]) {
94 dirs.sort_by(|a, b| match a.date().cmp(b.date()) {
95 Ordering::Equal => a.order().cmp(&b.order()),
96 other => other,
97 });
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 #[test]
104 fn test_parse() {
105 let text = r#"2023-01-01 open Assets:Bank GBP"#;
106 let entries = load(&text);
107 let Ledger {
108 dirs,
109 errs: _,
110 opts: _,
111 } = consume(entries);
112 let got = &dirs[0];
113 match got {
114 Directive::Open(_) => (),
115 _ => assert!(false, "Found wrong directive type"),
116 }
117 }
118
119 #[test]
120 fn test_bad_consume() {
121 let text = r#"
122 2023-01-01 foo
123 "#;
124 let entries = load(&text);
125 let Ledger {
126 dirs: _,
127 errs,
128 opts: _,
129 } = consume(entries);
130 assert!(errs.len() == 1);
131 }
132
133 #[test]
134 fn test_consume() {
135 let text = r#"
136 option "operating_currency" "GBP"
137 "#;
138 let entries = load(&text);
139 consume(entries);
140 }
141}