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
#![deny(future_incompatible, unsafe_code)]
#![warn(nonstandard_style, rust_2018_idioms, missing_docs, clippy::pedantic)]
#![cfg_attr(test, allow(clippy::needless_pass_by_value))]

//! A rust parsing library for [beancount](https://beancount.github.io/docs/) files
//!
//! At its core, this library provides is a [`Parser`] type that
//! is an iterator over the directives.
//!
//! ## Example
//! ```
//! use beancount_parser::{Directive, Parser, Error};
//!
//! # fn main() -> Result<(), Error> {
//! let beancount = r#"
//! 2022-09-11 * "Coffee beans"
//!   Expenses:Groceries   10 CHF
//!   Assets:Bank
//! "#;
//!
//! let directives: Vec<Directive<'_>> = Parser::new(beancount).collect::<Result<_, _>>()?;
//! let transaction = directives[0].as_transaction().unwrap();
//! assert_eq!(transaction.narration(), Some("Coffee beans"));
//!
//! let first_posting_amount = transaction.postings()[0].amount().unwrap();
//! assert_eq!(first_posting_amount.currency(), "CHF");
//! assert_eq!(first_posting_amount.value().try_into_f64()?, 10.0);
//! # Ok(()) }
//! ```

pub mod account;
pub mod amount;
mod date;
mod directive;
mod error;
mod string;
pub mod transaction;

use crate::directive::directive;

pub use crate::{
    account::Account, amount::Amount, date::Date, directive::Directive, error::Error,
    transaction::Transaction,
};

use nom::{
    branch::alt,
    character::complete::{line_ending, not_line_ending},
    combinator::{map, opt, value},
    sequence::tuple,
    IResult,
};

/// Parser of a beancount document
///
/// It is an iterator over the beancount directives.
///
/// See the crate documentation for usage example.
pub struct Parser<'a> {
    rest: &'a str,
}

impl<'a> Parser<'a> {
    /// Create a new parser from the beancount string to parse
    #[must_use]
    pub fn new(content: &'a str) -> Self {
        Self { rest: content }
    }
}

impl<'a> Iterator for Parser<'a> {
    type Item = Result<Directive<'a>, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        while !self.rest.is_empty() {
            if let Ok((rest, directive)) = next(self.rest) {
                self.rest = rest;
                if let Some(directive) = directive {
                    return Some(Ok(directive));
                }
            } else {
                self.rest = "";
                return Some(Err(Error));
            }
        }
        None
    }
}

fn next(input: &str) -> IResult<&str, Option<Directive<'_>>> {
    alt((
        map(directive, Some),
        value(None, tuple((not_line_ending, opt(line_ending)))),
    ))(input)
}