beancount_parser_lima/
types.rs

1use crate::{format::*, options::BeancountOption};
2use chumsky::{
3    extra::ParserExtra,
4    input::{Input, MapExtra},
5};
6use rust_decimal::Decimal;
7use std::{
8    cmp::max,
9    collections::{hash_map, HashMap, HashSet},
10    fmt::{self, Display, Formatter},
11    hash::{Hash, Hasher},
12    iter::empty,
13    mem::swap,
14    ops::Deref,
15};
16use std::{marker::PhantomData, ops::DerefMut};
17use strum_macros::{Display, EnumIter, EnumString, IntoStaticStr};
18use time::Date;
19
20/// Error or warning, according to the marker type with which it is instantiated.
21#[derive(Clone, Debug)]
22pub struct ErrorOrWarning<K>
23where
24    K: ErrorOrWarningKind,
25{
26    pub(crate) message: String,
27    pub(crate) reason: String,
28    pub(crate) span: Span,
29    pub(crate) contexts: Vec<(String, Span)>,
30    pub(crate) related: Vec<(String, Span)>,
31    kind: PhantomData<K>,
32}
33
34/// Marker type for [ErrorOrWarning] error.
35#[derive(Clone, Debug)]
36pub struct ErrorKind;
37
38/// The type of errors returned by the parser.
39/// All that can usefully be done with these is write them via `BeancountSources`.
40pub type Error = ErrorOrWarning<ErrorKind>;
41
42/// Marker type for [ErrorOrWarning] warning.
43#[derive(Clone, Debug)]
44pub struct WarningKind;
45
46/// The type of warnings returned by the parser.
47/// All that can usefully be done with these is write them via `BeancountSources`.
48pub type Warning = ErrorOrWarning<WarningKind>;
49
50impl Error {
51    pub(crate) fn new<M: Into<String>, R: Into<String>>(message: M, reason: R, span: Span) -> Self {
52        ErrorOrWarning {
53            message: message.into(),
54            reason: reason.into(),
55            span,
56            contexts: Vec::new(),
57            related: Vec::new(),
58            kind: PhantomData,
59        }
60    }
61
62    pub(crate) fn with_contexts<M: Into<String>, R: Into<String>>(
63        message: M,
64        reason: R,
65        span: Span,
66        contexts: Vec<(String, Span)>,
67    ) -> Self {
68        ErrorOrWarning {
69            message: message.into(),
70            reason: reason.into(),
71            span,
72            contexts,
73            related: Vec::new(),
74            kind: PhantomData,
75        }
76    }
77}
78
79impl Warning {
80    pub(crate) fn new<M: Into<String>, R: Into<String>>(message: M, reason: R, span: Span) -> Self {
81        ErrorOrWarning {
82            message: message.into(),
83            reason: reason.into(),
84            span,
85            contexts: Vec::new(),
86            related: Vec::new(),
87            kind: PhantomData,
88        }
89    }
90}
91
92impl<K> ErrorOrWarning<K>
93where
94    K: ErrorOrWarningKind,
95{
96    /// Annotate an error or warning as being related to another parsed element.
97    pub fn related_to<'a, T>(self, element: &'a Spanned<T>) -> Self
98    where
99        T: ElementType + 'a,
100    {
101        let mut e = self;
102        e.related
103            .push((element.element_type().to_string(), element.span));
104        e
105    }
106
107    /// Annotate an error or warning as being related to a number of parsed elements.
108    pub fn related_to_all<'a, T>(self, elements: impl IntoIterator<Item = &'a Spanned<T>>) -> Self
109    where
110        T: ElementType + 'a,
111    {
112        let mut e = self;
113        let mut new_related = elements
114            .into_iter()
115            .map(|element| (element.element_type().to_string(), element.span))
116            .collect::<Vec<_>>();
117        e.related.append(&mut new_related);
118        e
119    }
120
121    pub fn message(&self) -> &str {
122        self.message.as_str()
123    }
124
125    pub(crate) fn related_to_named_span<S>(self, name: S, span: Span) -> Self
126    where
127        S: ToString,
128    {
129        let mut e = self;
130        e.related.push((name.to_string(), span));
131        e
132    }
133
134    /// Annotate an error or warning as being in the context of another parsed elememt,
135    /// for example an error on a [Posting] being in the context of its [Transaction].
136    pub fn in_context<T>(self, element: &Spanned<T>) -> Self
137    where
138        T: ElementType,
139    {
140        let mut e = self;
141        e.contexts
142            .push((element.element_type().to_string(), element.span));
143        e
144    }
145
146    pub(crate) fn color(&self) -> ariadne::Color {
147        K::color()
148    }
149
150    pub(crate) fn report_kind(&self) -> ariadne::ReportKind<'static> {
151        K::report_kind()
152    }
153
154    pub fn with_annotation<S>(self, annotation: S) -> AnnotatedErrorOrWarning<K>
155    where
156        S: Into<String>,
157    {
158        AnnotatedErrorOrWarning {
159            error_or_warning: self,
160            annotation: Some(annotation.into()),
161        }
162    }
163}
164
165impl Display for Error {
166    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
167        use chumsky::span::Span;
168
169        write!(f, "{} ({}) ", self.message, self.reason)?;
170        write!(
171            f,
172            "at {}..{} of source {}",
173            self.span.start(),
174            self.span.end(),
175            self.span.context()
176        )?;
177        for context in self.contexts.iter() {
178            write!(f, " while parsing {} at {}", context.0, context.1)?;
179        }
180        Ok(())
181    }
182}
183
184impl std::error::Error for Error {}
185
186/// Trait for formatting of [Error] separately from [Warning].
187pub trait ErrorOrWarningKind {
188    fn report_kind() -> ariadne::ReportKind<'static>;
189
190    fn color() -> ariadne::Color;
191}
192
193impl ErrorOrWarningKind for ErrorKind {
194    fn report_kind() -> ariadne::ReportKind<'static> {
195        ariadne::ReportKind::Error
196    }
197
198    fn color() -> ariadne::Color {
199        ariadne::Color::Red
200    }
201}
202
203impl ErrorOrWarningKind for WarningKind {
204    fn report_kind() -> ariadne::ReportKind<'static> {
205        ariadne::ReportKind::Warning
206    }
207
208    fn color() -> ariadne::Color {
209        ariadne::Color::Yellow
210    }
211}
212
213/// Annotated error or warning
214#[derive(Clone, Debug)]
215pub struct AnnotatedErrorOrWarning<K>
216where
217    K: ErrorOrWarningKind,
218{
219    pub error_or_warning: ErrorOrWarning<K>,
220    pub annotation: Option<String>,
221}
222
223impl<K> From<ErrorOrWarning<K>> for AnnotatedErrorOrWarning<K>
224where
225    K: ErrorOrWarningKind,
226{
227    fn from(error_or_warning: ErrorOrWarning<K>) -> Self {
228        Self {
229            error_or_warning,
230            annotation: None,
231        }
232    }
233}
234
235pub type AnnotatedError = AnnotatedErrorOrWarning<ErrorKind>;
236pub type AnnotatedWarning = AnnotatedErrorOrWarning<WarningKind>;
237
238/// Top-level account type, the prefix of any fully-qualified [Account].
239#[derive(PartialEq, Eq, Hash, Clone, Copy, EnumString, EnumIter, IntoStaticStr, Debug)]
240pub enum AccountType {
241    Assets,
242    Liabilities,
243    Equity,
244    Income,
245    Expenses,
246}
247
248impl AsRef<str> for AccountType {
249    fn as_ref(&self) -> &'static str {
250        self.into()
251    }
252}
253
254/// A flag on a [Posting] or [Transaction].
255#[derive(PartialEq, Eq, Default, Clone, Copy, Debug)]
256pub enum Flag {
257    #[default]
258    Asterisk,
259    Exclamation,
260    Ampersand,
261    Hash,
262    Question,
263    Percent,
264    Letter(FlagLetter),
265}
266
267impl ElementType for Flag {
268    fn element_type(&self) -> &'static str {
269        "flag"
270    }
271}
272
273impl Display for Flag {
274    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
275        use Flag::*;
276        let (prefix, c) = match self {
277            Asterisk => (None, '*'),
278            Exclamation => (None, '!'),
279            Ampersand => (None, '&'),
280            Hash => (None, '#'),
281            Question => (None, '?'),
282            Percent => (None, '%'),
283            Letter(FlagLetter(c)) => (Some('\''), *c),
284        };
285
286        match prefix {
287            Some(prefix) => write!(f, "{}{}", prefix, c),
288            None => write!(f, "{}", c),
289        }
290    }
291}
292
293/// A flag other than one of the builtin ones.
294#[derive(PartialEq, Eq, Copy, Clone, Debug)]
295pub struct FlagLetter(char);
296
297impl FlagLetter {
298    /// Field accessor.
299    pub fn char(&self) -> char {
300        self.0
301    }
302
303    pub(crate) fn is_valid(c: &char) -> bool {
304        c.is_ascii_uppercase()
305    }
306}
307
308/// Error type for invalid [FlagLetter].
309#[derive(PartialEq, Eq, Debug)]
310pub struct FlagLetterError(char);
311
312impl Display for FlagLetterError {
313    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
314        write!(
315            f,
316            "invalid character '{}' for flag letter - must be uppercase ASCII",
317            self.0
318        )
319    }
320}
321
322impl std::error::Error for FlagLetterError {}
323
324impl TryFrom<char> for FlagLetter {
325    type Error = FlagLetterError;
326
327    fn try_from(c: char) -> Result<Self, Self::Error> {
328        if FlagLetter::is_valid(&c) {
329            Ok(FlagLetter(c))
330        } else {
331            Err(FlagLetterError(c))
332        }
333    }
334}
335
336/// The booking method for an account.
337#[derive(
338    EnumString, EnumIter, IntoStaticStr, PartialEq, Eq, Default, Clone, Copy, Display, Debug,
339)]
340#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
341pub enum Booking {
342    #[default]
343    Strict,
344    StrictWithSize,
345    None,
346    Average,
347    Fifo,
348    Lifo,
349    Hifo,
350}
351
352impl AsRef<str> for Booking {
353    fn as_ref(&self) -> &'static str {
354        self.into()
355    }
356}
357
358impl ElementType for Booking {
359    fn element_type(&self) -> &'static str {
360        "booking"
361    }
362}
363
364#[derive(
365    EnumString, EnumIter, IntoStaticStr, PartialEq, Eq, Default, Clone, Copy, Display, Debug,
366)]
367#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
368pub enum PluginProcessingMode {
369    #[default]
370    Default,
371    Raw,
372}
373
374/// Identifies a source file.
375#[derive(PartialEq, Eq, Copy, Clone, Default, Debug)]
376pub struct SourceId(u32);
377
378impl From<usize> for SourceId {
379    fn from(value: usize) -> Self {
380        SourceId(value as u32)
381    }
382}
383
384impl From<SourceId> for usize {
385    fn from(value: SourceId) -> Self {
386        value.0 as usize
387    }
388}
389
390impl Display for SourceId {
391    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
392        write!(f, "{}", self.0)
393    }
394}
395
396/// Identifies a span of text within its source file.
397pub type Span = chumsky::span::SimpleSpan<usize, SourceId>;
398
399/// A Spanned item is one located within its source file.
400/// The span is invisible with respect to equality and hashing.
401#[derive(Clone, Debug)]
402pub struct Spanned<T> {
403    pub(crate) item: T,
404    pub(crate) span: Span,
405}
406
407/// Spanned item may deref as simple item for convenience.
408impl<T> Deref for Spanned<T> {
409    type Target = T;
410
411    fn deref(&self) -> &Self::Target {
412        &self.item
413    }
414}
415
416/// Spanned item may deref mut as simple item for convenience.
417impl<T> DerefMut for Spanned<T> {
418    fn deref_mut(&mut self) -> &mut Self::Target {
419        &mut self.item
420    }
421}
422
423pub fn spanned<T>(item: T, span: Span) -> Spanned<T> {
424    Spanned { item, span }
425}
426
427/// for use with `map_with`
428pub(crate) fn spanned_extra<'a, 'b, T, I, E>(item: T, e: &mut MapExtra<'a, 'b, I, E>) -> Spanned<T>
429where
430    I: Input<'a, Span = Span>,
431    E: ParserExtra<'a, I>,
432{
433    Spanned {
434        item,
435        span: e.span(),
436    }
437}
438
439impl<T> Spanned<T> {
440    /// Field accessor.
441    pub fn item(&self) -> &T {
442        &self.item
443    }
444
445    /// Field accessor.
446    pub fn span(&self) -> &Span {
447        &self.span
448    }
449
450    /// Converts from `&Spanned<T>` to `Spanned<&T>`.
451    pub fn as_ref(&self) -> Spanned<&T> {
452        Spanned {
453            item: &self.item,
454            span: self.span,
455        }
456    }
457
458    /// Maps a `Spanned<T>` to `Spanned<U>` by applying a function to the contained item, keeping the same span.
459    pub fn map<U, F>(&self, f: F) -> Spanned<U>
460    where
461        F: FnOnce(&T) -> U,
462    {
463        Spanned {
464            item: f(&self.item),
465            span: self.span,
466        }
467    }
468}
469
470impl<T> Spanned<T>
471where
472    T: ElementType,
473{
474    /// Create a new `Error` referring to the spanned element.
475    pub fn error<S: Into<String>>(&self, reason: S) -> Error {
476        Error::new(
477            format!("invalid {}", self.element_type()),
478            reason,
479            self.span,
480        )
481    }
482
483    pub fn error_with_contexts<S: Into<String>>(
484        &self,
485        reason: S,
486        contexts: Vec<(String, Span)>,
487    ) -> Error {
488        Error::with_contexts(
489            format!("invalid {}", self.element_type()),
490            reason,
491            self.span,
492            contexts,
493        )
494    }
495
496    /// Create a new `Warning` referring to the spanned element.
497    pub fn warning<S: Into<String>>(&self, reason: S) -> Warning {
498        Warning::new(
499            format!("questionable {}", self.element_type()),
500            reason,
501            self.span,
502        )
503    }
504}
505
506impl<T> PartialEq for Spanned<T>
507where
508    T: PartialEq,
509{
510    fn eq(&self, other: &Self) -> bool {
511        self.item.eq(&other.item)
512    }
513}
514
515impl<T> Eq for Spanned<T> where T: Eq {}
516
517impl<T> Hash for Spanned<T>
518where
519    T: Hash,
520{
521    fn hash<H: Hasher>(&self, state: &mut H) {
522        self.item.hash(state)
523    }
524}
525
526impl<T> Copy for Spanned<T> where T: Copy {}
527
528impl<T> Display for Spanned<T>
529where
530    T: Display,
531{
532    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
533        write!(f, "{}", self.item,)
534    }
535}
536
537/// convenenience trait for dealing with Option<&Spanned>
538pub trait OptionalItem<T> {
539    fn item(&self) -> Option<&T>;
540}
541
542impl<T> OptionalItem<T> for Option<&Spanned<T>> {
543    fn item(&self) -> Option<&T> {
544        self.map(|spanned| spanned.item())
545    }
546}
547
548/// Implemented by any element, for access to its kind in error reporting.
549pub trait ElementType {
550    fn element_type(&self) -> &'static str;
551}
552
553// blanket implementation for references
554impl<T> ElementType for &T
555where
556    T: ElementType,
557{
558    fn element_type(&self) -> &'static str {
559        (**self).element_type()
560    }
561}
562
563impl<T> ElementType for &mut T
564where
565    T: ElementType,
566{
567    fn element_type(&self) -> &'static str {
568        (**self).element_type()
569    }
570}
571
572impl<T> ElementType for Box<T>
573where
574    T: ElementType,
575{
576    fn element_type(&self) -> &'static str {
577        (**self).element_type()
578    }
579}
580
581#[derive(PartialEq, Eq, Clone, Debug)]
582pub(crate) enum Declaration<'a> {
583    Directive(Directive<'a>),
584    Pragma(Pragma<'a>),
585}
586
587/// A Beancount directive of a particular [DirectiveVariant].
588#[derive(PartialEq, Eq, Clone, Debug)]
589pub struct Directive<'a> {
590    pub(crate) date: Spanned<Date>,
591    pub(crate) metadata: Spanned<Metadata<'a>>,
592    pub(crate) variant: DirectiveVariant<'a>,
593}
594
595impl Directive<'_> {
596    /// Field accessor.
597    pub fn date(&self) -> &Spanned<Date> {
598        &self.date
599    }
600
601    /// Field accessor.
602    pub fn metadata(&self) -> &Metadata {
603        &self.metadata
604    }
605
606    /// Field accessor.
607    pub fn variant(&self) -> &DirectiveVariant {
608        &self.variant
609    }
610}
611
612impl ElementType for Directive<'_> {
613    fn element_type(&self) -> &'static str {
614        use DirectiveVariant::*;
615
616        match &self.variant {
617            Transaction(_) => "transaction",
618            Price(_) => "price",
619            Balance(_) => "balance",
620            Open(_) => "open",
621            Close(_) => "close",
622            Commodity(_) => "commodity",
623            Pad(_) => "pad",
624            Document(_) => "document",
625            Note(_) => "note",
626            Event(_) => "event",
627            Query(_) => "query",
628        }
629    }
630}
631
632impl Display for Directive<'_> {
633    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
634        use DirectiveVariant::*;
635
636        match &self.variant {
637            Transaction(x) => x.fmt(f, self.date.item, &self.metadata),
638            Price(x) => x.fmt(f, self.date.item, &self.metadata),
639            Balance(x) => x.fmt(f, self.date.item, &self.metadata),
640            Open(x) => x.fmt(f, self.date.item, &self.metadata),
641            Close(x) => x.fmt(f, self.date.item, &self.metadata),
642            Commodity(x) => x.fmt(f, self.date.item, &self.metadata),
643            Pad(x) => x.fmt(f, self.date.item, &self.metadata),
644            Document(x) => x.fmt(f, self.date.item, &self.metadata),
645            Note(x) => x.fmt(f, self.date.item, &self.metadata),
646            Event(x) => x.fmt(f, self.date.item, &self.metadata),
647            Query(x) => x.fmt(f, self.date.item, &self.metadata),
648        }
649    }
650}
651
652/// A Beancount directive, without the fields common to all, which belong to [Directive].
653#[derive(PartialEq, Eq, Clone, Debug)]
654pub enum DirectiveVariant<'a> {
655    Transaction(Transaction<'a>),
656    Price(Price<'a>),
657    Balance(Balance<'a>),
658    Open(Open<'a>),
659    Close(Close<'a>),
660    Commodity(Commodity<'a>),
661    Pad(Pad<'a>),
662    Document(Document<'a>),
663    Note(Note<'a>),
664    Event(Event<'a>),
665    Query(Query<'a>),
666    // TODO Custom
667}
668
669#[derive(PartialEq, Eq, Clone, Debug)]
670pub(crate) enum Pragma<'a> {
671    // we keep pushed tags with their span
672    // since it may be useful downstream to know where these came from
673    Pushtag(Spanned<Tag<'a>>),
674    Poptag(Spanned<Tag<'a>>),
675    Pushmeta(MetaKeyValue<'a>),
676    Popmeta(Spanned<Key<'a>>),
677    Include(Spanned<&'a str>),
678    Option(BeancountOption<'a>),
679    Plugin(Plugin<'a>),
680}
681
682/// A Beancount transaction directive, without the common [Directive] fields.
683#[derive(PartialEq, Eq, Clone, Debug)]
684pub struct Transaction<'a> {
685    pub(crate) flag: Spanned<Flag>,
686    pub(crate) payee: Option<Spanned<&'a str>>,
687    pub(crate) narration: Option<Spanned<&'a str>>,
688    pub(crate) postings: Vec<Spanned<Posting<'a>>>,
689}
690
691impl Transaction<'_> {
692    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
693        write!(f, "{} {}", date, self.flag)?;
694
695        format(f, &self.payee, double_quoted, " ", Some(" "))?;
696        format(f, &self.narration, double_quoted, " ", Some(" "))?;
697        // we prefer to show tags and links inline rather then line by line in metadata
698        metadata.fmt_tags_links_inline(f)?;
699        metadata.fmt_keys_values(f)?;
700        format(
701            f,
702            &self.postings,
703            plain,
704            NEWLINE_INDENT,
705            Some(NEWLINE_INDENT),
706        )
707    }
708
709    /// Field accessor.
710    pub fn flag(&self) -> &Spanned<Flag> {
711        &self.flag
712    }
713
714    /// Field accessor.
715    pub fn payee(&self) -> Option<&Spanned<&str>> {
716        self.payee.as_ref()
717    }
718
719    /// Field accessor.
720    pub fn narration(&self) -> Option<&Spanned<&str>> {
721        self.narration.as_ref()
722    }
723
724    /// Field accessor.
725    pub fn postings(&self) -> impl ExactSizeIterator<Item = &Spanned<Posting>> {
726        self.postings.iter()
727    }
728}
729
730/// A Beancount price directive, without the common [Directive] fields.
731#[derive(PartialEq, Eq, Clone, Debug)]
732pub struct Price<'a> {
733    pub(crate) currency: Spanned<Currency<'a>>,
734    pub(crate) amount: Spanned<Amount<'a>>,
735}
736
737impl Price<'_> {
738    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
739        write!(f, "{} price {} {}", date, &self.currency, &self.amount)?;
740
741        // we prefer to show tags and links inline rather then line by line in metadata
742        metadata.fmt_tags_links_inline(f)?;
743        metadata.fmt_keys_values(f)?;
744
745        Ok(())
746    }
747
748    /// Field accessor.
749    pub fn currency(&self) -> &Spanned<Currency> {
750        &self.currency
751    }
752
753    /// Field accessor.
754    pub fn amount(&self) -> &Spanned<Amount> {
755        &self.amount
756    }
757}
758
759impl ElementType for Price<'_> {
760    fn element_type(&self) -> &'static str {
761        "price"
762    }
763}
764
765/// A Beancount balance directive, without the common [Directive] fields.
766#[derive(PartialEq, Eq, Clone, Debug)]
767pub struct Balance<'a> {
768    pub(crate) account: Spanned<Account<'a>>,
769    pub(crate) atol: Spanned<AmountWithTolerance<'a>>,
770}
771
772impl Balance<'_> {
773    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
774        write!(f, "{} balance {} {}", date, &self.account, &self.atol)?;
775
776        // we prefer to show tags and links inline rather then line by line in metadata
777        metadata.fmt_tags_links_inline(f)?;
778        metadata.fmt_keys_values(f)?;
779
780        Ok(())
781    }
782
783    /// Field accessor.
784    pub fn account(&self) -> &Spanned<Account> {
785        &self.account
786    }
787
788    /// Field accessor.
789    pub fn atol(&self) -> &Spanned<AmountWithTolerance> {
790        &self.atol
791    }
792}
793
794impl ElementType for Balance<'_> {
795    fn element_type(&self) -> &'static str {
796        "balance"
797    }
798}
799
800/// A Beancount open directive, without the common [Directive] fields.
801#[derive(PartialEq, Eq, Clone, Debug)]
802pub struct Open<'a> {
803    pub(crate) account: Spanned<Account<'a>>,
804    pub(crate) currencies: HashSet<Spanned<Currency<'a>>>,
805    pub(crate) booking: Option<Spanned<Booking>>,
806}
807
808impl Open<'_> {
809    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
810        write!(f, "{} open {}", date, self.account)?;
811        format(f, &self.currencies, plain, ",", Some(" "))?;
812        format(f, &self.booking, double_quoted, " ", Some(" "))?;
813        // we prefer to show tags and links inline rather then line by line in metadata
814        metadata.fmt_tags_links_inline(f)?;
815        metadata.fmt_keys_values(f)
816    }
817
818    /// Field accessor.
819    pub fn account(&self) -> &Spanned<Account> {
820        &self.account
821    }
822
823    /// Field accessor.
824    pub fn currencies(&self) -> impl ExactSizeIterator<Item = &Spanned<Currency>> {
825        self.currencies.iter()
826    }
827
828    /// Field accessor.
829    pub fn booking(&self) -> Option<&Spanned<Booking>> {
830        self.booking.as_ref()
831    }
832}
833
834/// A Beancount close directive, without the common [Directive] fields.
835#[derive(PartialEq, Eq, Clone, Debug)]
836pub struct Close<'a> {
837    pub(crate) account: Spanned<Account<'a>>,
838}
839
840impl Close<'_> {
841    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
842        write!(f, "{} close {}", date, self.account)?;
843        // we prefer to show tags and links inline rather then line by line in metadata
844        metadata.fmt_tags_links_inline(f)?;
845        metadata.fmt_keys_values(f)
846    }
847
848    /// Field accessor.
849    pub fn account(&self) -> &Spanned<Account> {
850        &self.account
851    }
852}
853
854/// A Beancount commodity directive, without the common [Directive] fields.
855#[derive(PartialEq, Eq, Clone, Debug)]
856pub struct Commodity<'a> {
857    pub(crate) currency: Spanned<Currency<'a>>,
858}
859
860impl Commodity<'_> {
861    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
862        write!(f, "{} commodity {}", date, self.currency)?;
863        // we prefer to show tags and links inline rather then line by line in metadata
864        metadata.fmt_tags_links_inline(f)?;
865        metadata.fmt_keys_values(f)
866    }
867
868    /// Field accessor.
869    pub fn currency(&self) -> &Spanned<Currency> {
870        &self.currency
871    }
872}
873
874/// A Beancount pad directive, without the common [Directive] fields.
875#[derive(PartialEq, Eq, Clone, Debug)]
876pub struct Pad<'a> {
877    pub(crate) account: Spanned<Account<'a>>,
878    pub(crate) source: Spanned<Account<'a>>,
879}
880
881impl Pad<'_> {
882    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
883        write!(f, "{} pad {} {}", date, self.account, self.source)?;
884        // we prefer to show tags and links inline rather then line by line in metadata
885        metadata.fmt_tags_links_inline(f)?;
886        metadata.fmt_keys_values(f)
887    }
888
889    /// Field accessor.
890    pub fn account(&self) -> &Spanned<Account> {
891        &self.account
892    }
893
894    /// Field accessor.
895    pub fn source(&self) -> &Spanned<Account> {
896        &self.source
897    }
898}
899
900/// A Beancount document directive, without the common [Directive] fields.
901#[derive(PartialEq, Eq, Clone, Debug)]
902pub struct Document<'a> {
903    pub(crate) account: Spanned<Account<'a>>,
904    pub(crate) path: Spanned<&'a str>,
905}
906
907impl Document<'_> {
908    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
909        write!(f, "{} document {} \"{}\"", date, self.account, self.path)?;
910        // we prefer to show tags and links inline rather then line by line in metadata
911        metadata.fmt_tags_links_inline(f)?;
912        metadata.fmt_keys_values(f)
913    }
914
915    /// Field accessor.
916    pub fn account(&self) -> &Spanned<Account> {
917        &self.account
918    }
919
920    /// The path to the document, possibly relative to a directory given by options.
921    /// No check is made as to validity of the path or existence of the file.
922    pub fn path(&self) -> &Spanned<&str> {
923        &self.path
924    }
925}
926
927/// A Beancount note directive, without the common [Directive] fields.
928#[derive(PartialEq, Eq, Clone, Debug)]
929pub struct Note<'a> {
930    pub(crate) account: Spanned<Account<'a>>,
931    pub(crate) comment: Spanned<&'a str>,
932}
933
934impl Note<'_> {
935    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
936        write!(f, "{} note {} \"{}\"", date, self.account, self.comment)?;
937        // we prefer to show tags and links inline rather then line by line in metadata
938        metadata.fmt_tags_links_inline(f)?;
939        metadata.fmt_keys_values(f)
940    }
941
942    /// Field accessor.
943    pub fn account(&self) -> &Spanned<Account> {
944        &self.account
945    }
946
947    /// Field accessor.
948    pub fn comment(&self) -> &Spanned<&str> {
949        &self.comment
950    }
951}
952
953/// A Beancount event directive, without the common [Directive] fields.
954#[derive(PartialEq, Eq, Clone, Debug)]
955pub struct Event<'a> {
956    pub(crate) event_type: Spanned<&'a str>,
957    pub(crate) description: Spanned<&'a str>,
958}
959
960impl Event<'_> {
961    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
962        write!(
963            f,
964            "{} event \"{}\" \"{}\"",
965            date, self.event_type, self.description
966        )?;
967        // we prefer to show tags and links inline rather then line by line in metadata
968        metadata.fmt_tags_links_inline(f)?;
969        metadata.fmt_keys_values(f)
970    }
971
972    /// Field accessor.
973    pub fn event_type(&self) -> &Spanned<&str> {
974        &self.event_type
975    }
976
977    /// Field accessor.
978    pub fn description(&self) -> &Spanned<&str> {
979        &self.description
980    }
981}
982
983/// A Beancount query directive, without the common [Directive] fields.
984#[derive(PartialEq, Eq, Clone, Debug)]
985pub struct Query<'a> {
986    pub(crate) name: Spanned<&'a str>,
987    pub(crate) content: Spanned<&'a str>,
988}
989
990impl Query<'_> {
991    fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
992        write!(f, "{} query \"{}\" \"{}\"", date, self.name, self.content)?;
993        // we prefer to show tags and links inline rather then line by line in metadata
994        metadata.fmt_tags_links_inline(f)?;
995        metadata.fmt_keys_values(f)
996    }
997
998    /// Field accessor.
999    pub fn name(&self) -> &Spanned<&str> {
1000        &self.name
1001    }
1002
1003    /// Field accessor.
1004    pub fn content(&self) -> &Spanned<&str> {
1005        &self.content
1006    }
1007}
1008
1009/// A Beancount plugin pragma.
1010#[derive(PartialEq, Eq, Clone, Debug)]
1011pub struct Plugin<'a> {
1012    pub(crate) module_name: Spanned<&'a str>,
1013    pub(crate) config: Option<Spanned<&'a str>>,
1014}
1015
1016impl Plugin<'_> {
1017    /// Field accessor.
1018    pub fn module_name(&self) -> &Spanned<&str> {
1019        &self.module_name
1020    }
1021
1022    /// Field accessor.
1023    pub fn config(&self) -> Option<&Spanned<&str>> {
1024        self.config.as_ref()
1025    }
1026}
1027
1028impl Display for Plugin<'_> {
1029    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1030        write!(f, "plugin \"{}\"", self.module_name)?;
1031        if let Some(config) = &self.config {
1032            write!(f, " \"{}\"", config)?;
1033        }
1034        writeln!(f)
1035    }
1036}
1037
1038/// A Beancount account, simply a string after account type validation.
1039#[derive(PartialEq, Eq, Clone, Debug)]
1040pub struct Account<'a> {
1041    account_type: AccountType,
1042    name: &'a str,
1043}
1044
1045impl<'a> Account<'a> {
1046    ///constructor
1047    pub(crate) fn new(
1048        s: &'a str,
1049        account_type_names: &AccountTypeNames,
1050    ) -> Result<Self, AccountError> {
1051        let mut account = s.split(':');
1052        let account_type_name = AccountTypeName::try_from(
1053            account
1054                .by_ref()
1055                .next()
1056                .ok_or(AccountError(AccountErrorKind::MissingColon))?,
1057        )
1058        .map_err(|e| AccountError(AccountErrorKind::TypeName(e)))?;
1059        for subaccount in account {
1060            let _ = AccountName::try_from(subaccount)
1061                .map_err(|e| AccountError(AccountErrorKind::AccountName(e)))?;
1062        }
1063
1064        match account_type_names.get(&account_type_name) {
1065            Some(account_type) => Ok(Account {
1066                account_type,
1067                name: s,
1068            }),
1069            None => Err(AccountError(AccountErrorKind::UnknownAccountType(format!(
1070                "unknown account type {}, must be one of {}",
1071                &account_type_name, account_type_names
1072            )))),
1073        }
1074    }
1075
1076    pub fn account_type(&self) -> AccountType {
1077        self.account_type
1078    }
1079}
1080
1081impl Display for Account<'_> {
1082    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1083        write!(f, "{}", &self.name)
1084    }
1085}
1086
1087impl AsRef<str> for Account<'_> {
1088    fn as_ref(&self) -> &str {
1089        self.name
1090    }
1091}
1092
1093/// Error type for [AccountTypeName] creation.
1094#[derive(PartialEq, Eq, Debug)]
1095pub struct AccountError(AccountErrorKind);
1096
1097#[derive(PartialEq, Eq, Debug)]
1098enum AccountErrorKind {
1099    MissingColon,
1100    TypeName(AccountTypeNameError),
1101    AccountName(AccountNameError),
1102    UnknownAccountType(String),
1103}
1104
1105impl Display for AccountError {
1106    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1107        use AccountErrorKind::*;
1108        match &self.0 {
1109            MissingColon => f.write_str("missing colon"),
1110            TypeName(e) => write!(f, "{}", e),
1111            AccountName(e) => write!(f, "{}", e),
1112            UnknownAccountType(e) => write!(f, "{}", e),
1113        }
1114    }
1115}
1116
1117impl std::error::Error for AccountError {}
1118
1119impl ElementType for Account<'_> {
1120    fn element_type(&self) -> &'static str {
1121        "account"
1122    }
1123}
1124
1125/// an account without the account type prefix
1126#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1127pub struct Subaccount<'a>(&'a str);
1128
1129impl AsRef<str> for Subaccount<'_> {
1130    fn as_ref(&self) -> &str {
1131        self.0
1132    }
1133}
1134
1135impl Display for Subaccount<'_> {
1136    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1137        write!(f, "{}", &self.0)
1138    }
1139}
1140
1141impl<'a> TryFrom<&'a str> for Subaccount<'a> {
1142    type Error = AccountNameError;
1143
1144    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1145        let mut empty = true;
1146        for account_name in s.split(':') {
1147            _ = AccountName::try_from(account_name)?;
1148            empty = false;
1149        }
1150
1151        if empty {
1152            Err(AccountNameError(AccountNameErrorKind::Empty))
1153        } else {
1154            Ok(Subaccount(s))
1155        }
1156    }
1157}
1158
1159/// A validated name for an account type.
1160#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1161pub struct AccountTypeName<'a>(&'a str);
1162
1163impl AccountTypeName<'_> {
1164    pub(crate) fn is_valid_initial(c: &char) -> bool {
1165        c.is_ascii_uppercase()
1166    }
1167
1168    pub(crate) fn is_valid_subsequent(c: &char) -> bool {
1169        c.is_alphanumeric() || *c == '-'
1170    }
1171}
1172
1173impl<'a> TryFrom<&'a str> for AccountTypeName<'a> {
1174    type Error = AccountTypeNameError;
1175
1176    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1177        use AccountTypeNameErrorKind::*;
1178        if s.is_empty() {
1179            Err(AccountTypeNameError(Empty))
1180        } else {
1181            let mut chars = s.chars();
1182            let initial = chars.next().unwrap();
1183            if !AccountTypeName::is_valid_initial(&initial) {
1184                Err(AccountTypeNameError(Initial(initial)))
1185            } else {
1186                let bad_chars = chars
1187                    .filter(|c| (!AccountTypeName::is_valid_subsequent(c)))
1188                    .collect::<Vec<char>>();
1189                if bad_chars.is_empty() {
1190                    Ok(AccountTypeName(s))
1191                } else {
1192                    Err(AccountTypeNameError(Subsequent(bad_chars)))
1193                }
1194            }
1195        }
1196    }
1197}
1198
1199impl ElementType for AccountTypeName<'_> {
1200    fn element_type(&self) -> &'static str {
1201        "account type name"
1202    }
1203}
1204
1205impl AsRef<str> for AccountTypeName<'_> {
1206    fn as_ref(&self) -> &str {
1207        self.0
1208    }
1209}
1210
1211impl PartialEq<&str> for AccountTypeName<'_> {
1212    fn eq(&self, other: &&str) -> bool {
1213        self.0 == *other
1214    }
1215}
1216
1217impl Display for AccountTypeName<'_> {
1218    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1219        write!(f, "{}", &self.0)
1220    }
1221}
1222
1223/// Error type for [AccountTypeName] creation.
1224#[derive(PartialEq, Eq, Debug)]
1225pub struct AccountTypeNameError(AccountTypeNameErrorKind);
1226
1227#[derive(PartialEq, Eq, Debug)]
1228enum AccountTypeNameErrorKind {
1229    Empty,
1230    Initial(char),
1231    Subsequent(Vec<char>),
1232}
1233
1234impl Display for AccountTypeNameError {
1235    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1236        use AccountTypeNameErrorKind::*;
1237        match &self.0 {
1238            Empty => write!(f, "empty account name"),
1239            Initial(bad_char) => write!(
1240                f,
1241                "invalid character '{}' for account name initial - must be uppercase ASCII letter",
1242                bad_char
1243            ),
1244            Subsequent(bad_chars) => {
1245                format(
1246                    f,
1247                    bad_chars,
1248                    single_quoted,
1249                    ", ",
1250                    Some("invalid character "),
1251                )?;
1252                f.write_str(" in account name - must be alphanumeric or '-'")
1253            }
1254        }
1255    }
1256}
1257
1258impl std::error::Error for AccountTypeNameError {}
1259
1260/// One component of a colon-separated account.
1261#[derive(PartialEq, Eq, Copy, Clone, Debug)]
1262pub struct AccountName<'a>(&'a str);
1263
1264impl AccountName<'_> {
1265    pub(crate) fn is_valid_initial(c: &char) -> bool {
1266        c.is_ascii_uppercase() || c.is_ascii_digit()
1267    }
1268
1269    pub(crate) fn is_valid_subsequent(c: &char) -> bool {
1270        c.is_alphanumeric() || *c == '-'
1271    }
1272}
1273
1274impl ElementType for AccountName<'_> {
1275    fn element_type(&self) -> &'static str {
1276        "account name"
1277    }
1278}
1279
1280impl AsRef<str> for AccountName<'_> {
1281    fn as_ref(&self) -> &str {
1282        self.0
1283    }
1284}
1285
1286impl PartialEq<&str> for AccountName<'_> {
1287    fn eq(&self, other: &&str) -> bool {
1288        self.0 == *other
1289    }
1290}
1291
1292impl Display for AccountName<'_> {
1293    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1294        write!(f, "{}", &self.0)
1295    }
1296}
1297
1298/// Error type for [AccountName] creation.
1299#[derive(PartialEq, Eq, Debug)]
1300pub struct AccountNameError(AccountNameErrorKind);
1301
1302#[derive(PartialEq, Eq, Debug)]
1303enum AccountNameErrorKind {
1304    Empty,
1305    Initial(char),
1306    Subsequent(Vec<char>),
1307}
1308
1309impl Display for AccountNameError {
1310    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1311        use AccountNameErrorKind::*;
1312        match &self.0 {
1313            Empty => write!(f, "empty account name"),
1314            Initial(bad_char) => write!(
1315                f,
1316                "invalid character '{}' for account name initial - must be uppercase ASCII letter or digit",
1317                bad_char
1318            ),
1319            Subsequent(bad_chars) => {
1320                format(f, bad_chars, single_quoted, ", ", Some("invalid character "))?;
1321                f.write_str(" in account name - must be alphanumeric or '-'")
1322            }
1323        }
1324    }
1325}
1326
1327impl std::error::Error for AccountNameError {}
1328
1329impl<'a> TryFrom<&'a str> for AccountName<'a> {
1330    type Error = AccountNameError;
1331
1332    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1333        use AccountNameErrorKind::*;
1334        if s.is_empty() {
1335            Err(AccountNameError(Empty))
1336        } else {
1337            let mut chars = s.chars();
1338            let initial = chars.next().unwrap();
1339            if !AccountName::is_valid_initial(&initial) {
1340                Err(AccountNameError(Initial(initial)))
1341            } else {
1342                let bad_chars = chars
1343                    .filter(|c| (!AccountName::is_valid_subsequent(c)))
1344                    .collect::<Vec<char>>();
1345                if bad_chars.is_empty() {
1346                    Ok(AccountName(s))
1347                } else {
1348                    Err(AccountNameError(Subsequent(bad_chars)))
1349                }
1350            }
1351        }
1352    }
1353}
1354
1355/// A Beancount currency.
1356#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1357pub struct Currency<'a>(&'a str);
1358
1359/// The valid intermediate characters for currency, in addition to ASCII uppercase and digits
1360const CURRENCY_INTERMEDIATE_EXTRA_CHARS: [char; 4] = ['\'', '.', '_', '-'];
1361
1362impl Currency<'_> {
1363    fn is_valid_initial(c: &char) -> bool {
1364        c.is_ascii_uppercase() || *c == '/'
1365    }
1366
1367    fn is_valid_intermediate(c: &char) -> bool {
1368        c.is_ascii_uppercase()
1369            || c.is_ascii_digit()
1370            || CURRENCY_INTERMEDIATE_EXTRA_CHARS.contains(c)
1371    }
1372
1373    fn is_valid_final(c: &char) -> bool {
1374        c.is_ascii_uppercase() || c.is_ascii_digit()
1375    }
1376}
1377
1378impl ElementType for Currency<'_> {
1379    fn element_type(&self) -> &'static str {
1380        "currency"
1381    }
1382}
1383
1384impl AsRef<str> for Currency<'_> {
1385    fn as_ref(&self) -> &str {
1386        self.0
1387    }
1388}
1389
1390impl PartialEq<&str> for Currency<'_> {
1391    fn eq(&self, other: &&str) -> bool {
1392        self.0 == *other
1393    }
1394}
1395
1396impl Display for Currency<'_> {
1397    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1398        write!(f, "{}", &self.0)
1399    }
1400}
1401
1402/// Error type for [Currency] creation.
1403#[derive(PartialEq, Eq, Debug)]
1404pub struct CurrencyError(CurrencyErrorKind);
1405
1406#[derive(PartialEq, Eq, Debug)]
1407enum CurrencyErrorKind {
1408    Empty,
1409    Initial(char),
1410    Intermediate(Vec<char>),
1411    Final(char),
1412    MissingLetter,
1413}
1414
1415impl Display for CurrencyError {
1416    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1417        use CurrencyErrorKind::*;
1418        match &self.0 {
1419            Empty => write!(f, "empty currency"),
1420            Initial(bad_char) => write!(
1421                f,
1422                "invalid initial character '{}' for currency - must be uppercase ASCII letter or '/'",
1423                bad_char
1424            ),
1425            Intermediate(bad_chars) => {
1426                format(f, bad_chars, single_quoted, ", ", Some("invalid intermediate characters "))?;
1427                format(f, CURRENCY_INTERMEDIATE_EXTRA_CHARS, single_quoted, ", ", Some(" for currency - must be upppercase ASCII alphanumeric or one of "))
1428            }
1429            Final(bad_char) => write!(
1430                f,
1431                "invalid final character '{}' for currency - must be uppercase ASCII alphanumeric",
1432                bad_char
1433            ),
1434            MissingLetter => write!(f, "currency must contain at least one letter")
1435        }
1436    }
1437}
1438
1439impl std::error::Error for CurrencyError {}
1440
1441impl<'a> TryFrom<&'a str> for Currency<'a> {
1442    type Error = CurrencyError;
1443
1444    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1445        use CurrencyErrorKind::*;
1446        if s.is_empty() {
1447            Err(CurrencyError(Empty))
1448        } else {
1449            let mut chars = s.chars();
1450            let first = chars.next().unwrap();
1451            let intermediate: Vec<char> = if s.len() > 2 {
1452                chars.by_ref().take(s.len() - 2).collect()
1453            } else {
1454                empty::<char>().collect()
1455            };
1456            let last = if s.len() > 1 {
1457                chars.next().unwrap()
1458            } else {
1459                first
1460            };
1461
1462            use CurrencyErrorKind::*;
1463            if !Currency::is_valid_initial(&first) {
1464                Err(CurrencyError(Initial(first)))
1465            } else if !Currency::is_valid_final(&last) {
1466                Err(CurrencyError(Final(last)))
1467            } else {
1468                let bad_intermediates = intermediate
1469                    .into_iter()
1470                    .filter(|c| (!Currency::is_valid_intermediate(c)))
1471                    .collect::<Vec<char>>();
1472                if !bad_intermediates.is_empty() {
1473                    Err(CurrencyError(Intermediate(bad_intermediates)))
1474                } else if s.find(|c: char| c.is_ascii_uppercase()).is_none() {
1475                    Err(CurrencyError(MissingLetter))
1476                } else {
1477                    Ok(Currency(s))
1478                }
1479            }
1480        }
1481    }
1482}
1483
1484/// A single posting within a [Transaction].
1485#[derive(PartialEq, Eq, Clone, Debug)]
1486pub struct Posting<'a> {
1487    pub(crate) flag: Option<Spanned<Flag>>,
1488    pub(crate) account: Spanned<Account<'a>>,
1489    pub(crate) amount: Option<Spanned<ExprValue>>,
1490    pub(crate) currency: Option<Spanned<Currency<'a>>>,
1491    pub(crate) cost_spec: Option<Spanned<CostSpec<'a>>>,
1492    pub(crate) price_annotation: Option<Spanned<PriceSpec<'a>>>,
1493    pub(crate) metadata: Spanned<Metadata<'a>>,
1494}
1495
1496impl<'a> Posting<'a> {
1497    /// Field accessor.
1498    pub fn flag(&self) -> Option<&Spanned<Flag>> {
1499        self.flag.as_ref()
1500    }
1501
1502    /// Field accessor.
1503    pub fn account(&self) -> &Spanned<Account> {
1504        &self.account
1505    }
1506
1507    /// Field accessor.
1508    pub fn amount(&self) -> Option<&Spanned<ExprValue>> {
1509        self.amount.as_ref()
1510    }
1511
1512    /// Field accessor.
1513    pub fn currency(&self) -> Option<&Spanned<Currency>> {
1514        self.currency.as_ref()
1515    }
1516
1517    /// Field accessor.
1518    pub fn cost_spec(&self) -> Option<&Spanned<CostSpec>> {
1519        self.cost_spec.as_ref()
1520    }
1521
1522    /// Field accessor.
1523    pub fn price_annotation(&self) -> Option<&Spanned<PriceSpec>> {
1524        self.price_annotation.as_ref()
1525    }
1526
1527    /// Field accessor.
1528    pub fn metadata(&self) -> &Metadata<'a> {
1529        &self.metadata
1530    }
1531}
1532
1533impl ElementType for Posting<'_> {
1534    fn element_type(&self) -> &'static str {
1535        "posting"
1536    }
1537}
1538
1539impl Display for Posting<'_> {
1540    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1541        simple_format(f, self.flag, None)?;
1542
1543        write!(
1544            f,
1545            "{}{}",
1546            if self.flag.is_some() { " " } else { "" },
1547            &self.account
1548        )?;
1549
1550        simple_format(f, &self.amount, Some(" "))?;
1551        simple_format(f, self.currency, Some(" "))?;
1552        simple_format(f, &self.cost_spec, Some(" "))?;
1553        simple_format(f, &self.price_annotation, Some(" @ "))?;
1554
1555        self.metadata.fmt(f)
1556    }
1557}
1558
1559/// Metadata associated with a [Directive] or a [Posting].
1560///
1561/// Note that tags and links and key/values that may have been specified at the top-level
1562/// of a directive are subsumed into the metadata element of the directive.
1563#[derive(PartialEq, Eq, Clone, Default, Debug)]
1564pub struct Metadata<'a> {
1565    pub(crate) key_values: HashMap<Spanned<Key<'a>>, Spanned<MetaValue<'a>>>,
1566    pub(crate) tags: HashSet<Spanned<Tag<'a>>>,
1567    pub(crate) links: HashSet<Spanned<Link<'a>>>,
1568}
1569
1570impl Metadata<'_> {
1571    /// is the metadata empty?
1572    pub fn is_empty(&self) -> bool {
1573        self.key_values().len() == 0 && self.tags().len() == 0 && self.links().len() == 0
1574    }
1575
1576    /// Field accessor.
1577    pub fn key_values(
1578        &self,
1579    ) -> impl ExactSizeIterator<Item = (&Spanned<Key>, &Spanned<MetaValue>)> {
1580        self.key_values.iter()
1581    }
1582
1583    /// Field accessor.
1584    pub fn key_value<'s, 'k>(&'s self, key: Key<'k>) -> Option<&'s Spanned<MetaValue<'s>>>
1585    where
1586        'k: 's,
1587    {
1588        // this is a bit gross because we don't have a span,
1589        // so we cheat and use any span we know, which is OK because it doesn't matter
1590        match self.key_values.keys().next() {
1591            None => None,
1592            Some(arbitrary_spanned_key) => {
1593                let key = spanned(key, *arbitrary_spanned_key.span());
1594                self.key_values.get(&key)
1595            }
1596        }
1597    }
1598
1599    /// Field accessor.
1600    pub fn tags(&self) -> impl ExactSizeIterator<Item = &Spanned<Tag>> {
1601        self.tags.iter()
1602    }
1603
1604    /// Field accessor.
1605    pub fn links(&self) -> impl ExactSizeIterator<Item = &Spanned<Link>> {
1606        self.links.iter()
1607    }
1608
1609    pub(crate) fn fmt_tags_links_inline(&self, f: &mut Formatter<'_>) -> fmt::Result {
1610        format(f, &self.tags, plain, SPACE, Some(SPACE))?;
1611        format(f, &self.links, plain, SPACE, Some(SPACE))
1612    }
1613
1614    pub(crate) fn fmt_keys_values(&self, f: &mut Formatter<'_>) -> fmt::Result {
1615        format(
1616            f,
1617            &self.key_values,
1618            key_value,
1619            NEWLINE_INDENT,
1620            Some(NEWLINE_INDENT),
1621        )
1622    }
1623}
1624
1625impl Display for Metadata<'_> {
1626    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1627        self.fmt_keys_values(f)?;
1628        format(f, &self.tags, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))?;
1629        format(f, &self.links, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))
1630    }
1631}
1632
1633#[derive(PartialEq, Eq, Clone, Debug)]
1634pub(crate) struct MetaKeyValue<'a> {
1635    pub(crate) key: Spanned<Key<'a>>,
1636    pub(crate) value: Spanned<MetaValue<'a>>,
1637}
1638
1639impl Display for MetaKeyValue<'_> {
1640    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1641        write!(f, "{}: \"{}\"", &self.key, &self.value)
1642    }
1643}
1644
1645/// A value of metadata key/value.
1646#[derive(PartialEq, Eq, Clone, Debug)]
1647pub enum MetaValue<'a> {
1648    Simple(SimpleValue<'a>),
1649    Amount(Amount<'a>),
1650}
1651
1652impl Display for MetaValue<'_> {
1653    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1654        use MetaValue::*;
1655
1656        match self {
1657            Simple(simple_value) => simple_value.fmt(f),
1658            Amount(amount) => amount.fmt(f),
1659        }
1660    }
1661}
1662
1663/// One possible type of [MetaValue].
1664#[derive(PartialEq, Eq, Clone, Debug)]
1665pub enum SimpleValue<'a> {
1666    String(&'a str),
1667    Currency(Currency<'a>),
1668    Account(Account<'a>),
1669    Tag(Tag<'a>),
1670    Link(Link<'a>),
1671    Date(Date),
1672    Bool(bool),
1673    None,
1674    Expr(ExprValue),
1675}
1676
1677impl ElementType for SimpleValue<'_> {
1678    fn element_type(&self) -> &'static str {
1679        "simple value"
1680    }
1681}
1682
1683impl Display for SimpleValue<'_> {
1684    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1685        use SimpleValue::*;
1686
1687        match self {
1688            String(x) => write!(f, r#""{}""#, x),
1689            Currency(x) => x.fmt(f),
1690            Account(x) => x.fmt(f),
1691            Tag(x) => x.fmt(f),
1692            Link(x) => x.fmt(f),
1693            Date(x) => x.fmt(f),
1694            Bool(x) => f.write_str(if *x { "TRUE" } else { "FALSE" }),
1695            None => Ok(()),
1696            Expr(x) => x.fmt(f),
1697        }
1698    }
1699}
1700
1701/// A tag.
1702#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1703pub struct Tag<'a>(pub(crate) TagOrLinkIdentifier<'a>);
1704
1705impl<'a> From<TagOrLinkIdentifier<'a>> for Tag<'a> {
1706    fn from(id: TagOrLinkIdentifier<'a>) -> Self {
1707        Self(id)
1708    }
1709}
1710
1711impl<'a> TryFrom<&'a str> for Tag<'a> {
1712    type Error = TagOrLinkIdentifierError;
1713
1714    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1715        TagOrLinkIdentifier::try_from(s).map(Tag)
1716    }
1717}
1718
1719impl ElementType for Tag<'_> {
1720    fn element_type(&self) -> &'static str {
1721        "tag"
1722    }
1723}
1724
1725impl AsRef<str> for Tag<'_> {
1726    fn as_ref(&self) -> &str {
1727        self.0.as_ref()
1728    }
1729}
1730
1731impl PartialEq<&str> for Tag<'_> {
1732    fn eq(&self, other: &&str) -> bool {
1733        self.0 == *other
1734    }
1735}
1736
1737impl Display for Tag<'_> {
1738    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1739        write!(f, "#{}", self.0 .0)
1740    }
1741}
1742
1743/// A link.
1744#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1745pub struct Link<'a>(pub(crate) TagOrLinkIdentifier<'a>);
1746
1747impl<'a> From<TagOrLinkIdentifier<'a>> for Link<'a> {
1748    fn from(id: TagOrLinkIdentifier<'a>) -> Self {
1749        Self(id)
1750    }
1751}
1752
1753impl<'a> TryFrom<&'a str> for Link<'a> {
1754    type Error = TagOrLinkIdentifierError;
1755
1756    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1757        TagOrLinkIdentifier::try_from(s).map(Link)
1758    }
1759}
1760
1761impl ElementType for Link<'_> {
1762    fn element_type(&self) -> &'static str {
1763        "link"
1764    }
1765}
1766
1767impl PartialEq<&str> for Link<'_> {
1768    fn eq(&self, other: &&str) -> bool {
1769        self.0 == *other
1770    }
1771}
1772
1773impl Display for Link<'_> {
1774    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1775        write!(f, "^{}", self.0 .0)
1776    }
1777}
1778
1779impl AsRef<str> for Link<'_> {
1780    fn as_ref(&self) -> &str {
1781        self.0.as_ref()
1782    }
1783}
1784
1785/// The validated identifier part of a `Tag` or `Link` without the `#` or `^` prefix.
1786#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1787pub struct TagOrLinkIdentifier<'a>(&'a str);
1788
1789/// The valid characters for tags and links besides alphanumeric.
1790const TAG_OR_LINK_EXTRA_CHARS: [char; 4] = ['-', '_', '/', '.'];
1791
1792impl TagOrLinkIdentifier<'_> {
1793    pub(crate) fn is_valid_char(c: &char) -> bool {
1794        c.is_alphanumeric() || TAG_OR_LINK_EXTRA_CHARS.contains(c)
1795    }
1796}
1797
1798/// Error type for [TagOrLinkIdentifier] creation.
1799#[derive(PartialEq, Eq, Debug)]
1800pub struct TagOrLinkIdentifierError(Vec<char>);
1801
1802impl Display for TagOrLinkIdentifierError {
1803    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1804        format(f, &self.0, single_quoted, ", ", Some("invalid characters "))?;
1805        format(
1806            f,
1807            TAG_OR_LINK_EXTRA_CHARS,
1808            single_quoted,
1809            ", ",
1810            Some(" for tag or link identifier - must be alphanumeric or one of "),
1811        )
1812    }
1813}
1814
1815impl std::error::Error for TagOrLinkIdentifierError {}
1816
1817impl<'a> TryFrom<&'a str> for TagOrLinkIdentifier<'a> {
1818    type Error = TagOrLinkIdentifierError;
1819
1820    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1821        let bad_chars = s
1822            .chars()
1823            .filter(|c| (!TagOrLinkIdentifier::is_valid_char(c)))
1824            .collect::<Vec<char>>();
1825        if bad_chars.is_empty() {
1826            Ok(TagOrLinkIdentifier(s))
1827        } else {
1828            Err(TagOrLinkIdentifierError(bad_chars))
1829        }
1830    }
1831}
1832
1833impl AsRef<str> for TagOrLinkIdentifier<'_> {
1834    fn as_ref(&self) -> &str {
1835        self.0
1836    }
1837}
1838
1839impl PartialEq<&str> for TagOrLinkIdentifier<'_> {
1840    fn eq(&self, other: &&str) -> bool {
1841        self.0 == *other
1842    }
1843}
1844
1845/// A key for a [Metadata] [MetaValue].
1846#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1847pub struct Key<'a>(&'a str);
1848
1849impl Key<'_> {
1850    pub(crate) fn is_valid_initial(c: &char) -> bool {
1851        c.is_ascii_lowercase()
1852    }
1853
1854    pub(crate) fn is_valid_subsequent(c: &char) -> bool {
1855        c.is_alphanumeric() || *c == '-' || *c == '_'
1856    }
1857}
1858
1859impl AsRef<str> for Key<'_> {
1860    fn as_ref(&self) -> &str {
1861        self.0
1862    }
1863}
1864
1865impl ElementType for Key<'_> {
1866    fn element_type(&self) -> &'static str {
1867        "key"
1868    }
1869}
1870
1871impl PartialEq<&str> for Key<'_> {
1872    fn eq(&self, other: &&str) -> bool {
1873        self.0 == *other
1874    }
1875}
1876
1877impl Display for Key<'_> {
1878    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1879        write!(f, "{}", &self.0)
1880    }
1881}
1882
1883/// Error type for [Key] creation.
1884#[derive(PartialEq, Eq, Debug)]
1885pub struct KeyError(KeyErrorKind);
1886
1887#[derive(PartialEq, Eq, Debug)]
1888enum KeyErrorKind {
1889    Empty,
1890    Initial(char),
1891    Subsequent(Vec<char>),
1892}
1893
1894impl Display for KeyError {
1895    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1896        use KeyErrorKind::*;
1897        match &self.0 {
1898            Empty => write!(f, "empty key"),
1899            Initial(bad_char) => write!(
1900                f,
1901                "invalid character '{}' for key initial - must be lowercase ASCII letter",
1902                bad_char
1903            ),
1904            Subsequent(bad_chars) => {
1905                format(
1906                    f,
1907                    bad_chars,
1908                    single_quoted,
1909                    ", ",
1910                    Some("invalid characters "),
1911                )?;
1912                f.write_str(" for key - must be alphanumeric or '-' or '_'")
1913            }
1914        }
1915    }
1916}
1917
1918impl std::error::Error for KeyError {}
1919
1920impl<'a> TryFrom<&'a str> for Key<'a> {
1921    type Error = KeyError;
1922
1923    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1924        use KeyErrorKind::*;
1925        if s.is_empty() {
1926            Err(KeyError(Empty))
1927        } else {
1928            let mut chars = s.chars();
1929            let initial = chars.next().unwrap();
1930            if !Key::is_valid_initial(&initial) {
1931                Err(KeyError(Initial(initial)))
1932            } else {
1933                let bad_chars = chars
1934                    .filter(|c| (!Key::is_valid_subsequent(c)))
1935                    .collect::<Vec<char>>();
1936                if bad_chars.is_empty() {
1937                    Ok(Key(s))
1938                } else {
1939                    Err(KeyError(Subsequent(bad_chars)))
1940                }
1941            }
1942        }
1943    }
1944}
1945
1946#[derive(PartialEq, Eq, Clone, Debug)]
1947/// An `Expr` which has been evaluated.
1948///
1949/// Note that the [decimal scale](https://docs.rs/rust_decimal/latest/rust_decimal/index.html) is set according to the maximum of the scales used within the expression.
1950pub struct ExprValue {
1951    value: Decimal,
1952    expr: Expr,
1953}
1954
1955impl ExprValue {
1956    /// Field accessor.
1957    pub fn value(&self) -> Decimal {
1958        self.value
1959    }
1960
1961    /// Field accessor.
1962    pub fn expr(&self) -> &Expr {
1963        &self.expr
1964    }
1965}
1966
1967impl PartialEq<Decimal> for ExprValue {
1968    fn eq(&self, other: &Decimal) -> bool {
1969        &self.value == other
1970    }
1971}
1972
1973impl From<Expr> for ExprValue {
1974    /// Evaluate the `Expr` rounding to the max scale it contains.
1975    fn from(expr: Expr) -> Self {
1976        let (mut value, scale) = expr.evaluate();
1977        value.rescale(scale);
1978        Self { value, expr }
1979    }
1980}
1981
1982//
1983impl ElementType for ExprValue {
1984    fn element_type(&self) -> &'static str {
1985        "amount" // is there a better user-facing name?
1986    }
1987}
1988
1989impl Display for ExprValue {
1990    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
1991        Display::fmt(&self.expr, format)
1992    }
1993}
1994
1995/// A numeric expression which respects standard operator precedence.
1996#[derive(PartialEq, Eq, Clone)]
1997pub enum Expr {
1998    Value(Decimal),
1999    Add(Box<Expr>, Box<Expr>),
2000    Sub(Box<Expr>, Box<Expr>),
2001    Mul(Box<Expr>, Box<Expr>),
2002    Div(Box<Expr>, Box<Expr>),
2003    Neg(Box<Expr>),
2004    Paren(Box<Expr>),
2005}
2006
2007impl Expr {
2008    fn evaluate(&self) -> (Decimal, u32) {
2009        fn evaluate_unary<F>(op: F, e1: &Expr) -> (Decimal, u32)
2010        where
2011            F: Fn(Decimal) -> Decimal,
2012        {
2013            let (d1, s1) = e1.evaluate();
2014            (op(d1), s1)
2015        }
2016
2017        fn evaluate_binary<F>(op: F, e1: &Expr, e2: &Expr) -> (Decimal, u32)
2018        where
2019            F: Fn(Decimal, Decimal) -> Decimal,
2020        {
2021            let (d1, s1) = e1.evaluate();
2022            let (d2, s2) = e2.evaluate();
2023            (op(d1, d2), max(s1, s2))
2024        }
2025
2026        use Expr::*;
2027        match self {
2028            Value(d) => (*d, d.scale()),
2029            Add(e1, e2) => evaluate_binary(std::ops::Add::add, e1, e2),
2030            Sub(e1, e2) => evaluate_binary(std::ops::Sub::sub, e1, e2),
2031            Mul(e1, e2) => evaluate_binary(std::ops::Mul::mul, e1, e2),
2032            Div(e1, e2) => evaluate_binary(std::ops::Div::div, e1, e2),
2033            Neg(e1) => evaluate_unary(std::ops::Neg::neg, e1),
2034            Paren(e) => e.evaluate(),
2035        }
2036    }
2037}
2038
2039// impl PartialEq for Expr {
2040
2041// }
2042
2043impl Display for Expr {
2044    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2045        use self::Expr::*;
2046        match *self {
2047            Value(val) => write!(format, "{}", val),
2048            Add(ref left, ref right) => write!(format, "{} + {}", left, right),
2049            Sub(ref left, ref right) => write!(format, "{} - {}", left, right),
2050            Mul(ref left, ref right) => write!(format, "{} * {}", left, right),
2051            Div(ref left, ref right) => write!(format, "{} / {}", left, right),
2052            Neg(ref expr) => write!(format, "-{}", expr),
2053            Paren(ref expr) => write!(format, "({})", expr),
2054        }
2055    }
2056}
2057
2058impl fmt::Debug for Expr {
2059    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2060        use self::Expr::*;
2061        match *self {
2062            Value(val) => write!(format, "{}", val),
2063            Add(ref left, ref right) => write!(format, "({:?} + {:?})", left, right),
2064            Sub(ref left, ref right) => write!(format, "({:?} - {:?})", left, right),
2065            Mul(ref left, ref right) => write!(format, "({:?} * {:?})", left, right),
2066            Div(ref left, ref right) => write!(format, "({:?} / {:?})", left, right),
2067            Neg(ref expr) => write!(format, "(-{:?})", expr),
2068            Paren(ref expr) => write!(format, "[{:?}]", expr),
2069        }
2070    }
2071}
2072
2073/// An `ExprValue` which quantifies a total or per-unit, or both.
2074#[derive(PartialEq, Eq, Clone, Debug)]
2075pub enum CompoundExprValue {
2076    PerUnit(ExprValue),
2077    Total(ExprValue),
2078    PerUnitAndTotal(ExprValue, ExprValue),
2079}
2080
2081impl Display for CompoundExprValue {
2082    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2083        use self::CompoundExprValue::*;
2084
2085        match self {
2086            PerUnit(e) => write!(format, "{}", e),
2087            Total(e) => write!(format, "# {}", e),
2088            PerUnitAndTotal(per_unit, total) => write!(format, "{} # {}", per_unit, total),
2089        }
2090    }
2091}
2092
2093/// An `ExprValue` which quantifies either a total or per-unit, but not both.
2094#[derive(PartialEq, Eq, Clone, Debug)]
2095pub enum ScopedExprValue {
2096    PerUnit(ExprValue),
2097    Total(ExprValue),
2098}
2099
2100impl Display for ScopedExprValue {
2101    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2102        use self::ScopedExprValue::*;
2103
2104        match self {
2105            PerUnit(e) => write!(format, "{}", e),
2106            Total(e) => write!(format, "# {}", e),
2107        }
2108    }
2109}
2110
2111/// A `ExprValue` and `Currency`.
2112#[derive(PartialEq, Eq, Clone, Debug)]
2113pub struct Amount<'a> {
2114    number: Spanned<ExprValue>,
2115    currency: Spanned<Currency<'a>>,
2116}
2117
2118impl<'a> Amount<'a> {
2119    pub(crate) fn new(amount: (Spanned<ExprValue>, Spanned<Currency<'a>>)) -> Self {
2120        Amount {
2121            number: amount.0,
2122            currency: amount.1,
2123        }
2124    }
2125
2126    /// Field accessor.
2127    pub fn number(&self) -> &Spanned<ExprValue> {
2128        &self.number
2129    }
2130
2131    /// Field accessor.
2132    pub fn currency(&self) -> &Spanned<Currency> {
2133        &self.currency
2134    }
2135}
2136
2137impl Display for Amount<'_> {
2138    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2139        write!(format, "{} {}", &self.number, &self.currency)
2140    }
2141}
2142
2143/// An `Amount` with optional tolerance.
2144#[derive(PartialEq, Eq, Clone, Debug)]
2145pub struct AmountWithTolerance<'a> {
2146    amount: Spanned<Amount<'a>>,
2147    tolerance: Option<Spanned<Decimal>>,
2148}
2149
2150impl<'a> AmountWithTolerance<'a> {
2151    pub(crate) fn new(awt: (Spanned<Amount<'a>>, Option<Spanned<Decimal>>)) -> Self {
2152        AmountWithTolerance {
2153            amount: awt.0,
2154            tolerance: awt.1,
2155        }
2156    }
2157
2158    /// Field accessor.
2159    pub fn amount(&self) -> &Spanned<Amount> {
2160        &self.amount
2161    }
2162
2163    /// Field accessor.
2164    pub fn tolerance(&self) -> Option<&Spanned<Decimal>> {
2165        self.tolerance.as_ref()
2166    }
2167}
2168
2169impl Display for AmountWithTolerance<'_> {
2170    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2171        if let Some(tolerance) = self.tolerance {
2172            write!(format, "{} ~ {}", &self.amount, tolerance)
2173        } else {
2174            write!(format, "{}", &self.amount)
2175        }
2176    }
2177}
2178
2179/// An amount where each element of `ExprValue` and `Currency` may not actually be specified.
2180#[derive(PartialEq, Eq, Clone, Debug)]
2181pub struct LooseAmount<'a> {
2182    number: Option<Spanned<ExprValue>>,
2183    currency: Option<Spanned<Currency<'a>>>,
2184}
2185
2186impl<'a> LooseAmount<'a> {
2187    pub(crate) fn new(amount: (Option<Spanned<ExprValue>>, Option<Spanned<Currency<'a>>>)) -> Self {
2188        LooseAmount {
2189            number: amount.0,
2190            currency: amount.1,
2191        }
2192    }
2193
2194    /// Field accessor.
2195    pub fn number(&self) -> Option<&Spanned<ExprValue>> {
2196        self.number.as_ref()
2197    }
2198
2199    /// Field accessor.
2200    pub fn currency(&self) -> Option<&Spanned<Currency>> {
2201        self.currency.as_ref()
2202    }
2203}
2204
2205/// An amount which specifies a total or per-unit value or both, with or without a currency, or simply just a `Currency`.
2206#[derive(PartialEq, Eq, Clone, Debug)]
2207pub enum CompoundAmount<'a> {
2208    BareCurrency(Currency<'a>),
2209    BareAmount(CompoundExprValue),
2210    CurrencyAmount(CompoundExprValue, Currency<'a>),
2211}
2212
2213impl Display for CompoundAmount<'_> {
2214    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2215        use self::CompoundAmount::*;
2216        match self {
2217            BareCurrency(cur) => write!(format, "{}", cur),
2218            BareAmount(ce) => write!(format, "{}", ce),
2219            CurrencyAmount(ce, cur) => write!(format, "{} {}", ce, cur),
2220        }
2221    }
2222}
2223
2224/// A cost specification.
2225#[derive(PartialEq, Eq, Clone, Debug)]
2226pub struct CostSpec<'a> {
2227    per_unit: Option<Spanned<ExprValue>>,
2228    total: Option<Spanned<ExprValue>>,
2229    currency: Option<Spanned<Currency<'a>>>,
2230    date: Option<Spanned<Date>>,
2231    label: Option<Spanned<&'a str>>,
2232    merge: bool,
2233}
2234
2235impl CostSpec<'_> {
2236    /// Field accessor.
2237    pub fn per_unit(&self) -> Option<&Spanned<ExprValue>> {
2238        self.per_unit.as_ref()
2239    }
2240
2241    /// Field accessor.
2242    pub fn total(&self) -> Option<&Spanned<ExprValue>> {
2243        self.total.as_ref()
2244    }
2245
2246    /// Field accessor.
2247    pub fn currency(&self) -> Option<&Spanned<Currency<'_>>> {
2248        self.currency.as_ref()
2249    }
2250
2251    /// Field accessor.
2252    pub fn date(&self) -> Option<&Spanned<Date>> {
2253        self.date.as_ref()
2254    }
2255
2256    /// Field accessor.
2257    pub fn label(&self) -> Option<&Spanned<&str>> {
2258        self.label.as_ref()
2259    }
2260
2261    /// Field accessor.
2262    pub fn merge(&self) -> bool {
2263        self.merge
2264    }
2265}
2266
2267impl ElementType for CostSpec<'_> {
2268    fn element_type(&self) -> &'static str {
2269        "cost specification"
2270    }
2271}
2272
2273impl Display for CostSpec<'_> {
2274    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2275        let mut prefix = "";
2276        let space = " ";
2277
2278        f.write_str("{")?;
2279
2280        if let Some(per_unit) = &self.per_unit {
2281            write!(f, "{}{}", prefix, per_unit)?;
2282            prefix = space;
2283        }
2284
2285        if let Some(total) = &self.total {
2286            write!(f, "{}# {}", prefix, total)?;
2287            prefix = space;
2288        }
2289
2290        if let Some(currency) = &self.currency {
2291            write!(f, "{}{}", prefix, currency)?;
2292            prefix = space;
2293        }
2294
2295        if let Some(date) = &self.date {
2296            write!(f, "{}{}", prefix, date)?;
2297            prefix = space;
2298        }
2299
2300        if let Some(label) = &self.label {
2301            write!(f, "{}\"{}\"", prefix, label)?;
2302            prefix = space;
2303        }
2304
2305        if self.merge {
2306            write!(f, "{}*", prefix)?;
2307        }
2308
2309        f.write_str("}")
2310    }
2311}
2312
2313#[derive(Default, Debug)]
2314/// Only allows setting each field once, and requires at least one field to be set before building.
2315pub(crate) struct CostSpecBuilder<'a> {
2316    per_unit: Option<Spanned<ExprValue>>,
2317    total: Option<Spanned<ExprValue>>,
2318    currency: Option<Spanned<Currency<'a>>>,
2319    date: Option<Spanned<Date>>,
2320    label: Option<Spanned<&'a str>>,
2321    merge: bool,
2322    errors: Vec<CostSpecError>,
2323}
2324
2325impl<'a> CostSpecBuilder<'a> {
2326    pub(crate) fn compound_expr(self, value: CompoundExprValue, span: Span) -> Self {
2327        use CompoundExprValue::*;
2328
2329        match value {
2330            PerUnit(value) => self.per_unit(spanned(value, span)),
2331            Total(value) => self.total(spanned(value, span)),
2332            PerUnitAndTotal(per_unit, total) => self
2333                .per_unit(spanned(per_unit, span))
2334                .total(spanned(total, span)),
2335        }
2336    }
2337
2338    fn per_unit(mut self, value: Spanned<ExprValue>) -> Self {
2339        if self.per_unit.is_none() {
2340            self.per_unit = Some(value);
2341        } else {
2342            self.errors.push(CostSpecError(CostSpecErrorKind::PerUnit))
2343        }
2344        self
2345    }
2346
2347    fn total(mut self, value: Spanned<ExprValue>) -> Self {
2348        if self.total.is_none() {
2349            self.total = Some(value);
2350        } else {
2351            self.errors.push(CostSpecError(CostSpecErrorKind::Total))
2352        }
2353        self
2354    }
2355
2356    pub(crate) fn currency(mut self, value: Currency<'a>, span: Span) -> Self {
2357        if self.currency.is_none() {
2358            self.currency = Some(spanned(value, span));
2359        } else {
2360            self.errors.push(CostSpecError(CostSpecErrorKind::Currency))
2361        }
2362        self
2363    }
2364
2365    pub(crate) fn date(mut self, value: Date, span: Span) -> Self {
2366        if self.date.is_none() {
2367            self.date = Some(spanned(value, span));
2368        } else {
2369            self.errors.push(CostSpecError(CostSpecErrorKind::Date))
2370        }
2371        self
2372    }
2373
2374    pub(crate) fn label(mut self, value: &'a str, span: Span) -> Self {
2375        if self.label.is_none() {
2376            self.label = Some(spanned(value, span));
2377        } else {
2378            self.errors.push(CostSpecError(CostSpecErrorKind::Label))
2379        }
2380        self
2381    }
2382
2383    pub(crate) fn merge(mut self, _span: Span) -> Self {
2384        if !self.merge {
2385            // TODO find a way to keep a span iff merge is true
2386            self.merge = true;
2387        } else {
2388            self.errors
2389                .push(CostSpecError(CostSpecErrorKind::MergeCost))
2390        }
2391        self
2392    }
2393
2394    // the lifetime `'a` of the CostSpec returned outlives the builder lifetime `'b`
2395    pub(crate) fn build<'b>(&'b mut self) -> Result<CostSpec<'a>, CostSpecErrors>
2396    where
2397        'a: 'b,
2398    {
2399        let per_unit = self.per_unit.take();
2400        let total = self.total.take();
2401        let currency = self.currency.take();
2402        let date = self.date.take();
2403        let label = self.label.take();
2404        let merge = self.merge;
2405        self.merge = false;
2406
2407        if !self.errors.is_empty() {
2408            let mut errors = Vec::new();
2409            swap(&mut self.errors, &mut errors);
2410            Err(CostSpecErrors(errors))
2411        } else if per_unit.is_none()
2412            && total.is_none()
2413            && currency.is_none()
2414            && date.is_none()
2415            && label.is_none()
2416            && !merge
2417        {
2418            Err(CostSpecErrors(vec![CostSpecError(
2419                CostSpecErrorKind::Empty,
2420            )]))
2421        } else {
2422            Ok(CostSpec {
2423                per_unit,
2424                total,
2425                currency,
2426                date,
2427                label,
2428                merge,
2429            })
2430        }
2431        // let errors: Vec<CostSpecError>,
2432    }
2433}
2434
2435/// Error type for [CostSpec] creation.
2436#[derive(PartialEq, Eq, Debug)]
2437pub struct CostSpecError(CostSpecErrorKind);
2438
2439#[derive(PartialEq, Eq, Display, Debug)]
2440#[strum(serialize_all = "kebab-case")]
2441enum CostSpecErrorKind {
2442    PerUnit,
2443    Total,
2444    Currency,
2445    Date,
2446    Label,
2447    MergeCost,
2448    Empty,
2449}
2450
2451impl CostSpecErrorKind {
2452    fn unless(self, condition: bool) -> Result<(), CostSpecError> {
2453        if condition {
2454            Ok(())
2455        } else {
2456            Err(CostSpecError(self))
2457        }
2458    }
2459}
2460
2461impl Display for CostSpecError {
2462    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2463        if self.0 == CostSpecErrorKind::Empty {
2464            write!(f, "empty cost specification")
2465        } else {
2466            write!(f, "duplicate {} field in cost specification", self.0)
2467        }
2468    }
2469}
2470
2471impl std::error::Error for CostSpecError {}
2472
2473/// Multiple errors arising from creation of a [CostSpec].
2474#[derive(PartialEq, Eq, Debug)]
2475pub struct CostSpecErrors(Vec<CostSpecError>);
2476
2477impl Display for CostSpecErrors {
2478    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2479        format(f, &self.0, plain, ", ", None)
2480    }
2481}
2482
2483impl std::error::Error for CostSpecErrors {}
2484
2485/// An amount which specifies a total or per-unit value, with or without a currency, or simply just a `Currency`.
2486/// Unlike a `CompoundAmount` it is forbidden to have both total and per-unit.
2487#[derive(PartialEq, Eq, Clone, Debug)]
2488pub enum PriceSpec<'a> {
2489    BareCurrency(Currency<'a>),
2490    BareAmount(ScopedExprValue),
2491    CurrencyAmount(ScopedExprValue, Currency<'a>),
2492}
2493
2494impl Display for PriceSpec<'_> {
2495    fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2496        use self::PriceSpec::*;
2497        match self {
2498            BareCurrency(cur) => write!(format, "{}", cur),
2499            BareAmount(ce) => write!(format, "{}", ce),
2500            CurrencyAmount(ce, cur) => write!(format, "{} {}", ce, cur),
2501        }
2502    }
2503}
2504
2505#[derive(Debug)]
2506pub(crate) struct AccountTypeNames<'a> {
2507    // relies on AccountType discriminants being contiguous and from zero, which they are
2508    pub(crate) name_by_type: Vec<AccountTypeName<'a>>,
2509    pub(crate) type_by_name: HashMap<AccountTypeName<'a>, AccountType>,
2510}
2511
2512impl<'a> AccountTypeNames<'a> {
2513    pub(crate) fn name(&self, account_type: AccountType) -> AccountTypeName<'a> {
2514        self.name_by_type[account_type as usize]
2515    }
2516
2517    pub(crate) fn get(&self, name: &AccountTypeName) -> Option<AccountType> {
2518        self.type_by_name.get(name).copied()
2519    }
2520
2521    pub(crate) fn update(
2522        &mut self,
2523        account_type: AccountType,
2524        name: AccountTypeName<'a>,
2525    ) -> Result<(), AccountTypeNamesError> {
2526        use hash_map::Entry::*;
2527
2528        match self.type_by_name.entry(name) {
2529            Vacant(e) => {
2530                e.insert(account_type);
2531                let old_name = self.name_by_type[account_type as usize];
2532                self.name_by_type[account_type as usize] = name;
2533                self.type_by_name.remove(&old_name);
2534                Ok(())
2535            }
2536            Occupied(o) => {
2537                let existing_account_type = *o.get();
2538                if existing_account_type == account_type {
2539                    // updating as same, harmless
2540                    Ok(())
2541                } else {
2542                    Err(AccountTypeNamesError(AccountTypeNamesErrorKind::NameInUse(
2543                        existing_account_type,
2544                    )))
2545                }
2546            }
2547        }
2548    }
2549}
2550
2551impl<'a> Default for AccountTypeNames<'a> {
2552    fn default() -> Self {
2553        use AccountType::*;
2554
2555        let names_types = vec![
2556            ("Assets", Assets),
2557            ("Liabilities", Liabilities),
2558            ("Equity", Equity),
2559            ("Income", Income),
2560            ("Expenses", Expenses),
2561        ];
2562
2563        let mut names_type_indices = names_types
2564            .iter()
2565            .map(|(n, t)| (*n, *t as usize))
2566            .collect::<Vec<_>>();
2567        names_type_indices.sort_by_key(|(_n, t)| *t);
2568
2569        let name_by_type = names_type_indices
2570            .into_iter()
2571            .map(|(n, _t)| AccountTypeName::try_from(n).unwrap())
2572            .collect::<Vec<_>>();
2573
2574        let type_by_name: HashMap<AccountTypeName<'a>, AccountType> = HashMap::from_iter(
2575            names_types
2576                .into_iter()
2577                .map(|(n, t)| (AccountTypeName::try_from(n).unwrap(), t)),
2578        );
2579
2580        AccountTypeNames {
2581            name_by_type,
2582            type_by_name,
2583        }
2584    }
2585}
2586
2587impl Display for AccountTypeNames<'_> {
2588    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2589        format(f, &self.name_by_type, plain, ", ", None)
2590    }
2591}
2592
2593#[derive(PartialEq, Eq, Debug)]
2594pub(crate) struct AccountTypeNamesError(AccountTypeNamesErrorKind);
2595
2596#[derive(PartialEq, Eq, Debug)]
2597enum AccountTypeNamesErrorKind {
2598    NameInUse(AccountType),
2599}
2600
2601impl Display for AccountTypeNamesError {
2602    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2603        use AccountTypeNamesErrorKind::*;
2604        match &self.0 {
2605            NameInUse(t) => write!(f, "account type name in use for {}", t.as_ref()),
2606        }
2607    }
2608}
2609
2610impl std::error::Error for AccountTypeNamesError {}
2611
2612mod tests;