beancount_parser/
metadata.rs1use std::{
18 borrow::Borrow,
19 collections::HashMap,
20 fmt::{Debug, Display, Formatter},
21 str::FromStr,
22 sync::Arc,
23};
24
25use nom::{
26 branch::alt,
27 bytes::complete::take_while,
28 character::complete::{char, satisfy, space1},
29 combinator::{all_consuming, iterator, map, recognize},
30 sequence::preceded,
31 Parser,
32};
33
34use crate::{amount, empty_line, end_of_line, string, Currency, Decimal, IResult, Span};
35
36pub type Map<D> = HashMap<Key, Value<D>>;
40
41#[derive(Debug, Clone, Eq, PartialEq, Hash)]
45pub struct Key(Arc<str>);
46
47impl Display for Key {
48 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49 Display::fmt(&self.0, f)
50 }
51}
52
53impl AsRef<str> for Key {
54 fn as_ref(&self) -> &str {
55 self.0.as_ref()
56 }
57}
58
59impl Borrow<str> for Key {
60 fn borrow(&self) -> &str {
61 self.0.borrow()
62 }
63}
64
65impl FromStr for Key {
66 type Err = crate::Error;
67 fn from_str(s: &str) -> Result<Self, Self::Err> {
68 let span = Span::new(s);
69 match all_consuming(key).parse(span) {
70 Ok((_, key)) => Ok(key),
71 Err(_) => Err(crate::Error::new(s, span)),
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq)]
80#[non_exhaustive]
81pub enum Value<D> {
82 String(String),
84 Number(D),
86 Currency(Currency),
88}
89
90impl<D> Value<D> {
91 pub fn as_string(&self) -> Option<&str> {
93 match self {
94 Value::String(s) => Some(s.as_str()),
95 _ => None,
96 }
97 }
98
99 pub fn as_number(&self) -> Option<&D> {
101 match self {
102 Value::Number(n) => Some(n),
103 _ => None,
104 }
105 }
106
107 pub fn as_currency(&self) -> Option<&Currency> {
109 match self {
110 Value::Currency(c) => Some(c),
111 _ => None,
112 }
113 }
114}
115
116pub(crate) fn parse<D: Decimal>(input: Span<'_>) -> IResult<'_, Map<D>> {
117 let mut iter = iterator(input, alt((entry.map(Some), empty_line.map(|()| None))));
118 let map: HashMap<_, _> = iter.by_ref().flatten().collect();
119 let (input, ()) = iter.finish()?;
120 Ok((input, map))
121}
122
123fn entry<D: Decimal>(input: Span<'_>) -> IResult<'_, (Key, Value<D>)> {
124 let (input, _) = space1(input)?;
125 let (input, key) = key(input)?;
126 let (input, _) = char(':')(input)?;
127 let (input, _) = space1(input)?;
128 let (input, value) = alt((
129 string.map(Value::String),
130 amount::expression.map(Value::Number),
131 amount::currency.map(Value::Currency),
132 ))
133 .parse(input)?;
134 let (input, ()) = end_of_line(input)?;
135 Ok((input, (key, value)))
136}
137
138fn key(input: Span<'_>) -> IResult<'_, Key> {
139 map(
140 recognize(preceded(
141 satisfy(char::is_lowercase),
142 take_while(|c: char| c.is_alphanumeric() || c == '-' || c == '_'),
143 )),
144 |s: Span<'_>| Key((*s.fragment()).into()),
145 )
146 .parse(input)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 use rstest::rstest;
154
155 #[rstest]
156 fn key_from_str_should_parse_key() {
157 let key: Key = "foo".parse().unwrap();
158 assert_eq!(key.as_ref(), "foo");
159 }
160
161 #[rstest]
162 fn key_from_str_should_not_parse_invalid_key() {
163 let key: Result<Key, _> = "foo bar".parse();
164 assert!(key.is_err(), "{key:?}");
165 }
166}