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
98
99
100
101
use nom::{
branch::alt,
bytes::complete::{tag, take_while1},
character::complete::char,
combinator::map,
multi::separated_list1,
sequence::separated_pair,
IResult,
};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Account<'a> {
type_: Type,
components: Vec<&'a str>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Type {
Assets,
Liabilities,
Equity,
Income,
Expenses,
}
impl<'a> Account<'a> {
pub(crate) fn new(type_: Type, path: impl IntoIterator<Item = &'a str>) -> Self {
Self {
type_,
components: path.into_iter().collect(),
}
}
#[must_use]
pub fn type_(&self) -> Type {
self.type_
}
#[must_use]
pub fn components(&self) -> &[&'a str] {
self.components.as_ref()
}
}
pub(crate) fn account(input: &str) -> IResult<&str, Account<'_>> {
map(
separated_pair(
type_,
char(':'),
separated_list1(
char(':'),
take_while1(|c: char| c.is_alphanumeric() || c == '-'),
),
),
|(t, p)| Account::new(t, p),
)(input)
}
fn type_(input: &str) -> IResult<&str, Type> {
alt((
map(tag("Assets"), |_| Type::Assets),
map(tag("Liabilities"), |_| Type::Liabilities),
map(tag("Income"), |_| Type::Income),
map(tag("Expenses"), |_| Type::Expenses),
map(tag("Equity"), |_| Type::Equity),
))(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[rstest]
#[case("Assets:MyAccount", Account::new(Type::Assets, ["MyAccount"]))]
#[case("Liabilities:A:B:C", Account::new(Type::Liabilities, ["A", "B", "C"]))]
#[case("Income:Foo:Bar12", Account::new(Type::Income, ["Foo", "Bar12"]))]
#[case("Expenses:3Foo", Account::new(Type::Expenses, ["3Foo"]))]
#[case("Equity:Foo-Bar", Account::new(Type::Equity, ["Foo-Bar"]))]
fn valid_account(#[case] input: &str, #[case] expected: Account<'_>) {
assert_eq!(account(input), Ok(("", expected)));
}
}