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#[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#[derive(Clone, Debug)]
36pub struct ErrorKind;
37
38pub type Error = ErrorOrWarning<ErrorKind>;
41
42#[derive(Clone, Debug)]
44pub struct WarningKind;
45
46pub 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 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 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 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
165fn fmt_error_or_warning<K>(value: &ErrorOrWarning<K>, f: &mut Formatter<'_>) -> fmt::Result
166where
167 K: ErrorOrWarningKind,
168{
169 use chumsky::span::Span;
170
171 write!(f, "{} ({}) ", value.message, value.reason)?;
172 write!(
173 f,
174 "at {}..{} of source {}",
175 value.span.start(),
176 value.span.end(),
177 value.span.context()
178 )?;
179 for context in value.contexts.iter() {
180 write!(f, " while parsing {} at {}", context.0, context.1)?;
181 }
182 Ok(())
183}
184
185impl Display for Error {
186 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187 fmt_error_or_warning(self, f)
188 }
189}
190
191impl std::error::Error for Error {}
192
193impl Display for Warning {
194 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
195 fmt_error_or_warning(self, f)
196 }
197}
198
199pub trait ErrorOrWarningKind {
201 fn report_kind() -> ariadne::ReportKind<'static>;
202
203 fn color() -> ariadne::Color;
204}
205
206impl ErrorOrWarningKind for ErrorKind {
207 fn report_kind() -> ariadne::ReportKind<'static> {
208 ariadne::ReportKind::Error
209 }
210
211 fn color() -> ariadne::Color {
212 ariadne::Color::Red
213 }
214}
215
216impl ErrorOrWarningKind for WarningKind {
217 fn report_kind() -> ariadne::ReportKind<'static> {
218 ariadne::ReportKind::Warning
219 }
220
221 fn color() -> ariadne::Color {
222 ariadne::Color::Yellow
223 }
224}
225
226#[derive(Clone, Debug)]
228pub struct AnnotatedErrorOrWarning<K>
229where
230 K: ErrorOrWarningKind,
231{
232 pub error_or_warning: ErrorOrWarning<K>,
233 pub annotation: Option<String>,
234}
235
236impl<K> From<ErrorOrWarning<K>> for AnnotatedErrorOrWarning<K>
237where
238 K: ErrorOrWarningKind,
239{
240 fn from(error_or_warning: ErrorOrWarning<K>) -> Self {
241 Self {
242 error_or_warning,
243 annotation: None,
244 }
245 }
246}
247
248pub type AnnotatedError = AnnotatedErrorOrWarning<ErrorKind>;
249pub type AnnotatedWarning = AnnotatedErrorOrWarning<WarningKind>;
250
251#[derive(PartialEq, Eq, Hash, Clone, Copy, EnumString, EnumIter, IntoStaticStr, Debug)]
253pub enum AccountType {
254 Assets,
255 Liabilities,
256 Equity,
257 Income,
258 Expenses,
259}
260
261impl AsRef<str> for AccountType {
262 fn as_ref(&self) -> &'static str {
263 self.into()
264 }
265}
266
267#[derive(PartialEq, Eq, Default, Clone, Copy, Debug)]
269pub enum Flag {
270 #[default]
271 Asterisk,
272 Exclamation,
273 Ampersand,
274 Hash,
275 Question,
276 Percent,
277 Letter(FlagLetter),
278}
279
280impl ElementType for Flag {
281 fn element_type(&self) -> &'static str {
282 "flag"
283 }
284}
285
286impl Display for Flag {
287 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
288 use Flag::*;
289 let (prefix, c) = match self {
290 Asterisk => (None, '*'),
291 Exclamation => (None, '!'),
292 Ampersand => (None, '&'),
293 Hash => (None, '#'),
294 Question => (None, '?'),
295 Percent => (None, '%'),
296 Letter(FlagLetter(c)) => (Some('\''), *c),
297 };
298
299 match prefix {
300 Some(prefix) => write!(f, "{}{}", prefix, c),
301 None => write!(f, "{}", c),
302 }
303 }
304}
305
306#[derive(PartialEq, Eq, Copy, Clone, Debug)]
308pub struct FlagLetter(char);
309
310impl FlagLetter {
311 pub fn char(&self) -> char {
313 self.0
314 }
315
316 pub(crate) fn is_valid(c: &char) -> bool {
317 c.is_ascii_uppercase()
318 }
319}
320
321#[derive(PartialEq, Eq, Debug)]
323pub struct FlagLetterError(char);
324
325impl Display for FlagLetterError {
326 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
327 write!(
328 f,
329 "invalid character '{}' for flag letter - must be uppercase ASCII",
330 self.0
331 )
332 }
333}
334
335impl std::error::Error for FlagLetterError {}
336
337impl TryFrom<char> for FlagLetter {
338 type Error = FlagLetterError;
339
340 fn try_from(c: char) -> Result<Self, Self::Error> {
341 if FlagLetter::is_valid(&c) {
342 Ok(FlagLetter(c))
343 } else {
344 Err(FlagLetterError(c))
345 }
346 }
347}
348
349#[derive(
351 EnumString, EnumIter, IntoStaticStr, PartialEq, Eq, Default, Clone, Copy, Display, Debug,
352)]
353#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
354pub enum Booking {
355 #[default]
356 Strict,
357 StrictWithSize,
358 None,
359 Average,
360 Fifo,
361 Lifo,
362 Hifo,
363}
364
365impl AsRef<str> for Booking {
366 fn as_ref(&self) -> &'static str {
367 self.into()
368 }
369}
370
371impl ElementType for Booking {
372 fn element_type(&self) -> &'static str {
373 "booking"
374 }
375}
376
377#[derive(
378 EnumString, EnumIter, IntoStaticStr, PartialEq, Eq, Default, Clone, Copy, Display, Debug,
379)]
380#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
381pub enum PluginProcessingMode {
382 #[default]
383 Default,
384 Raw,
385}
386
387#[derive(PartialEq, Eq, Copy, Clone, Default, Debug)]
389pub struct SourceId(u32);
390
391impl From<usize> for SourceId {
392 fn from(value: usize) -> Self {
393 SourceId(value as u32)
394 }
395}
396
397impl From<SourceId> for usize {
398 fn from(value: SourceId) -> Self {
399 value.0 as usize
400 }
401}
402
403impl Display for SourceId {
404 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
405 write!(f, "{}", self.0)
406 }
407}
408
409pub type Span = chumsky::span::SimpleSpan<usize, SourceId>;
411
412#[derive(Clone, Debug)]
415pub struct Spanned<T> {
416 pub(crate) item: T,
417 pub(crate) span: Span,
418}
419
420impl<T> Deref for Spanned<T> {
422 type Target = T;
423
424 fn deref(&self) -> &Self::Target {
425 &self.item
426 }
427}
428
429impl<T> DerefMut for Spanned<T> {
431 fn deref_mut(&mut self) -> &mut Self::Target {
432 &mut self.item
433 }
434}
435
436pub fn spanned<T>(item: T, span: Span) -> Spanned<T> {
437 Spanned { item, span }
438}
439
440pub(crate) fn spanned_extra<'a, 'b, T, I, E>(item: T, e: &mut MapExtra<'a, 'b, I, E>) -> Spanned<T>
442where
443 I: Input<'a, Span = Span>,
444 E: ParserExtra<'a, I>,
445{
446 Spanned {
447 item,
448 span: e.span(),
449 }
450}
451
452impl<T> Spanned<T> {
453 pub fn item(&self) -> &T {
455 &self.item
456 }
457
458 pub fn span(&self) -> &Span {
460 &self.span
461 }
462
463 pub fn as_ref(&self) -> Spanned<&T> {
465 Spanned {
466 item: &self.item,
467 span: self.span,
468 }
469 }
470
471 pub fn map<U, F>(&self, f: F) -> Spanned<U>
473 where
474 F: FnOnce(&T) -> U,
475 {
476 Spanned {
477 item: f(&self.item),
478 span: self.span,
479 }
480 }
481}
482
483impl<T> Spanned<T>
484where
485 T: ElementType,
486{
487 pub fn error<S: Into<String>>(&self, reason: S) -> Error {
489 Error::new(
490 format!("invalid {}", self.element_type()),
491 reason,
492 self.span,
493 )
494 }
495
496 pub fn error_with_contexts<S: Into<String>>(
497 &self,
498 reason: S,
499 contexts: Vec<(String, Span)>,
500 ) -> Error {
501 Error::with_contexts(
502 format!("invalid {}", self.element_type()),
503 reason,
504 self.span,
505 contexts,
506 )
507 }
508
509 pub fn warning<S: Into<String>>(&self, reason: S) -> Warning {
511 Warning::new(
512 format!("questionable {}", self.element_type()),
513 reason,
514 self.span,
515 )
516 }
517}
518
519impl<T> PartialEq for Spanned<T>
520where
521 T: PartialEq,
522{
523 fn eq(&self, other: &Self) -> bool {
524 self.item.eq(&other.item)
525 }
526}
527
528impl<T> Eq for Spanned<T> where T: Eq {}
529
530impl<T> Hash for Spanned<T>
531where
532 T: Hash,
533{
534 fn hash<H: Hasher>(&self, state: &mut H) {
535 self.item.hash(state)
536 }
537}
538
539impl<T> Copy for Spanned<T> where T: Copy {}
540
541impl<T> Display for Spanned<T>
542where
543 T: Display,
544{
545 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
546 write!(f, "{}", self.item,)
547 }
548}
549
550pub trait OptionalItem<T> {
552 fn item(&self) -> Option<&T>;
553}
554
555impl<T> OptionalItem<T> for Option<&Spanned<T>> {
556 fn item(&self) -> Option<&T> {
557 self.map(|spanned| spanned.item())
558 }
559}
560
561pub trait ElementType {
563 fn element_type(&self) -> &'static str;
564}
565
566impl<T> ElementType for &T
568where
569 T: ElementType,
570{
571 fn element_type(&self) -> &'static str {
572 (**self).element_type()
573 }
574}
575
576impl<T> ElementType for &mut T
577where
578 T: ElementType,
579{
580 fn element_type(&self) -> &'static str {
581 (**self).element_type()
582 }
583}
584
585impl<T> ElementType for Box<T>
586where
587 T: ElementType,
588{
589 fn element_type(&self) -> &'static str {
590 (**self).element_type()
591 }
592}
593
594#[derive(PartialEq, Eq, Clone, Debug)]
595pub(crate) enum Declaration<'a> {
596 Directive(Directive<'a>),
597 Pragma(Pragma<'a>),
598}
599
600#[derive(PartialEq, Eq, Clone, Debug)]
602pub struct Directive<'a> {
603 pub(crate) date: Spanned<Date>,
604 pub(crate) metadata: Spanned<Metadata<'a>>,
605 pub(crate) variant: DirectiveVariant<'a>,
606}
607
608impl Directive<'_> {
609 pub fn date(&self) -> &Spanned<Date> {
611 &self.date
612 }
613
614 pub fn metadata(&self) -> &Metadata {
616 &self.metadata
617 }
618
619 pub fn variant(&self) -> &DirectiveVariant {
621 &self.variant
622 }
623}
624
625impl ElementType for Directive<'_> {
626 fn element_type(&self) -> &'static str {
627 use DirectiveVariant::*;
628
629 match &self.variant {
630 Transaction(_) => "transaction",
631 Price(_) => "price",
632 Balance(_) => "balance",
633 Open(_) => "open",
634 Close(_) => "close",
635 Commodity(_) => "commodity",
636 Pad(_) => "pad",
637 Document(_) => "document",
638 Note(_) => "note",
639 Event(_) => "event",
640 Query(_) => "query",
641 }
642 }
643}
644
645impl Display for Directive<'_> {
646 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
647 use DirectiveVariant::*;
648
649 match &self.variant {
650 Transaction(x) => x.fmt(f, self.date.item, &self.metadata),
651 Price(x) => x.fmt(f, self.date.item, &self.metadata),
652 Balance(x) => x.fmt(f, self.date.item, &self.metadata),
653 Open(x) => x.fmt(f, self.date.item, &self.metadata),
654 Close(x) => x.fmt(f, self.date.item, &self.metadata),
655 Commodity(x) => x.fmt(f, self.date.item, &self.metadata),
656 Pad(x) => x.fmt(f, self.date.item, &self.metadata),
657 Document(x) => x.fmt(f, self.date.item, &self.metadata),
658 Note(x) => x.fmt(f, self.date.item, &self.metadata),
659 Event(x) => x.fmt(f, self.date.item, &self.metadata),
660 Query(x) => x.fmt(f, self.date.item, &self.metadata),
661 }
662 }
663}
664
665#[derive(PartialEq, Eq, Clone, Debug)]
667pub enum DirectiveVariant<'a> {
668 Transaction(Transaction<'a>),
669 Price(Price<'a>),
670 Balance(Balance<'a>),
671 Open(Open<'a>),
672 Close(Close<'a>),
673 Commodity(Commodity<'a>),
674 Pad(Pad<'a>),
675 Document(Document<'a>),
676 Note(Note<'a>),
677 Event(Event<'a>),
678 Query(Query<'a>),
679 }
681
682#[derive(PartialEq, Eq, Clone, Debug)]
683pub(crate) enum Pragma<'a> {
684 Pushtag(Spanned<Tag<'a>>),
687 Poptag(Spanned<Tag<'a>>),
688 Pushmeta(MetaKeyValue<'a>),
689 Popmeta(Spanned<Key<'a>>),
690 Include(Spanned<&'a str>),
691 Option(BeancountOption<'a>),
692 Plugin(Plugin<'a>),
693}
694
695#[derive(PartialEq, Eq, Clone, Debug)]
697pub struct Transaction<'a> {
698 pub(crate) flag: Spanned<Flag>,
699 pub(crate) payee: Option<Spanned<&'a str>>,
700 pub(crate) narration: Option<Spanned<&'a str>>,
701 pub(crate) postings: Vec<Spanned<Posting<'a>>>,
702}
703
704impl Transaction<'_> {
705 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
706 write!(f, "{} {}", date, self.flag)?;
707
708 format(f, &self.payee, double_quoted, " ", Some(" "))?;
709 format(f, &self.narration, double_quoted, " ", Some(" "))?;
710 metadata.fmt_tags_links_inline(f)?;
712 metadata.fmt_keys_values(f)?;
713 format(
714 f,
715 &self.postings,
716 plain,
717 NEWLINE_INDENT,
718 Some(NEWLINE_INDENT),
719 )
720 }
721
722 pub fn flag(&self) -> &Spanned<Flag> {
724 &self.flag
725 }
726
727 pub fn payee(&self) -> Option<&Spanned<&str>> {
729 self.payee.as_ref()
730 }
731
732 pub fn narration(&self) -> Option<&Spanned<&str>> {
734 self.narration.as_ref()
735 }
736
737 pub fn postings(&self) -> impl ExactSizeIterator<Item = &Spanned<Posting>> {
739 self.postings.iter()
740 }
741}
742
743#[derive(PartialEq, Eq, Clone, Debug)]
745pub struct Price<'a> {
746 pub(crate) currency: Spanned<Currency<'a>>,
747 pub(crate) amount: Spanned<Amount<'a>>,
748}
749
750impl Price<'_> {
751 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
752 write!(f, "{} price {} {}", date, &self.currency, &self.amount)?;
753
754 metadata.fmt_tags_links_inline(f)?;
756 metadata.fmt_keys_values(f)?;
757
758 Ok(())
759 }
760
761 pub fn currency(&self) -> &Spanned<Currency> {
763 &self.currency
764 }
765
766 pub fn amount(&self) -> &Spanned<Amount> {
768 &self.amount
769 }
770}
771
772impl ElementType for Price<'_> {
773 fn element_type(&self) -> &'static str {
774 "price"
775 }
776}
777
778#[derive(PartialEq, Eq, Clone, Debug)]
780pub struct Balance<'a> {
781 pub(crate) account: Spanned<Account<'a>>,
782 pub(crate) atol: Spanned<AmountWithTolerance<'a>>,
783}
784
785impl Balance<'_> {
786 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
787 write!(f, "{} balance {} {}", date, &self.account, &self.atol)?;
788
789 metadata.fmt_tags_links_inline(f)?;
791 metadata.fmt_keys_values(f)?;
792
793 Ok(())
794 }
795
796 pub fn account(&self) -> &Spanned<Account> {
798 &self.account
799 }
800
801 pub fn atol(&self) -> &Spanned<AmountWithTolerance> {
803 &self.atol
804 }
805}
806
807impl ElementType for Balance<'_> {
808 fn element_type(&self) -> &'static str {
809 "balance"
810 }
811}
812
813#[derive(PartialEq, Eq, Clone, Debug)]
815pub struct Open<'a> {
816 pub(crate) account: Spanned<Account<'a>>,
817 pub(crate) currencies: HashSet<Spanned<Currency<'a>>>,
818 pub(crate) booking: Option<Spanned<Booking>>,
819}
820
821impl Open<'_> {
822 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
823 write!(f, "{} open {}", date, self.account)?;
824 format(f, &self.currencies, plain, ",", Some(" "))?;
825 format(f, &self.booking, double_quoted, " ", Some(" "))?;
826 metadata.fmt_tags_links_inline(f)?;
828 metadata.fmt_keys_values(f)
829 }
830
831 pub fn account(&self) -> &Spanned<Account> {
833 &self.account
834 }
835
836 pub fn currencies(&self) -> impl ExactSizeIterator<Item = &Spanned<Currency>> {
838 self.currencies.iter()
839 }
840
841 pub fn booking(&self) -> Option<&Spanned<Booking>> {
843 self.booking.as_ref()
844 }
845}
846
847#[derive(PartialEq, Eq, Clone, Debug)]
849pub struct Close<'a> {
850 pub(crate) account: Spanned<Account<'a>>,
851}
852
853impl Close<'_> {
854 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
855 write!(f, "{} close {}", date, self.account)?;
856 metadata.fmt_tags_links_inline(f)?;
858 metadata.fmt_keys_values(f)
859 }
860
861 pub fn account(&self) -> &Spanned<Account> {
863 &self.account
864 }
865}
866
867#[derive(PartialEq, Eq, Clone, Debug)]
869pub struct Commodity<'a> {
870 pub(crate) currency: Spanned<Currency<'a>>,
871}
872
873impl Commodity<'_> {
874 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
875 write!(f, "{} commodity {}", date, self.currency)?;
876 metadata.fmt_tags_links_inline(f)?;
878 metadata.fmt_keys_values(f)
879 }
880
881 pub fn currency(&self) -> &Spanned<Currency> {
883 &self.currency
884 }
885}
886
887#[derive(PartialEq, Eq, Clone, Debug)]
889pub struct Pad<'a> {
890 pub(crate) account: Spanned<Account<'a>>,
891 pub(crate) source: Spanned<Account<'a>>,
892}
893
894impl Pad<'_> {
895 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
896 write!(f, "{} pad {} {}", date, self.account, self.source)?;
897 metadata.fmt_tags_links_inline(f)?;
899 metadata.fmt_keys_values(f)
900 }
901
902 pub fn account(&self) -> &Spanned<Account> {
904 &self.account
905 }
906
907 pub fn source(&self) -> &Spanned<Account> {
909 &self.source
910 }
911}
912
913#[derive(PartialEq, Eq, Clone, Debug)]
915pub struct Document<'a> {
916 pub(crate) account: Spanned<Account<'a>>,
917 pub(crate) path: Spanned<&'a str>,
918}
919
920impl Document<'_> {
921 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
922 write!(f, "{} document {} \"{}\"", date, self.account, self.path)?;
923 metadata.fmt_tags_links_inline(f)?;
925 metadata.fmt_keys_values(f)
926 }
927
928 pub fn account(&self) -> &Spanned<Account> {
930 &self.account
931 }
932
933 pub fn path(&self) -> &Spanned<&str> {
936 &self.path
937 }
938}
939
940#[derive(PartialEq, Eq, Clone, Debug)]
942pub struct Note<'a> {
943 pub(crate) account: Spanned<Account<'a>>,
944 pub(crate) comment: Spanned<&'a str>,
945}
946
947impl Note<'_> {
948 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
949 write!(f, "{} note {} \"{}\"", date, self.account, self.comment)?;
950 metadata.fmt_tags_links_inline(f)?;
952 metadata.fmt_keys_values(f)
953 }
954
955 pub fn account(&self) -> &Spanned<Account> {
957 &self.account
958 }
959
960 pub fn comment(&self) -> &Spanned<&str> {
962 &self.comment
963 }
964}
965
966#[derive(PartialEq, Eq, Clone, Debug)]
968pub struct Event<'a> {
969 pub(crate) event_type: Spanned<&'a str>,
970 pub(crate) description: Spanned<&'a str>,
971}
972
973impl Event<'_> {
974 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
975 write!(
976 f,
977 "{} event \"{}\" \"{}\"",
978 date, self.event_type, self.description
979 )?;
980 metadata.fmt_tags_links_inline(f)?;
982 metadata.fmt_keys_values(f)
983 }
984
985 pub fn event_type(&self) -> &Spanned<&str> {
987 &self.event_type
988 }
989
990 pub fn description(&self) -> &Spanned<&str> {
992 &self.description
993 }
994}
995
996#[derive(PartialEq, Eq, Clone, Debug)]
998pub struct Query<'a> {
999 pub(crate) name: Spanned<&'a str>,
1000 pub(crate) content: Spanned<&'a str>,
1001}
1002
1003impl Query<'_> {
1004 fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
1005 write!(f, "{} query \"{}\" \"{}\"", date, self.name, self.content)?;
1006 metadata.fmt_tags_links_inline(f)?;
1008 metadata.fmt_keys_values(f)
1009 }
1010
1011 pub fn name(&self) -> &Spanned<&str> {
1013 &self.name
1014 }
1015
1016 pub fn content(&self) -> &Spanned<&str> {
1018 &self.content
1019 }
1020}
1021
1022#[derive(PartialEq, Eq, Clone, Debug)]
1024pub struct Plugin<'a> {
1025 pub(crate) module_name: Spanned<&'a str>,
1026 pub(crate) config: Option<Spanned<&'a str>>,
1027}
1028
1029impl Plugin<'_> {
1030 pub fn module_name(&self) -> &Spanned<&str> {
1032 &self.module_name
1033 }
1034
1035 pub fn config(&self) -> Option<&Spanned<&str>> {
1037 self.config.as_ref()
1038 }
1039}
1040
1041impl Display for Plugin<'_> {
1042 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1043 write!(f, "plugin \"{}\"", self.module_name)?;
1044 if let Some(config) = &self.config {
1045 write!(f, " \"{}\"", config)?;
1046 }
1047 writeln!(f)
1048 }
1049}
1050
1051#[derive(PartialEq, Eq, Clone, Debug)]
1053pub struct Account<'a> {
1054 account_type: AccountType,
1055 name: &'a str,
1056}
1057
1058impl<'a> Account<'a> {
1059 pub(crate) fn new(
1061 s: &'a str,
1062 account_type_names: &AccountTypeNames,
1063 ) -> Result<Self, AccountError> {
1064 let mut account = s.split(':');
1065 let account_type_name = AccountTypeName::try_from(
1066 account
1067 .by_ref()
1068 .next()
1069 .ok_or(AccountError(AccountErrorKind::MissingColon))?,
1070 )
1071 .map_err(|e| AccountError(AccountErrorKind::TypeName(e)))?;
1072 for subaccount in account {
1073 let _ = AccountName::try_from(subaccount)
1074 .map_err(|e| AccountError(AccountErrorKind::AccountName(e)))?;
1075 }
1076
1077 match account_type_names.get(&account_type_name) {
1078 Some(account_type) => Ok(Account {
1079 account_type,
1080 name: s,
1081 }),
1082 None => Err(AccountError(AccountErrorKind::UnknownAccountType(format!(
1083 "unknown account type {}, must be one of {}",
1084 &account_type_name, account_type_names
1085 )))),
1086 }
1087 }
1088
1089 pub fn account_type(&self) -> AccountType {
1090 self.account_type
1091 }
1092}
1093
1094impl Display for Account<'_> {
1095 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1096 write!(f, "{}", &self.name)
1097 }
1098}
1099
1100impl AsRef<str> for Account<'_> {
1101 fn as_ref(&self) -> &str {
1102 self.name
1103 }
1104}
1105
1106#[derive(PartialEq, Eq, Debug)]
1108pub struct AccountError(AccountErrorKind);
1109
1110#[derive(PartialEq, Eq, Debug)]
1111enum AccountErrorKind {
1112 MissingColon,
1113 TypeName(AccountTypeNameError),
1114 AccountName(AccountNameError),
1115 UnknownAccountType(String),
1116}
1117
1118impl Display for AccountError {
1119 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1120 use AccountErrorKind::*;
1121 match &self.0 {
1122 MissingColon => f.write_str("missing colon"),
1123 TypeName(e) => write!(f, "{}", e),
1124 AccountName(e) => write!(f, "{}", e),
1125 UnknownAccountType(e) => write!(f, "{}", e),
1126 }
1127 }
1128}
1129
1130impl std::error::Error for AccountError {}
1131
1132impl ElementType for Account<'_> {
1133 fn element_type(&self) -> &'static str {
1134 "account"
1135 }
1136}
1137
1138#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1140pub struct Subaccount<'a>(&'a str);
1141
1142impl AsRef<str> for Subaccount<'_> {
1143 fn as_ref(&self) -> &str {
1144 self.0
1145 }
1146}
1147
1148impl Display for Subaccount<'_> {
1149 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1150 write!(f, "{}", &self.0)
1151 }
1152}
1153
1154impl<'a> TryFrom<&'a str> for Subaccount<'a> {
1155 type Error = AccountNameError;
1156
1157 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1158 let mut empty = true;
1159 for account_name in s.split(':') {
1160 _ = AccountName::try_from(account_name)?;
1161 empty = false;
1162 }
1163
1164 if empty {
1165 Err(AccountNameError(AccountNameErrorKind::Empty))
1166 } else {
1167 Ok(Subaccount(s))
1168 }
1169 }
1170}
1171
1172#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1174pub struct AccountTypeName<'a>(&'a str);
1175
1176impl AccountTypeName<'_> {
1177 pub(crate) fn is_valid_initial(c: &char) -> bool {
1178 c.is_ascii_uppercase()
1179 }
1180
1181 pub(crate) fn is_valid_subsequent(c: &char) -> bool {
1182 c.is_alphanumeric() || *c == '-'
1183 }
1184}
1185
1186impl<'a> TryFrom<&'a str> for AccountTypeName<'a> {
1187 type Error = AccountTypeNameError;
1188
1189 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1190 use AccountTypeNameErrorKind::*;
1191 if s.is_empty() {
1192 Err(AccountTypeNameError(Empty))
1193 } else {
1194 let mut chars = s.chars();
1195 let initial = chars.next().unwrap();
1196 if !AccountTypeName::is_valid_initial(&initial) {
1197 Err(AccountTypeNameError(Initial(initial)))
1198 } else {
1199 let bad_chars = chars
1200 .filter(|c| (!AccountTypeName::is_valid_subsequent(c)))
1201 .collect::<Vec<char>>();
1202 if bad_chars.is_empty() {
1203 Ok(AccountTypeName(s))
1204 } else {
1205 Err(AccountTypeNameError(Subsequent(bad_chars)))
1206 }
1207 }
1208 }
1209 }
1210}
1211
1212impl ElementType for AccountTypeName<'_> {
1213 fn element_type(&self) -> &'static str {
1214 "account type name"
1215 }
1216}
1217
1218impl AsRef<str> for AccountTypeName<'_> {
1219 fn as_ref(&self) -> &str {
1220 self.0
1221 }
1222}
1223
1224impl PartialEq<&str> for AccountTypeName<'_> {
1225 fn eq(&self, other: &&str) -> bool {
1226 self.0 == *other
1227 }
1228}
1229
1230impl Display for AccountTypeName<'_> {
1231 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1232 write!(f, "{}", &self.0)
1233 }
1234}
1235
1236#[derive(PartialEq, Eq, Debug)]
1238pub struct AccountTypeNameError(AccountTypeNameErrorKind);
1239
1240#[derive(PartialEq, Eq, Debug)]
1241enum AccountTypeNameErrorKind {
1242 Empty,
1243 Initial(char),
1244 Subsequent(Vec<char>),
1245}
1246
1247impl Display for AccountTypeNameError {
1248 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1249 use AccountTypeNameErrorKind::*;
1250 match &self.0 {
1251 Empty => write!(f, "empty account name"),
1252 Initial(bad_char) => write!(
1253 f,
1254 "invalid character '{}' for account name initial - must be uppercase ASCII letter",
1255 bad_char
1256 ),
1257 Subsequent(bad_chars) => {
1258 format(
1259 f,
1260 bad_chars,
1261 single_quoted,
1262 ", ",
1263 Some("invalid character "),
1264 )?;
1265 f.write_str(" in account name - must be alphanumeric or '-'")
1266 }
1267 }
1268 }
1269}
1270
1271impl std::error::Error for AccountTypeNameError {}
1272
1273#[derive(PartialEq, Eq, Copy, Clone, Debug)]
1275pub struct AccountName<'a>(&'a str);
1276
1277impl AccountName<'_> {
1278 pub(crate) fn is_valid_initial(c: &char) -> bool {
1279 c.is_ascii_uppercase() || c.is_ascii_digit()
1280 }
1281
1282 pub(crate) fn is_valid_subsequent(c: &char) -> bool {
1283 c.is_alphanumeric() || *c == '-'
1284 }
1285}
1286
1287impl ElementType for AccountName<'_> {
1288 fn element_type(&self) -> &'static str {
1289 "account name"
1290 }
1291}
1292
1293impl AsRef<str> for AccountName<'_> {
1294 fn as_ref(&self) -> &str {
1295 self.0
1296 }
1297}
1298
1299impl PartialEq<&str> for AccountName<'_> {
1300 fn eq(&self, other: &&str) -> bool {
1301 self.0 == *other
1302 }
1303}
1304
1305impl Display for AccountName<'_> {
1306 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1307 write!(f, "{}", &self.0)
1308 }
1309}
1310
1311#[derive(PartialEq, Eq, Debug)]
1313pub struct AccountNameError(AccountNameErrorKind);
1314
1315#[derive(PartialEq, Eq, Debug)]
1316enum AccountNameErrorKind {
1317 Empty,
1318 Initial(char),
1319 Subsequent(Vec<char>),
1320}
1321
1322impl Display for AccountNameError {
1323 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1324 use AccountNameErrorKind::*;
1325 match &self.0 {
1326 Empty => write!(f, "empty account name"),
1327 Initial(bad_char) => write!(
1328 f,
1329 "invalid character '{}' for account name initial - must be uppercase ASCII letter or digit",
1330 bad_char
1331 ),
1332 Subsequent(bad_chars) => {
1333 format(f, bad_chars, single_quoted, ", ", Some("invalid character "))?;
1334 f.write_str(" in account name - must be alphanumeric or '-'")
1335 }
1336 }
1337 }
1338}
1339
1340impl std::error::Error for AccountNameError {}
1341
1342impl<'a> TryFrom<&'a str> for AccountName<'a> {
1343 type Error = AccountNameError;
1344
1345 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1346 use AccountNameErrorKind::*;
1347 if s.is_empty() {
1348 Err(AccountNameError(Empty))
1349 } else {
1350 let mut chars = s.chars();
1351 let initial = chars.next().unwrap();
1352 if !AccountName::is_valid_initial(&initial) {
1353 Err(AccountNameError(Initial(initial)))
1354 } else {
1355 let bad_chars = chars
1356 .filter(|c| (!AccountName::is_valid_subsequent(c)))
1357 .collect::<Vec<char>>();
1358 if bad_chars.is_empty() {
1359 Ok(AccountName(s))
1360 } else {
1361 Err(AccountNameError(Subsequent(bad_chars)))
1362 }
1363 }
1364 }
1365 }
1366}
1367
1368#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1370pub struct Currency<'a>(&'a str);
1371
1372const CURRENCY_INTERMEDIATE_EXTRA_CHARS: [char; 4] = ['\'', '.', '_', '-'];
1374
1375impl Currency<'_> {
1376 fn is_valid_initial(c: &char) -> bool {
1377 c.is_ascii_uppercase() || *c == '/'
1378 }
1379
1380 fn is_valid_intermediate(c: &char) -> bool {
1381 c.is_ascii_uppercase()
1382 || c.is_ascii_digit()
1383 || CURRENCY_INTERMEDIATE_EXTRA_CHARS.contains(c)
1384 }
1385
1386 fn is_valid_final(c: &char) -> bool {
1387 c.is_ascii_uppercase() || c.is_ascii_digit()
1388 }
1389}
1390
1391impl ElementType for Currency<'_> {
1392 fn element_type(&self) -> &'static str {
1393 "currency"
1394 }
1395}
1396
1397impl AsRef<str> for Currency<'_> {
1398 fn as_ref(&self) -> &str {
1399 self.0
1400 }
1401}
1402
1403impl PartialEq<&str> for Currency<'_> {
1404 fn eq(&self, other: &&str) -> bool {
1405 self.0 == *other
1406 }
1407}
1408
1409impl Display for Currency<'_> {
1410 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1411 write!(f, "{}", &self.0)
1412 }
1413}
1414
1415#[derive(PartialEq, Eq, Debug)]
1417pub struct CurrencyError(CurrencyErrorKind);
1418
1419#[derive(PartialEq, Eq, Debug)]
1420enum CurrencyErrorKind {
1421 Empty,
1422 Initial(char),
1423 Intermediate(Vec<char>),
1424 Final(char),
1425 MissingLetter,
1426}
1427
1428impl Display for CurrencyError {
1429 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1430 use CurrencyErrorKind::*;
1431 match &self.0 {
1432 Empty => write!(f, "empty currency"),
1433 Initial(bad_char) => write!(
1434 f,
1435 "invalid initial character '{}' for currency - must be uppercase ASCII letter or '/'",
1436 bad_char
1437 ),
1438 Intermediate(bad_chars) => {
1439 format(f, bad_chars, single_quoted, ", ", Some("invalid intermediate characters "))?;
1440 format(f, CURRENCY_INTERMEDIATE_EXTRA_CHARS, single_quoted, ", ", Some(" for currency - must be upppercase ASCII alphanumeric or one of "))
1441 }
1442 Final(bad_char) => write!(
1443 f,
1444 "invalid final character '{}' for currency - must be uppercase ASCII alphanumeric",
1445 bad_char
1446 ),
1447 MissingLetter => write!(f, "currency must contain at least one letter")
1448 }
1449 }
1450}
1451
1452impl std::error::Error for CurrencyError {}
1453
1454impl<'a> TryFrom<&'a str> for Currency<'a> {
1455 type Error = CurrencyError;
1456
1457 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1458 use CurrencyErrorKind::*;
1459 if s.is_empty() {
1460 Err(CurrencyError(Empty))
1461 } else {
1462 let mut chars = s.chars();
1463 let first = chars.next().unwrap();
1464 let intermediate: Vec<char> = if s.len() > 2 {
1465 chars.by_ref().take(s.len() - 2).collect()
1466 } else {
1467 empty::<char>().collect()
1468 };
1469 let last = if s.len() > 1 {
1470 chars.next().unwrap()
1471 } else {
1472 first
1473 };
1474
1475 use CurrencyErrorKind::*;
1476 if !Currency::is_valid_initial(&first) {
1477 Err(CurrencyError(Initial(first)))
1478 } else if !Currency::is_valid_final(&last) {
1479 Err(CurrencyError(Final(last)))
1480 } else {
1481 let bad_intermediates = intermediate
1482 .into_iter()
1483 .filter(|c| (!Currency::is_valid_intermediate(c)))
1484 .collect::<Vec<char>>();
1485 if !bad_intermediates.is_empty() {
1486 Err(CurrencyError(Intermediate(bad_intermediates)))
1487 } else if s.find(|c: char| c.is_ascii_uppercase()).is_none() {
1488 Err(CurrencyError(MissingLetter))
1489 } else {
1490 Ok(Currency(s))
1491 }
1492 }
1493 }
1494 }
1495}
1496
1497#[derive(PartialEq, Eq, Clone, Debug)]
1499pub struct Posting<'a> {
1500 pub(crate) flag: Option<Spanned<Flag>>,
1501 pub(crate) account: Spanned<Account<'a>>,
1502 pub(crate) amount: Option<Spanned<ExprValue>>,
1503 pub(crate) currency: Option<Spanned<Currency<'a>>>,
1504 pub(crate) cost_spec: Option<Spanned<CostSpec<'a>>>,
1505 pub(crate) price_annotation: Option<Spanned<PriceSpec<'a>>>,
1506 pub(crate) metadata: Spanned<Metadata<'a>>,
1507}
1508
1509impl<'a> Posting<'a> {
1510 pub fn flag(&self) -> Option<&Spanned<Flag>> {
1512 self.flag.as_ref()
1513 }
1514
1515 pub fn account(&self) -> &Spanned<Account> {
1517 &self.account
1518 }
1519
1520 pub fn amount(&self) -> Option<&Spanned<ExprValue>> {
1522 self.amount.as_ref()
1523 }
1524
1525 pub fn currency(&self) -> Option<&Spanned<Currency>> {
1527 self.currency.as_ref()
1528 }
1529
1530 pub fn cost_spec(&self) -> Option<&Spanned<CostSpec>> {
1532 self.cost_spec.as_ref()
1533 }
1534
1535 pub fn price_annotation(&self) -> Option<&Spanned<PriceSpec>> {
1537 self.price_annotation.as_ref()
1538 }
1539
1540 pub fn metadata(&self) -> &Metadata<'a> {
1542 &self.metadata
1543 }
1544}
1545
1546impl ElementType for Posting<'_> {
1547 fn element_type(&self) -> &'static str {
1548 "posting"
1549 }
1550}
1551
1552impl Display for Posting<'_> {
1553 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1554 simple_format(f, self.flag, None)?;
1555
1556 write!(
1557 f,
1558 "{}{}",
1559 if self.flag.is_some() { " " } else { "" },
1560 &self.account
1561 )?;
1562
1563 simple_format(f, &self.amount, Some(" "))?;
1564 simple_format(f, self.currency, Some(" "))?;
1565 simple_format(f, &self.cost_spec, Some(" "))?;
1566 simple_format(f, &self.price_annotation, Some(" @ "))?;
1567
1568 self.metadata.fmt(f)
1569 }
1570}
1571
1572#[derive(PartialEq, Eq, Clone, Default, Debug)]
1577pub struct Metadata<'a> {
1578 pub(crate) key_values: HashMap<Spanned<Key<'a>>, Spanned<MetaValue<'a>>>,
1579 pub(crate) tags: HashSet<Spanned<Tag<'a>>>,
1580 pub(crate) links: HashSet<Spanned<Link<'a>>>,
1581}
1582
1583impl Metadata<'_> {
1584 pub fn is_empty(&self) -> bool {
1586 self.key_values().len() == 0 && self.tags().len() == 0 && self.links().len() == 0
1587 }
1588
1589 pub fn key_values(
1591 &self,
1592 ) -> impl ExactSizeIterator<Item = (&Spanned<Key>, &Spanned<MetaValue>)> {
1593 self.key_values.iter()
1594 }
1595
1596 pub fn key_value<'s, 'k>(&'s self, key: Key<'k>) -> Option<&'s Spanned<MetaValue<'s>>>
1598 where
1599 'k: 's,
1600 {
1601 match self.key_values.keys().next() {
1604 None => None,
1605 Some(arbitrary_spanned_key) => {
1606 let key = spanned(key, *arbitrary_spanned_key.span());
1607 self.key_values.get(&key)
1608 }
1609 }
1610 }
1611
1612 pub fn tags(&self) -> impl ExactSizeIterator<Item = &Spanned<Tag>> {
1614 self.tags.iter()
1615 }
1616
1617 pub fn links(&self) -> impl ExactSizeIterator<Item = &Spanned<Link>> {
1619 self.links.iter()
1620 }
1621
1622 pub(crate) fn fmt_tags_links_inline(&self, f: &mut Formatter<'_>) -> fmt::Result {
1623 format(f, &self.tags, plain, SPACE, Some(SPACE))?;
1624 format(f, &self.links, plain, SPACE, Some(SPACE))
1625 }
1626
1627 pub(crate) fn fmt_keys_values(&self, f: &mut Formatter<'_>) -> fmt::Result {
1628 format(
1629 f,
1630 &self.key_values,
1631 key_value,
1632 NEWLINE_INDENT,
1633 Some(NEWLINE_INDENT),
1634 )
1635 }
1636}
1637
1638impl Display for Metadata<'_> {
1639 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1640 self.fmt_keys_values(f)?;
1641 format(f, &self.tags, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))?;
1642 format(f, &self.links, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))
1643 }
1644}
1645
1646#[derive(PartialEq, Eq, Clone, Debug)]
1647pub(crate) struct MetaKeyValue<'a> {
1648 pub(crate) key: Spanned<Key<'a>>,
1649 pub(crate) value: Spanned<MetaValue<'a>>,
1650}
1651
1652impl Display for MetaKeyValue<'_> {
1653 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1654 write!(f, "{}: \"{}\"", &self.key, &self.value)
1655 }
1656}
1657
1658#[derive(PartialEq, Eq, Clone, Debug)]
1660pub enum MetaValue<'a> {
1661 Simple(SimpleValue<'a>),
1662 Amount(Amount<'a>),
1663}
1664
1665impl Display for MetaValue<'_> {
1666 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1667 use MetaValue::*;
1668
1669 match self {
1670 Simple(simple_value) => simple_value.fmt(f),
1671 Amount(amount) => amount.fmt(f),
1672 }
1673 }
1674}
1675
1676#[derive(PartialEq, Eq, Clone, Debug)]
1678pub enum SimpleValue<'a> {
1679 String(&'a str),
1680 Currency(Currency<'a>),
1681 Account(Account<'a>),
1682 Tag(Tag<'a>),
1683 Link(Link<'a>),
1684 Date(Date),
1685 Bool(bool),
1686 None,
1687 Expr(ExprValue),
1688}
1689
1690impl ElementType for SimpleValue<'_> {
1691 fn element_type(&self) -> &'static str {
1692 "simple value"
1693 }
1694}
1695
1696impl Display for SimpleValue<'_> {
1697 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1698 use SimpleValue::*;
1699
1700 match self {
1701 String(x) => write!(f, r#""{}""#, x),
1702 Currency(x) => x.fmt(f),
1703 Account(x) => x.fmt(f),
1704 Tag(x) => x.fmt(f),
1705 Link(x) => x.fmt(f),
1706 Date(x) => x.fmt(f),
1707 Bool(x) => f.write_str(if *x { "TRUE" } else { "FALSE" }),
1708 None => Ok(()),
1709 Expr(x) => x.fmt(f),
1710 }
1711 }
1712}
1713
1714#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1716pub struct Tag<'a>(pub(crate) TagOrLinkIdentifier<'a>);
1717
1718impl<'a> From<TagOrLinkIdentifier<'a>> for Tag<'a> {
1719 fn from(id: TagOrLinkIdentifier<'a>) -> Self {
1720 Self(id)
1721 }
1722}
1723
1724impl<'a> TryFrom<&'a str> for Tag<'a> {
1725 type Error = TagOrLinkIdentifierError;
1726
1727 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1728 TagOrLinkIdentifier::try_from(s).map(Tag)
1729 }
1730}
1731
1732impl ElementType for Tag<'_> {
1733 fn element_type(&self) -> &'static str {
1734 "tag"
1735 }
1736}
1737
1738impl AsRef<str> for Tag<'_> {
1739 fn as_ref(&self) -> &str {
1740 self.0.as_ref()
1741 }
1742}
1743
1744impl PartialEq<&str> for Tag<'_> {
1745 fn eq(&self, other: &&str) -> bool {
1746 self.0 == *other
1747 }
1748}
1749
1750impl Display for Tag<'_> {
1751 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1752 write!(f, "#{}", self.0 .0)
1753 }
1754}
1755
1756#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1758pub struct Link<'a>(pub(crate) TagOrLinkIdentifier<'a>);
1759
1760impl<'a> From<TagOrLinkIdentifier<'a>> for Link<'a> {
1761 fn from(id: TagOrLinkIdentifier<'a>) -> Self {
1762 Self(id)
1763 }
1764}
1765
1766impl<'a> TryFrom<&'a str> for Link<'a> {
1767 type Error = TagOrLinkIdentifierError;
1768
1769 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1770 TagOrLinkIdentifier::try_from(s).map(Link)
1771 }
1772}
1773
1774impl ElementType for Link<'_> {
1775 fn element_type(&self) -> &'static str {
1776 "link"
1777 }
1778}
1779
1780impl PartialEq<&str> for Link<'_> {
1781 fn eq(&self, other: &&str) -> bool {
1782 self.0 == *other
1783 }
1784}
1785
1786impl Display for Link<'_> {
1787 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1788 write!(f, "^{}", self.0 .0)
1789 }
1790}
1791
1792impl AsRef<str> for Link<'_> {
1793 fn as_ref(&self) -> &str {
1794 self.0.as_ref()
1795 }
1796}
1797
1798#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1800pub struct TagOrLinkIdentifier<'a>(&'a str);
1801
1802const TAG_OR_LINK_EXTRA_CHARS: [char; 4] = ['-', '_', '/', '.'];
1804
1805impl TagOrLinkIdentifier<'_> {
1806 pub(crate) fn is_valid_char(c: &char) -> bool {
1807 c.is_alphanumeric() || TAG_OR_LINK_EXTRA_CHARS.contains(c)
1808 }
1809}
1810
1811#[derive(PartialEq, Eq, Debug)]
1813pub struct TagOrLinkIdentifierError(Vec<char>);
1814
1815impl Display for TagOrLinkIdentifierError {
1816 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1817 format(f, &self.0, single_quoted, ", ", Some("invalid characters "))?;
1818 format(
1819 f,
1820 TAG_OR_LINK_EXTRA_CHARS,
1821 single_quoted,
1822 ", ",
1823 Some(" for tag or link identifier - must be alphanumeric or one of "),
1824 )
1825 }
1826}
1827
1828impl std::error::Error for TagOrLinkIdentifierError {}
1829
1830impl<'a> TryFrom<&'a str> for TagOrLinkIdentifier<'a> {
1831 type Error = TagOrLinkIdentifierError;
1832
1833 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1834 let bad_chars = s
1835 .chars()
1836 .filter(|c| (!TagOrLinkIdentifier::is_valid_char(c)))
1837 .collect::<Vec<char>>();
1838 if bad_chars.is_empty() {
1839 Ok(TagOrLinkIdentifier(s))
1840 } else {
1841 Err(TagOrLinkIdentifierError(bad_chars))
1842 }
1843 }
1844}
1845
1846impl AsRef<str> for TagOrLinkIdentifier<'_> {
1847 fn as_ref(&self) -> &str {
1848 self.0
1849 }
1850}
1851
1852impl PartialEq<&str> for TagOrLinkIdentifier<'_> {
1853 fn eq(&self, other: &&str) -> bool {
1854 self.0 == *other
1855 }
1856}
1857
1858#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
1860pub struct Key<'a>(&'a str);
1861
1862impl Key<'_> {
1863 pub(crate) fn is_valid_initial(c: &char) -> bool {
1864 c.is_ascii_lowercase()
1865 }
1866
1867 pub(crate) fn is_valid_subsequent(c: &char) -> bool {
1868 c.is_alphanumeric() || *c == '-' || *c == '_'
1869 }
1870}
1871
1872impl AsRef<str> for Key<'_> {
1873 fn as_ref(&self) -> &str {
1874 self.0
1875 }
1876}
1877
1878impl ElementType for Key<'_> {
1879 fn element_type(&self) -> &'static str {
1880 "key"
1881 }
1882}
1883
1884impl PartialEq<&str> for Key<'_> {
1885 fn eq(&self, other: &&str) -> bool {
1886 self.0 == *other
1887 }
1888}
1889
1890impl Display for Key<'_> {
1891 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1892 write!(f, "{}", &self.0)
1893 }
1894}
1895
1896#[derive(PartialEq, Eq, Debug)]
1898pub struct KeyError(KeyErrorKind);
1899
1900#[derive(PartialEq, Eq, Debug)]
1901enum KeyErrorKind {
1902 Empty,
1903 Initial(char),
1904 Subsequent(Vec<char>),
1905}
1906
1907impl Display for KeyError {
1908 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1909 use KeyErrorKind::*;
1910 match &self.0 {
1911 Empty => write!(f, "empty key"),
1912 Initial(bad_char) => write!(
1913 f,
1914 "invalid character '{}' for key initial - must be lowercase ASCII letter",
1915 bad_char
1916 ),
1917 Subsequent(bad_chars) => {
1918 format(
1919 f,
1920 bad_chars,
1921 single_quoted,
1922 ", ",
1923 Some("invalid characters "),
1924 )?;
1925 f.write_str(" for key - must be alphanumeric or '-' or '_'")
1926 }
1927 }
1928 }
1929}
1930
1931impl std::error::Error for KeyError {}
1932
1933impl<'a> TryFrom<&'a str> for Key<'a> {
1934 type Error = KeyError;
1935
1936 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1937 use KeyErrorKind::*;
1938 if s.is_empty() {
1939 Err(KeyError(Empty))
1940 } else {
1941 let mut chars = s.chars();
1942 let initial = chars.next().unwrap();
1943 if !Key::is_valid_initial(&initial) {
1944 Err(KeyError(Initial(initial)))
1945 } else {
1946 let bad_chars = chars
1947 .filter(|c| (!Key::is_valid_subsequent(c)))
1948 .collect::<Vec<char>>();
1949 if bad_chars.is_empty() {
1950 Ok(Key(s))
1951 } else {
1952 Err(KeyError(Subsequent(bad_chars)))
1953 }
1954 }
1955 }
1956 }
1957}
1958
1959#[derive(PartialEq, Eq, Clone, Debug)]
1960pub struct ExprValue {
1964 value: Decimal,
1965 expr: Expr,
1966}
1967
1968impl ExprValue {
1969 pub fn value(&self) -> Decimal {
1971 self.value
1972 }
1973
1974 pub fn expr(&self) -> &Expr {
1976 &self.expr
1977 }
1978}
1979
1980impl PartialEq<Decimal> for ExprValue {
1981 fn eq(&self, other: &Decimal) -> bool {
1982 &self.value == other
1983 }
1984}
1985
1986impl From<Expr> for ExprValue {
1987 fn from(expr: Expr) -> Self {
1989 let (mut value, scale) = expr.evaluate();
1990 value.rescale(scale);
1991 Self { value, expr }
1992 }
1993}
1994
1995impl ElementType for ExprValue {
1997 fn element_type(&self) -> &'static str {
1998 "amount" }
2000}
2001
2002impl Display for ExprValue {
2003 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2004 Display::fmt(&self.expr, format)
2005 }
2006}
2007
2008#[derive(PartialEq, Eq, Clone)]
2010pub enum Expr {
2011 Value(Decimal),
2012 Add(Box<Expr>, Box<Expr>),
2013 Sub(Box<Expr>, Box<Expr>),
2014 Mul(Box<Expr>, Box<Expr>),
2015 Div(Box<Expr>, Box<Expr>),
2016 Neg(Box<Expr>),
2017 Paren(Box<Expr>),
2018}
2019
2020impl Expr {
2021 fn evaluate(&self) -> (Decimal, u32) {
2022 fn evaluate_unary<F>(op: F, e1: &Expr) -> (Decimal, u32)
2023 where
2024 F: Fn(Decimal) -> Decimal,
2025 {
2026 let (d1, s1) = e1.evaluate();
2027 (op(d1), s1)
2028 }
2029
2030 fn evaluate_binary<F>(op: F, e1: &Expr, e2: &Expr) -> (Decimal, u32)
2031 where
2032 F: Fn(Decimal, Decimal) -> Decimal,
2033 {
2034 let (d1, s1) = e1.evaluate();
2035 let (d2, s2) = e2.evaluate();
2036 (op(d1, d2), max(s1, s2))
2037 }
2038
2039 use Expr::*;
2040 match self {
2041 Value(d) => (*d, d.scale()),
2042 Add(e1, e2) => evaluate_binary(std::ops::Add::add, e1, e2),
2043 Sub(e1, e2) => evaluate_binary(std::ops::Sub::sub, e1, e2),
2044 Mul(e1, e2) => evaluate_binary(std::ops::Mul::mul, e1, e2),
2045 Div(e1, e2) => evaluate_binary(std::ops::Div::div, e1, e2),
2046 Neg(e1) => evaluate_unary(std::ops::Neg::neg, e1),
2047 Paren(e) => e.evaluate(),
2048 }
2049 }
2050}
2051
2052impl Display for Expr {
2057 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2058 use self::Expr::*;
2059 match *self {
2060 Value(val) => write!(format, "{}", val),
2061 Add(ref left, ref right) => write!(format, "{} + {}", left, right),
2062 Sub(ref left, ref right) => write!(format, "{} - {}", left, right),
2063 Mul(ref left, ref right) => write!(format, "{} * {}", left, right),
2064 Div(ref left, ref right) => write!(format, "{} / {}", left, right),
2065 Neg(ref expr) => write!(format, "-{}", expr),
2066 Paren(ref expr) => write!(format, "({})", expr),
2067 }
2068 }
2069}
2070
2071impl fmt::Debug for Expr {
2072 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2073 use self::Expr::*;
2074 match *self {
2075 Value(val) => write!(format, "{}", val),
2076 Add(ref left, ref right) => write!(format, "({:?} + {:?})", left, right),
2077 Sub(ref left, ref right) => write!(format, "({:?} - {:?})", left, right),
2078 Mul(ref left, ref right) => write!(format, "({:?} * {:?})", left, right),
2079 Div(ref left, ref right) => write!(format, "({:?} / {:?})", left, right),
2080 Neg(ref expr) => write!(format, "(-{:?})", expr),
2081 Paren(ref expr) => write!(format, "[{:?}]", expr),
2082 }
2083 }
2084}
2085
2086#[derive(PartialEq, Eq, Clone, Debug)]
2088pub enum CompoundExprValue {
2089 PerUnit(ExprValue),
2090 Total(ExprValue),
2091 PerUnitAndTotal(ExprValue, ExprValue),
2092}
2093
2094impl Display for CompoundExprValue {
2095 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2096 use self::CompoundExprValue::*;
2097
2098 match self {
2099 PerUnit(e) => write!(format, "{}", e),
2100 Total(e) => write!(format, "# {}", e),
2101 PerUnitAndTotal(per_unit, total) => write!(format, "{} # {}", per_unit, total),
2102 }
2103 }
2104}
2105
2106#[derive(PartialEq, Eq, Clone, Debug)]
2108pub enum ScopedExprValue {
2109 PerUnit(ExprValue),
2110 Total(ExprValue),
2111}
2112
2113impl Display for ScopedExprValue {
2114 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2115 use self::ScopedExprValue::*;
2116
2117 match self {
2118 PerUnit(e) => write!(format, "{}", e),
2119 Total(e) => write!(format, "# {}", e),
2120 }
2121 }
2122}
2123
2124#[derive(PartialEq, Eq, Clone, Debug)]
2126pub struct Amount<'a> {
2127 number: Spanned<ExprValue>,
2128 currency: Spanned<Currency<'a>>,
2129}
2130
2131impl<'a> Amount<'a> {
2132 pub(crate) fn new(amount: (Spanned<ExprValue>, Spanned<Currency<'a>>)) -> Self {
2133 Amount {
2134 number: amount.0,
2135 currency: amount.1,
2136 }
2137 }
2138
2139 pub fn number(&self) -> &Spanned<ExprValue> {
2141 &self.number
2142 }
2143
2144 pub fn currency(&self) -> &Spanned<Currency> {
2146 &self.currency
2147 }
2148}
2149
2150impl Display for Amount<'_> {
2151 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2152 write!(format, "{} {}", &self.number, &self.currency)
2153 }
2154}
2155
2156#[derive(PartialEq, Eq, Clone, Debug)]
2158pub struct AmountWithTolerance<'a> {
2159 amount: Spanned<Amount<'a>>,
2160 tolerance: Option<Spanned<Decimal>>,
2161}
2162
2163impl<'a> AmountWithTolerance<'a> {
2164 pub(crate) fn new(awt: (Spanned<Amount<'a>>, Option<Spanned<Decimal>>)) -> Self {
2165 AmountWithTolerance {
2166 amount: awt.0,
2167 tolerance: awt.1,
2168 }
2169 }
2170
2171 pub fn amount(&self) -> &Spanned<Amount> {
2173 &self.amount
2174 }
2175
2176 pub fn tolerance(&self) -> Option<&Spanned<Decimal>> {
2178 self.tolerance.as_ref()
2179 }
2180}
2181
2182impl Display for AmountWithTolerance<'_> {
2183 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2184 if let Some(tolerance) = self.tolerance {
2185 write!(format, "{} ~ {}", &self.amount, tolerance)
2186 } else {
2187 write!(format, "{}", &self.amount)
2188 }
2189 }
2190}
2191
2192#[derive(PartialEq, Eq, Clone, Debug)]
2194pub struct LooseAmount<'a> {
2195 number: Option<Spanned<ExprValue>>,
2196 currency: Option<Spanned<Currency<'a>>>,
2197}
2198
2199impl<'a> LooseAmount<'a> {
2200 pub(crate) fn new(amount: (Option<Spanned<ExprValue>>, Option<Spanned<Currency<'a>>>)) -> Self {
2201 LooseAmount {
2202 number: amount.0,
2203 currency: amount.1,
2204 }
2205 }
2206
2207 pub fn number(&self) -> Option<&Spanned<ExprValue>> {
2209 self.number.as_ref()
2210 }
2211
2212 pub fn currency(&self) -> Option<&Spanned<Currency>> {
2214 self.currency.as_ref()
2215 }
2216}
2217
2218#[derive(PartialEq, Eq, Clone, Debug)]
2220pub enum CompoundAmount<'a> {
2221 BareCurrency(Currency<'a>),
2222 BareAmount(CompoundExprValue),
2223 CurrencyAmount(CompoundExprValue, Currency<'a>),
2224}
2225
2226impl Display for CompoundAmount<'_> {
2227 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2228 use self::CompoundAmount::*;
2229 match self {
2230 BareCurrency(cur) => write!(format, "{}", cur),
2231 BareAmount(ce) => write!(format, "{}", ce),
2232 CurrencyAmount(ce, cur) => write!(format, "{} {}", ce, cur),
2233 }
2234 }
2235}
2236
2237#[derive(PartialEq, Eq, Clone, Debug)]
2239pub struct CostSpec<'a> {
2240 per_unit: Option<Spanned<ExprValue>>,
2241 total: Option<Spanned<ExprValue>>,
2242 currency: Option<Spanned<Currency<'a>>>,
2243 date: Option<Spanned<Date>>,
2244 label: Option<Spanned<&'a str>>,
2245 merge: bool,
2246}
2247
2248impl CostSpec<'_> {
2249 pub fn per_unit(&self) -> Option<&Spanned<ExprValue>> {
2251 self.per_unit.as_ref()
2252 }
2253
2254 pub fn total(&self) -> Option<&Spanned<ExprValue>> {
2256 self.total.as_ref()
2257 }
2258
2259 pub fn currency(&self) -> Option<&Spanned<Currency<'_>>> {
2261 self.currency.as_ref()
2262 }
2263
2264 pub fn date(&self) -> Option<&Spanned<Date>> {
2266 self.date.as_ref()
2267 }
2268
2269 pub fn label(&self) -> Option<&Spanned<&str>> {
2271 self.label.as_ref()
2272 }
2273
2274 pub fn merge(&self) -> bool {
2276 self.merge
2277 }
2278}
2279
2280impl ElementType for CostSpec<'_> {
2281 fn element_type(&self) -> &'static str {
2282 "cost specification"
2283 }
2284}
2285
2286impl Display for CostSpec<'_> {
2287 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2288 let mut prefix = "";
2289 let space = " ";
2290
2291 f.write_str("{")?;
2292
2293 if let Some(per_unit) = &self.per_unit {
2294 write!(f, "{}{}", prefix, per_unit)?;
2295 prefix = space;
2296 }
2297
2298 if let Some(total) = &self.total {
2299 write!(f, "{}# {}", prefix, total)?;
2300 prefix = space;
2301 }
2302
2303 if let Some(currency) = &self.currency {
2304 write!(f, "{}{}", prefix, currency)?;
2305 prefix = space;
2306 }
2307
2308 if let Some(date) = &self.date {
2309 write!(f, "{}{}", prefix, date)?;
2310 prefix = space;
2311 }
2312
2313 if let Some(label) = &self.label {
2314 write!(f, "{}\"{}\"", prefix, label)?;
2315 prefix = space;
2316 }
2317
2318 if self.merge {
2319 write!(f, "{}*", prefix)?;
2320 }
2321
2322 f.write_str("}")
2323 }
2324}
2325
2326#[derive(Default, Debug)]
2327pub(crate) struct CostSpecBuilder<'a> {
2329 per_unit: Option<Spanned<ExprValue>>,
2330 total: Option<Spanned<ExprValue>>,
2331 currency: Option<Spanned<Currency<'a>>>,
2332 date: Option<Spanned<Date>>,
2333 label: Option<Spanned<&'a str>>,
2334 merge: bool,
2335 errors: Vec<CostSpecError>,
2336}
2337
2338impl<'a> CostSpecBuilder<'a> {
2339 pub(crate) fn compound_expr(self, value: CompoundExprValue, span: Span) -> Self {
2340 use CompoundExprValue::*;
2341
2342 match value {
2343 PerUnit(value) => self.per_unit(spanned(value, span)),
2344 Total(value) => self.total(spanned(value, span)),
2345 PerUnitAndTotal(per_unit, total) => self
2346 .per_unit(spanned(per_unit, span))
2347 .total(spanned(total, span)),
2348 }
2349 }
2350
2351 fn per_unit(mut self, value: Spanned<ExprValue>) -> Self {
2352 if self.per_unit.is_none() {
2353 self.per_unit = Some(value);
2354 } else {
2355 self.errors.push(CostSpecError(CostSpecErrorKind::PerUnit))
2356 }
2357 self
2358 }
2359
2360 fn total(mut self, value: Spanned<ExprValue>) -> Self {
2361 if self.total.is_none() {
2362 self.total = Some(value);
2363 } else {
2364 self.errors.push(CostSpecError(CostSpecErrorKind::Total))
2365 }
2366 self
2367 }
2368
2369 pub(crate) fn currency(mut self, value: Currency<'a>, span: Span) -> Self {
2370 if self.currency.is_none() {
2371 self.currency = Some(spanned(value, span));
2372 } else {
2373 self.errors.push(CostSpecError(CostSpecErrorKind::Currency))
2374 }
2375 self
2376 }
2377
2378 pub(crate) fn date(mut self, value: Date, span: Span) -> Self {
2379 if self.date.is_none() {
2380 self.date = Some(spanned(value, span));
2381 } else {
2382 self.errors.push(CostSpecError(CostSpecErrorKind::Date))
2383 }
2384 self
2385 }
2386
2387 pub(crate) fn label(mut self, value: &'a str, span: Span) -> Self {
2388 if self.label.is_none() {
2389 self.label = Some(spanned(value, span));
2390 } else {
2391 self.errors.push(CostSpecError(CostSpecErrorKind::Label))
2392 }
2393 self
2394 }
2395
2396 pub(crate) fn merge(mut self, _span: Span) -> Self {
2397 if !self.merge {
2398 self.merge = true;
2400 } else {
2401 self.errors
2402 .push(CostSpecError(CostSpecErrorKind::MergeCost))
2403 }
2404 self
2405 }
2406
2407 pub(crate) fn build<'b>(&'b mut self) -> Result<CostSpec<'a>, CostSpecErrors>
2409 where
2410 'a: 'b,
2411 {
2412 let per_unit = self.per_unit.take();
2413 let total = self.total.take();
2414 let currency = self.currency.take();
2415 let date = self.date.take();
2416 let label = self.label.take();
2417 let merge = self.merge;
2418 self.merge = false;
2419
2420 if !self.errors.is_empty() {
2421 let mut errors = Vec::new();
2422 swap(&mut self.errors, &mut errors);
2423 Err(CostSpecErrors(errors))
2424 } else {
2425 Ok(CostSpec {
2426 per_unit,
2427 total,
2428 currency,
2429 date,
2430 label,
2431 merge,
2432 })
2433 }
2434 }
2436}
2437
2438#[derive(PartialEq, Eq, Debug)]
2440pub struct CostSpecError(CostSpecErrorKind);
2441
2442#[derive(PartialEq, Eq, Display, Debug)]
2443#[strum(serialize_all = "kebab-case")]
2444enum CostSpecErrorKind {
2445 PerUnit,
2446 Total,
2447 Currency,
2448 Date,
2449 Label,
2450 MergeCost,
2451}
2452
2453impl CostSpecErrorKind {
2454 fn unless(self, condition: bool) -> Result<(), CostSpecError> {
2455 if condition {
2456 Ok(())
2457 } else {
2458 Err(CostSpecError(self))
2459 }
2460 }
2461}
2462
2463impl Display for CostSpecError {
2464 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2465 write!(f, "duplicate {} field in cost specification", self.0)
2466 }
2467}
2468
2469impl std::error::Error for CostSpecError {}
2470
2471#[derive(PartialEq, Eq, Debug)]
2473pub struct CostSpecErrors(Vec<CostSpecError>);
2474
2475impl Display for CostSpecErrors {
2476 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2477 format(f, &self.0, plain, ", ", None)
2478 }
2479}
2480
2481impl std::error::Error for CostSpecErrors {}
2482
2483#[derive(PartialEq, Eq, Clone, Debug)]
2486pub enum PriceSpec<'a> {
2487 Unspecified,
2488 BareCurrency(Currency<'a>),
2489 BareAmount(ScopedExprValue),
2490 CurrencyAmount(ScopedExprValue, Currency<'a>),
2491}
2492
2493impl Display for PriceSpec<'_> {
2494 fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
2495 use self::PriceSpec::*;
2496 match self {
2497 Unspecified => Ok(()),
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 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 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;