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