use crate::{format::*, options::BeancountOption};
use chumsky::{
extra::ParserExtra,
input::{Input, MapExtra},
};
use rust_decimal::Decimal;
use std::{
cmp::max,
collections::{HashMap, HashSet, hash_map},
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
iter::empty,
mem::swap,
ops::Deref,
};
use std::{marker::PhantomData, ops::DerefMut};
use strum_macros::{Display, EnumIter, EnumString, IntoStaticStr};
use time::Date;
pub trait Report {
fn message(&self) -> &str;
fn reason(&self) -> &str;
fn span(&self) -> Span;
fn contexts(&self) -> impl Iterator<Item = (&str, Span)>;
fn related(&self) -> impl Iterator<Item = (&str, Span)>;
}
#[derive(Clone, Debug)]
pub struct ErrorOrWarning<K>(pub(crate) Box<ErrorOrWarningImpl<K>>);
impl<K> From<ErrorOrWarningImpl<K>> for ErrorOrWarning<K> {
fn from(value: ErrorOrWarningImpl<K>) -> Self {
Self(Box::new(value))
}
}
#[derive(Clone, Debug)]
pub(crate) struct ErrorOrWarningImpl<K> {
pub(crate) message: String,
pub(crate) reason: String,
pub(crate) span: Span,
pub(crate) contexts: Option<Vec<(String, Span)>>,
pub(crate) related: Option<Vec<(String, Span)>>,
kind: PhantomData<K>,
}
#[derive(Clone, Debug)]
pub struct ErrorKind;
pub type Error = ErrorOrWarning<ErrorKind>;
#[derive(Clone, Debug)]
pub struct WarningKind;
pub type Warning = ErrorOrWarning<WarningKind>;
impl<K> Report for ErrorOrWarning<K> {
fn message(&self) -> &str {
&self.0.message
}
fn reason(&self) -> &str {
&self.0.reason
}
fn span(&self) -> Span {
self.0.span
}
fn contexts(&self) -> impl Iterator<Item = (&str, Span)> {
self.0
.contexts
.iter()
.flatten()
.map(|(element, span)| (element.as_str(), *span))
}
fn related(&self) -> impl Iterator<Item = (&str, Span)> {
self.0
.related
.iter()
.flatten()
.map(|(element, span)| (element.as_str(), *span))
}
}
impl<K> ErrorOrWarning<K>
where
K: ErrorOrWarningKind,
{
pub(crate) fn new<M: Into<String>, R: Into<String>>(message: M, reason: R, span: Span) -> Self {
ErrorOrWarningImpl::<K> {
message: message.into(),
reason: reason.into(),
span,
contexts: None,
related: None,
kind: PhantomData,
}
.into()
}
pub fn related_to<'a, 'b, T>(self, element: &'b Spanned<T>) -> Self
where
T: ElementType<'a> + 'b,
{
let mut e = self;
let related = e.0.related.get_or_insert(Vec::new());
related.push((element.element_type().to_string(), element.span));
e
}
pub fn related_to_all<'a, 'b, T, I>(self, elements: I) -> Self
where
T: ElementType<'a> + 'b,
I: IntoIterator<Item = &'b Spanned<T>>,
{
let mut new_related = elements
.into_iter()
.map(|element| (element.element_type().to_string(), element.span))
.collect::<Vec<_>>();
let mut e = self;
let related = e.0.related.get_or_insert(Vec::new());
related.append(&mut new_related);
e
}
pub(crate) fn related_to_named_span<S>(self, name: S, span: Span) -> Self
where
S: ToString,
{
let mut e = self;
let related = e.0.related.get_or_insert(Vec::new());
related.push((name.to_string(), span));
e
}
pub fn in_context<'a, T>(self, element: &Spanned<T>) -> Self
where
T: ElementType<'a>,
{
let mut e = self;
let contexts = e.0.contexts.get_or_insert(Vec::new());
contexts.push((element.element_type().to_string(), element.span));
e
}
pub fn in_contexts<'a, 'b, T, I>(self, elements: I) -> Self
where
T: ElementType<'a> + 'b,
I: IntoIterator<Item = &'b Spanned<T>>,
{
let mut new_contexts = elements
.into_iter()
.map(|element| (element.element_type().to_string(), element.span))
.collect::<Vec<_>>();
let mut e = self;
let contexts = e.0.contexts.get_or_insert(Vec::new());
contexts.append(&mut new_contexts);
e
}
pub(crate) fn in_explicitly_labelled_contexts<I>(self, elements: I) -> Self
where
I: IntoIterator<Item = (String, Span)>,
{
let mut new_contexts = elements.into_iter().collect::<Vec<_>>();
let mut e = self;
let contexts = e.0.contexts.get_or_insert(Vec::new());
contexts.append(&mut new_contexts);
e
}
pub fn with_annotation<S>(self, annotation: S) -> AnnotatedErrorOrWarning<K>
where
S: Into<String>,
{
AnnotatedErrorOrWarning {
error_or_warning: self,
annotation: Some(annotation.into()),
}
}
}
impl<K> ErrorOrWarningImpl<K>
where
K: ErrorOrWarningKind,
{
pub(crate) fn color(&self) -> ariadne::Color {
K::color()
}
pub(crate) fn report_kind(&self) -> ariadne::ReportKind<'static> {
K::report_kind()
}
}
fn fmt_error_or_warning<K>(value: &ErrorOrWarning<K>, f: &mut Formatter<'_>) -> fmt::Result
where
K: ErrorOrWarningKind,
{
write!(f, "{} ({}) ", value.0.message, value.0.reason)?;
write!(
f,
"at {}..{} of source {}",
value.0.span.start, value.0.span.end, value.0.span.source
)?;
if let Some(contexts) = &value.0.contexts {
for context in contexts.iter() {
write!(f, " while parsing {} at {:?}", context.0, context.1)?;
}
}
Ok(())
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt_error_or_warning(self, f)
}
}
impl std::error::Error for Error {}
impl Display for Warning {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt_error_or_warning(self, f)
}
}
pub trait ErrorOrWarningKind {
fn report_kind() -> ariadne::ReportKind<'static>;
fn color() -> ariadne::Color;
}
impl ErrorOrWarningKind for ErrorKind {
fn report_kind() -> ariadne::ReportKind<'static> {
ariadne::ReportKind::Error
}
fn color() -> ariadne::Color {
ariadne::Color::Red
}
}
impl ErrorOrWarningKind for WarningKind {
fn report_kind() -> ariadne::ReportKind<'static> {
ariadne::ReportKind::Warning
}
fn color() -> ariadne::Color {
ariadne::Color::Yellow
}
}
#[derive(Clone, Debug)]
pub struct AnnotatedErrorOrWarning<K>
where
K: ErrorOrWarningKind,
{
pub error_or_warning: ErrorOrWarning<K>,
pub annotation: Option<String>,
}
impl<K> From<ErrorOrWarning<K>> for AnnotatedErrorOrWarning<K>
where
K: ErrorOrWarningKind,
{
fn from(error_or_warning: ErrorOrWarning<K>) -> Self {
Self {
error_or_warning,
annotation: None,
}
}
}
impl<K> Deref for AnnotatedErrorOrWarning<K>
where
K: ErrorOrWarningKind,
{
type Target = ErrorOrWarning<K>;
fn deref(&self) -> &Self::Target {
&self.error_or_warning
}
}
impl<K> AnnotatedErrorOrWarning<K>
where
K: ErrorOrWarningKind,
{
pub fn annotation(&self) -> Option<&str> {
self.annotation.as_deref()
}
}
pub type AnnotatedError = AnnotatedErrorOrWarning<ErrorKind>;
pub type AnnotatedWarning = AnnotatedErrorOrWarning<WarningKind>;
pub struct SpannedSource<'a> {
pub file_name: Option<&'a str>,
pub start_line: usize,
pub end_line: usize,
pub content: &'a str,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy, EnumString, EnumIter, IntoStaticStr, Debug)]
pub enum AccountType {
Assets,
Liabilities,
Equity,
Income,
Expenses,
}
impl AsRef<str> for AccountType {
fn as_ref(&self) -> &'static str {
self.into()
}
}
#[derive(PartialEq, Eq, Default, Clone, Copy, Debug)]
pub enum Flag {
#[default]
Asterisk,
Exclamation,
Ampersand,
Hash,
Question,
Percent,
Letter(FlagLetter),
}
impl ElementType<'static> for Flag {
fn element_type(&self) -> &'static str {
"flag"
}
}
impl Display for Flag {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use Flag::*;
let (prefix, c) = match self {
Asterisk => (None, '*'),
Exclamation => (None, '!'),
Ampersand => (None, '&'),
Hash => (None, '#'),
Question => (None, '?'),
Percent => (None, '%'),
Letter(FlagLetter(c)) => (Some('\''), *c),
};
match prefix {
Some(prefix) => write!(f, "{}{}", prefix, c),
None => write!(f, "{}", c),
}
}
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub struct FlagLetter(char);
impl FlagLetter {
pub fn char(&self) -> char {
self.0
}
pub(crate) fn is_valid(c: &char) -> bool {
c.is_ascii_uppercase()
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct FlagLetterError(char);
impl Display for FlagLetterError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid character '{}' for flag letter - must be uppercase ASCII",
self.0
)
}
}
impl std::error::Error for FlagLetterError {}
impl TryFrom<char> for FlagLetter {
type Error = FlagLetterError;
fn try_from(c: char) -> Result<Self, Self::Error> {
if FlagLetter::is_valid(&c) {
Ok(FlagLetter(c))
} else {
Err(FlagLetterError(c))
}
}
}
#[derive(
EnumString, EnumIter, IntoStaticStr, PartialEq, Eq, Default, Clone, Copy, Display, Debug,
)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum Booking {
#[default]
Strict,
StrictWithSize,
None,
Average,
Fifo,
Lifo,
Hifo,
}
impl AsRef<str> for Booking {
fn as_ref(&self) -> &'static str {
self.into()
}
}
impl ElementType<'static> for Booking {
fn element_type(&self) -> &'static str {
"booking"
}
}
#[derive(
EnumString, EnumIter, IntoStaticStr, PartialEq, Eq, Default, Clone, Copy, Display, Debug,
)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum PluginProcessingMode {
#[default]
Default,
Raw,
}
#[derive(PartialEq, Eq, Copy, Clone, Default, Debug)]
pub struct SourceId(u32);
impl From<usize> for SourceId {
fn from(value: usize) -> Self {
SourceId(value as u32)
}
}
impl From<SourceId> for usize {
fn from(value: SourceId) -> Self {
value.0 as usize
}
}
impl Display for SourceId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub(crate) type Span_ = chumsky::span::SimpleSpan<usize, SourceId>;
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub struct Span {
pub source: usize,
pub start: usize,
pub end: usize,
}
impl From<&Span_> for Span {
fn from(value: &Span_) -> Self {
Span {
source: value.context.into(),
start: value.start,
end: value.end,
}
}
}
impl From<Span_> for Span {
fn from(value: Span_) -> Self {
Span {
source: value.context.into(),
start: value.start,
end: value.end,
}
}
}
impl From<Span> for Span_ {
fn from(value: Span) -> Self {
chumsky::span::SimpleSpan {
start: value.start,
end: value.end,
context: value.source.into(),
}
}
}
#[derive(Clone, Debug)]
pub struct Spanned<T> {
pub(crate) item: T,
pub(crate) span: Span,
}
impl<T> Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.item
}
}
impl<T> DerefMut for Spanned<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.item
}
}
pub(crate) fn spanned_<T>(item: T, span: Span_) -> Spanned<T> {
Spanned {
item,
span: span.into(),
}
}
pub fn spanned<T>(item: T, span: Span) -> Spanned<T> {
Spanned { item, span }
}
pub(crate) fn spanned_extra<'a, 'b, T, I, E>(item: T, e: &mut MapExtra<'a, 'b, I, E>) -> Spanned<T>
where
I: Input<'a, Span = Span_>,
E: ParserExtra<'a, I>,
{
Spanned {
item,
span: e.span().into(),
}
}
impl<T> Spanned<T> {
pub fn item(&self) -> &T {
&self.item
}
pub fn span(&self) -> &Span {
&self.span
}
pub fn as_ref(&self) -> Spanned<&T> {
Spanned {
item: &self.item,
span: self.span,
}
}
pub fn map<U, F>(&self, f: F) -> Spanned<U>
where
F: FnOnce(&T) -> U,
{
Spanned {
item: f(&self.item),
span: self.span,
}
}
pub fn map_into<U, F>(self, f: F) -> Spanned<U>
where
F: FnOnce(T) -> U,
{
Spanned {
item: f(self.item),
span: self.span,
}
}
}
impl<'a, T> Spanned<T>
where
T: ElementType<'a>,
{
pub fn error<S: Into<String>>(&self, reason: S) -> Error {
Error::new(
format!("invalid {}", self.element_type()),
reason,
self.span,
)
}
pub fn warning<S: Into<String>>(&self, reason: S) -> Warning {
Warning::new(
format!("questionable {}", self.element_type()),
reason,
self.span,
)
}
}
impl<T> PartialEq for Spanned<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.item.eq(&other.item)
}
}
impl<T> Eq for Spanned<T> where T: Eq {}
impl<T> Hash for Spanned<T>
where
T: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.item.hash(state)
}
}
impl<T> Copy for Spanned<T> where T: Copy {}
impl<T> Display for Spanned<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.item,)
}
}
pub trait OptionalItem<T> {
fn item(&self) -> Option<&T>;
}
impl<T> OptionalItem<T> for Option<&Spanned<T>> {
fn item(&self) -> Option<&T> {
self.map(|spanned| spanned.item())
}
}
pub trait ElementType<'a> {
fn element_type(&self) -> &'a str;
}
impl<'a, T> ElementType<'a> for &T
where
T: ElementType<'a>,
{
fn element_type(&self) -> &'a str {
(**self).element_type()
}
}
impl<'a, T> ElementType<'a> for &mut T
where
T: ElementType<'a>,
{
fn element_type(&self) -> &'a str {
(**self).element_type()
}
}
impl<'a, T> ElementType<'a> for Box<T>
where
T: ElementType<'a>,
{
fn element_type(&self) -> &'a str {
(**self).element_type()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum Declaration<'a> {
Directive(Directive<'a>),
Pragma(Pragma<'a>),
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Directive<'a> {
pub(crate) date: Spanned<Date>,
pub(crate) metadata: Spanned<Metadata<'a>>,
pub(crate) variant: DirectiveVariant<'a>,
}
impl<'a> Directive<'a> {
pub fn date(&self) -> &Spanned<Date> {
&self.date
}
pub fn metadata(&self) -> &Metadata<'a> {
&self.metadata
}
pub fn variant(&self) -> &DirectiveVariant<'a> {
&self.variant
}
}
impl ElementType<'static> for Directive<'_> {
fn element_type(&self) -> &'static str {
use DirectiveVariant::*;
match &self.variant {
Transaction(_) => "transaction",
Price(_) => "price",
Balance(_) => "balance",
Open(_) => "open",
Close(_) => "close",
Commodity(_) => "commodity",
Pad(_) => "pad",
Document(_) => "document",
Note(_) => "note",
Event(_) => "event",
Query(_) => "query",
Custom(_) => "custom",
}
}
}
impl Display for Directive<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use DirectiveVariant::*;
match &self.variant {
Transaction(x) => x.fmt(f, self.date.item, &self.metadata),
Price(x) => x.fmt(f, self.date.item, &self.metadata),
Balance(x) => x.fmt(f, self.date.item, &self.metadata),
Open(x) => x.fmt(f, self.date.item, &self.metadata),
Close(x) => x.fmt(f, self.date.item, &self.metadata),
Commodity(x) => x.fmt(f, self.date.item, &self.metadata),
Pad(x) => x.fmt(f, self.date.item, &self.metadata),
Document(x) => x.fmt(f, self.date.item, &self.metadata),
Note(x) => x.fmt(f, self.date.item, &self.metadata),
Event(x) => x.fmt(f, self.date.item, &self.metadata),
Query(x) => x.fmt(f, self.date.item, &self.metadata),
Custom(x) => x.fmt(f, self.date.item, &self.metadata),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum DirectiveVariant<'a> {
Transaction(Transaction<'a>),
Price(Price<'a>),
Balance(Balance<'a>),
Open(Open<'a>),
Close(Close<'a>),
Commodity(Commodity<'a>),
Pad(Pad<'a>),
Document(Document<'a>),
Note(Note<'a>),
Event(Event<'a>),
Query(Query<'a>),
Custom(Custom<'a>),
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub(crate) enum Pragma<'a> {
Pushtag(Spanned<Tag<'a>>),
Poptag(Spanned<Tag<'a>>),
Pushmeta(MetaKeyValue<'a>),
Popmeta(Spanned<Key<'a>>),
Include(Spanned<&'a str>),
Option(BeancountOption<'a>),
Plugin(Plugin<'a>),
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Transaction<'a> {
pub(crate) flag: Spanned<Flag>,
pub(crate) payee: Option<Spanned<&'a str>>,
pub(crate) narration: Option<Spanned<&'a str>>,
pub(crate) postings: Vec<Spanned<Posting<'a>>>,
}
impl<'a> Transaction<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} {}", date, self.flag)?;
format(f, &self.payee, double_quoted, " ", Some(" "))?;
format(f, &self.narration, double_quoted, " ", Some(" "))?;
if self.payee.is_some() && self.narration.is_none() {
f.write_str(r#" """#)?;
}
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)?;
format(
f,
&self.postings,
plain,
NEWLINE_INDENT,
Some(NEWLINE_INDENT),
)
}
pub fn flag(&self) -> &Spanned<Flag> {
&self.flag
}
pub fn payee(&self) -> Option<&Spanned<&'a str>> {
self.payee.as_ref()
}
pub fn narration(&self) -> Option<&Spanned<&'a str>> {
self.narration.as_ref()
}
pub fn postings(&self) -> impl ExactSizeIterator<Item = &Spanned<Posting<'a>>> {
self.postings.iter()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Price<'a> {
pub(crate) currency: Spanned<Currency<'a>>,
pub(crate) amount: Spanned<Amount<'a>>,
}
impl<'a> Price<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} price {} {}", date, &self.currency, &self.amount)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)?;
Ok(())
}
pub fn currency(&self) -> &Spanned<Currency<'a>> {
&self.currency
}
pub fn amount(&self) -> &Spanned<Amount<'a>> {
&self.amount
}
}
impl ElementType<'static> for Price<'_> {
fn element_type(&self) -> &'static str {
"price"
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Balance<'a> {
pub(crate) account: Spanned<Account<'a>>,
pub(crate) atol: Spanned<AmountWithTolerance<'a>>,
}
impl<'a> Balance<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} balance {} {}", date, &self.account, &self.atol)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)?;
Ok(())
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
pub fn atol(&self) -> &Spanned<AmountWithTolerance<'a>> {
&self.atol
}
}
impl ElementType<'static> for Balance<'_> {
fn element_type(&self) -> &'static str {
"balance"
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Open<'a> {
pub(crate) account: Spanned<Account<'a>>,
pub(crate) currencies: HashSet<Spanned<Currency<'a>>>,
pub(crate) booking: Option<Spanned<Booking>>,
}
impl<'a> Open<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} open {}", date, self.account)?;
format(f, &self.currencies, plain, ",", Some(" "))?;
format(f, &self.booking, double_quoted, " ", Some(" "))?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
pub fn currencies(&self) -> impl ExactSizeIterator<Item = &Spanned<Currency<'a>>> {
self.currencies.iter()
}
pub fn booking(&self) -> Option<&Spanned<Booking>> {
self.booking.as_ref()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Close<'a> {
pub(crate) account: Spanned<Account<'a>>,
}
impl<'a> Close<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} close {}", date, self.account)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Commodity<'a> {
pub(crate) currency: Spanned<Currency<'a>>,
}
impl<'a> Commodity<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} commodity {}", date, self.currency)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn currency(&self) -> &Spanned<Currency<'a>> {
&self.currency
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Pad<'a> {
pub(crate) account: Spanned<Account<'a>>,
pub(crate) source: Spanned<Account<'a>>,
}
impl<'a> Pad<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} pad {} {}", date, self.account, self.source)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
pub fn source(&self) -> &Spanned<Account<'a>> {
&self.source
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Document<'a> {
pub(crate) account: Spanned<Account<'a>>,
pub(crate) path: Spanned<&'a str>,
}
impl<'a> Document<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} document {} \"{}\"", date, self.account, self.path)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
pub fn path(&self) -> &Spanned<&'a str> {
&self.path
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Note<'a> {
pub(crate) account: Spanned<Account<'a>>,
pub(crate) comment: Spanned<&'a str>,
}
impl<'a> Note<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} note {} \"{}\"", date, self.account, self.comment)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
pub fn comment(&self) -> &Spanned<&'a str> {
&self.comment
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Event<'a> {
pub(crate) event_type: Spanned<&'a str>,
pub(crate) description: Spanned<&'a str>,
}
impl<'a> Event<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(
f,
"{} event \"{}\" \"{}\"",
date, self.event_type, self.description
)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn event_type(&self) -> &Spanned<&'a str> {
&self.event_type
}
pub fn description(&self) -> &Spanned<&'a str> {
&self.description
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Query<'a> {
pub(crate) name: Spanned<&'a str>,
pub(crate) content: Spanned<&'a str>,
}
impl<'a> Query<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} query \"{}\" \"{}\"", date, self.name, self.content)?;
metadata.fmt_tags_links_inline(f)?;
metadata.fmt_keys_values(f)
}
pub fn name(&self) -> &Spanned<&'a str> {
&self.name
}
pub fn content(&self) -> &Spanned<&'a str> {
&self.content
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Custom<'a> {
pub(crate) type_: Spanned<&'a str>,
pub(crate) values: Vec<Spanned<MetaValue<'a>>>,
}
impl<'a> Custom<'a> {
fn fmt(&self, f: &mut Formatter<'_>, date: Date, metadata: &Metadata) -> fmt::Result {
write!(f, "{} custom \"{}\"", date, self.type_)?;
format(f, &self.values, plain, SPACE, Some(SPACE))?;
metadata.fmt_tags_links_on_separate_lines(f)?;
metadata.fmt_keys_values(f)
}
pub fn type_(&self) -> &Spanned<&'a str> {
&self.type_
}
pub fn values(&self) -> impl ExactSizeIterator<Item = &Spanned<MetaValue<'a>>> {
self.values.iter()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Plugin<'a> {
pub(crate) module_name: Spanned<&'a str>,
pub(crate) config: Option<Spanned<&'a str>>,
}
impl<'a> Plugin<'a> {
pub fn module_name(&self) -> &Spanned<&'a str> {
&self.module_name
}
pub fn config(&self) -> Option<&Spanned<&'a str>> {
self.config.as_ref()
}
}
impl Display for Plugin<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "plugin \"{}\"", self.module_name)?;
if let Some(config) = &self.config {
write!(f, " \"{}\"", config)?;
}
writeln!(f)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Account<'a> {
account_type: AccountType,
name: &'a str,
}
impl<'a> Account<'a> {
pub(crate) fn new(
s: &'a str,
account_type_names: &AccountTypeNames,
) -> Result<Self, AccountError> {
let mut account = s.split(':');
let account_type_name = AccountTypeName::try_from(
account
.by_ref()
.next()
.ok_or(AccountError(AccountErrorKind::MissingColon))?,
)
.map_err(|e| AccountError(AccountErrorKind::TypeName(e)))?;
for subaccount in account {
let _ = AccountName::try_from(subaccount)
.map_err(|e| AccountError(AccountErrorKind::AccountName(e)))?;
}
match account_type_names.get(&account_type_name) {
Some(account_type) => Ok(Account {
account_type,
name: s,
}),
None => Err(AccountError(AccountErrorKind::UnknownAccountType(format!(
"unknown account type {}, must be one of {}",
&account_type_name, account_type_names
)))),
}
}
pub fn account_type(&self) -> AccountType {
self.account_type
}
}
impl Display for Account<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.name)
}
}
impl<'a> AsRef<str> for Account<'a> {
fn as_ref(&self) -> &'a str {
self.name
}
}
impl<'a> From<&'_ Account<'a>> for &'a str {
fn from(value: &Account<'a>) -> Self {
value.name
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct AccountError(AccountErrorKind);
#[derive(PartialEq, Eq, Debug)]
enum AccountErrorKind {
MissingColon,
TypeName(AccountTypeNameError),
AccountName(AccountNameError),
UnknownAccountType(String),
}
impl Display for AccountError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use AccountErrorKind::*;
match &self.0 {
MissingColon => f.write_str("missing colon"),
TypeName(e) => write!(f, "{}", e),
AccountName(e) => write!(f, "{}", e),
UnknownAccountType(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for AccountError {}
impl ElementType<'static> for Account<'_> {
fn element_type(&self) -> &'static str {
"account"
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct Subaccount<'a>(&'a str);
impl<'a> AsRef<str> for Subaccount<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<Subaccount<'a>> for &'a str {
fn from(value: Subaccount<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ Subaccount<'a>> for &'a str {
fn from(value: &Subaccount<'a>) -> Self {
value.0
}
}
impl Display for Subaccount<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
impl<'a> TryFrom<&'a str> for Subaccount<'a> {
type Error = AccountNameError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
let mut empty = true;
for account_name in s.split(':') {
_ = AccountName::try_from(account_name)?;
empty = false;
}
if empty {
Err(AccountNameError(AccountNameErrorKind::Empty))
} else {
Ok(Subaccount(s))
}
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct AccountTypeName<'a>(&'a str);
impl AccountTypeName<'_> {
pub(crate) fn is_valid_initial(c: &char) -> bool {
c.is_ascii_uppercase()
}
pub(crate) fn is_valid_subsequent(c: &char) -> bool {
c.is_alphanumeric() || *c == '-'
}
}
impl<'a> TryFrom<&'a str> for AccountTypeName<'a> {
type Error = AccountTypeNameError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
use AccountTypeNameErrorKind::*;
if s.is_empty() {
Err(AccountTypeNameError(Empty))
} else {
let mut chars = s.chars();
let initial = chars.next().unwrap();
if !AccountTypeName::is_valid_initial(&initial) {
Err(AccountTypeNameError(Initial(initial)))
} else {
let bad_chars = chars
.filter(|c| !AccountTypeName::is_valid_subsequent(c))
.collect::<Vec<char>>();
if bad_chars.is_empty() {
Ok(AccountTypeName(s))
} else {
Err(AccountTypeNameError(Subsequent(bad_chars)))
}
}
}
}
}
impl ElementType<'static> for AccountTypeName<'_> {
fn element_type(&self) -> &'static str {
"account type name"
}
}
impl<'a> AsRef<str> for AccountTypeName<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<AccountTypeName<'a>> for &'a str {
fn from(value: AccountTypeName<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ AccountTypeName<'a>> for &'a str {
fn from(value: &AccountTypeName<'a>) -> Self {
value.0
}
}
impl PartialEq<&str> for AccountTypeName<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Display for AccountTypeName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct AccountTypeNameError(AccountTypeNameErrorKind);
#[derive(PartialEq, Eq, Debug)]
enum AccountTypeNameErrorKind {
Empty,
Initial(char),
Subsequent(Vec<char>),
}
impl Display for AccountTypeNameError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use AccountTypeNameErrorKind::*;
match &self.0 {
Empty => write!(f, "empty account name"),
Initial(bad_char) => write!(
f,
"invalid character '{}' for account name initial - must be uppercase ASCII letter",
bad_char
),
Subsequent(bad_chars) => {
format(
f,
bad_chars,
single_quoted,
", ",
Some("invalid character "),
)?;
f.write_str(" in account name - must be alphanumeric or '-'")
}
}
}
}
impl std::error::Error for AccountTypeNameError {}
#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Debug)]
pub struct AccountName<'a>(&'a str);
impl AccountName<'_> {
pub(crate) fn is_valid_initial(c: &char) -> bool {
c.is_ascii_uppercase() || c.is_ascii_digit() || (!c.is_ascii() && c.is_alphabetic())
}
pub(crate) fn is_valid_subsequent(c: &char) -> bool {
c.is_alphanumeric() || *c == '-'
}
}
impl ElementType<'static> for AccountName<'_> {
fn element_type(&self) -> &'static str {
"account name"
}
}
impl<'a> AsRef<str> for AccountName<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<AccountName<'a>> for &'a str {
fn from(value: AccountName<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ AccountName<'a>> for &'a str {
fn from(value: &AccountName<'a>) -> Self {
value.0
}
}
impl PartialEq<&str> for AccountName<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Display for AccountName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct AccountNameError(AccountNameErrorKind);
#[derive(PartialEq, Eq, Debug)]
enum AccountNameErrorKind {
Empty,
Initial(char),
Subsequent(Vec<char>),
}
impl Display for AccountNameError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use AccountNameErrorKind::*;
match &self.0 {
Empty => write!(f, "empty account name"),
Initial(bad_char) => write!(
f,
"invalid character '{}' for account name initial - must be uppercase ASCII letter or digit",
bad_char
),
Subsequent(bad_chars) => {
format(
f,
bad_chars,
single_quoted,
", ",
Some("invalid character "),
)?;
f.write_str(" in account name - must be alphanumeric or '-'")
}
}
}
}
impl std::error::Error for AccountNameError {}
impl<'a> TryFrom<&'a str> for AccountName<'a> {
type Error = AccountNameError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
use AccountNameErrorKind::*;
if s.is_empty() {
Err(AccountNameError(Empty))
} else {
let mut chars = s.chars();
let initial = chars.next().unwrap();
if !AccountName::is_valid_initial(&initial) {
Err(AccountNameError(Initial(initial)))
} else {
let bad_chars = chars
.filter(|c| !AccountName::is_valid_subsequent(c))
.collect::<Vec<char>>();
if bad_chars.is_empty() {
Ok(AccountName(s))
} else {
Err(AccountNameError(Subsequent(bad_chars)))
}
}
}
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct Currency<'a>(&'a str);
const CURRENCY_INTERMEDIATE_EXTRA_CHARS: [char; 4] = ['\'', '.', '_', '-'];
impl Currency<'_> {
fn is_valid_initial(c: &char) -> bool {
c.is_ascii_uppercase() || *c == '/'
}
fn is_valid_intermediate(c: &char) -> bool {
c.is_ascii_uppercase()
|| c.is_ascii_digit()
|| CURRENCY_INTERMEDIATE_EXTRA_CHARS.contains(c)
}
fn is_valid_final(c: &char) -> bool {
c.is_ascii_uppercase() || c.is_ascii_digit()
}
}
impl ElementType<'static> for Currency<'_> {
fn element_type(&self) -> &'static str {
"currency"
}
}
impl<'a> AsRef<str> for Currency<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<Currency<'a>> for &'a str {
fn from(value: Currency<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ Currency<'a>> for &'a str {
fn from(value: &Currency<'a>) -> Self {
value.0
}
}
impl PartialEq<&str> for Currency<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Display for Currency<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct CurrencyError(CurrencyErrorKind);
#[derive(PartialEq, Eq, Debug)]
enum CurrencyErrorKind {
Empty,
Initial(char),
Intermediate(Vec<char>),
Final(char),
MissingLetter,
}
impl Display for CurrencyError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use CurrencyErrorKind::*;
match &self.0 {
Empty => write!(f, "empty currency"),
Initial(bad_char) => write!(
f,
"invalid initial character '{}' for currency - must be uppercase ASCII letter or '/'",
bad_char
),
Intermediate(bad_chars) => {
format(
f,
bad_chars,
single_quoted,
", ",
Some("invalid intermediate characters "),
)?;
format(
f,
CURRENCY_INTERMEDIATE_EXTRA_CHARS,
single_quoted,
", ",
Some(" for currency - must be upppercase ASCII alphanumeric or one of "),
)
}
Final(bad_char) => write!(
f,
"invalid final character '{}' for currency - must be uppercase ASCII alphanumeric",
bad_char
),
MissingLetter => write!(f, "currency must contain at least one letter"),
}
}
}
impl std::error::Error for CurrencyError {}
impl<'a> TryFrom<&'a str> for Currency<'a> {
type Error = CurrencyError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
use CurrencyErrorKind::*;
if s.is_empty() {
Err(CurrencyError(Empty))
} else {
let mut chars = s.chars();
let first = chars.next().unwrap();
let intermediate: Vec<char> = if s.len() > 2 {
chars.by_ref().take(s.len() - 2).collect()
} else {
empty::<char>().collect()
};
let last = if s.len() > 1 {
chars.next().unwrap()
} else {
first
};
use CurrencyErrorKind::*;
if !Currency::is_valid_initial(&first) {
Err(CurrencyError(Initial(first)))
} else if !Currency::is_valid_final(&last) {
Err(CurrencyError(Final(last)))
} else {
let bad_intermediates = intermediate
.into_iter()
.filter(|c| !Currency::is_valid_intermediate(c))
.collect::<Vec<char>>();
if !bad_intermediates.is_empty() {
Err(CurrencyError(Intermediate(bad_intermediates)))
} else if s.find(|c: char| c.is_ascii_uppercase()).is_none() {
Err(CurrencyError(MissingLetter))
} else {
Ok(Currency(s))
}
}
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Posting<'a> {
pub(crate) flag: Option<Spanned<Flag>>,
pub(crate) account: Spanned<Account<'a>>,
pub(crate) amount: Option<Spanned<ExprValue>>,
pub(crate) currency: Option<Spanned<Currency<'a>>>,
pub(crate) cost_spec: Option<Spanned<CostSpec<'a>>>,
pub(crate) price_annotation: Option<Spanned<PriceSpec<'a>>>,
pub(crate) metadata: Spanned<Metadata<'a>>,
}
impl<'a> Posting<'a> {
pub fn flag(&self) -> Option<&Spanned<Flag>> {
self.flag.as_ref()
}
pub fn account(&self) -> &Spanned<Account<'a>> {
&self.account
}
pub fn amount(&self) -> Option<&Spanned<ExprValue>> {
self.amount.as_ref()
}
pub fn currency(&self) -> Option<&Spanned<Currency<'a>>> {
self.currency.as_ref()
}
pub fn cost_spec(&self) -> Option<&Spanned<CostSpec<'a>>> {
self.cost_spec.as_ref()
}
pub fn price_annotation(&self) -> Option<&Spanned<PriceSpec<'a>>> {
self.price_annotation.as_ref()
}
pub fn metadata(&self) -> &Metadata<'a> {
&self.metadata
}
}
impl ElementType<'static> for Posting<'_> {
fn element_type(&self) -> &'static str {
"posting"
}
}
impl Display for Posting<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
simple_format(f, self.flag, None)?;
write!(
f,
"{}{}",
if self.flag.is_some() { " " } else { "" },
&self.account
)?;
simple_format(f, &self.amount, Some(" "))?;
simple_format(f, self.currency, Some(" "))?;
simple_format(f, &self.cost_spec, Some(" "))?;
simple_format(f, &self.price_annotation, Some(" @ "))?;
self.metadata.fmt(f)
}
}
#[derive(PartialEq, Eq, Clone, Default, Debug)]
pub struct Metadata<'a> {
pub(crate) key_values: HashMap<Spanned<Key<'a>>, Spanned<MetaValue<'a>>>,
pub(crate) tags: HashSet<Spanned<Tag<'a>>>,
pub(crate) links: HashSet<Spanned<Link<'a>>>,
}
impl<'a> Metadata<'a> {
pub fn is_empty(&self) -> bool {
self.key_values().len() == 0 && self.tags().len() == 0 && self.links().len() == 0
}
pub fn key_values(
&self,
) -> impl ExactSizeIterator<Item = (&Spanned<Key<'a>>, &Spanned<MetaValue<'a>>)> {
self.key_values.iter()
}
pub fn key_value<'s, 'k>(&'s self, key: Key<'k>) -> Option<&'s Spanned<MetaValue<'s>>>
where
'k: 's,
{
match self.key_values.keys().next() {
None => None,
Some(arbitrary_spanned_key) => {
let key = spanned(key, *arbitrary_spanned_key.span());
self.key_values.get(&key)
}
}
}
pub fn tags(&self) -> impl ExactSizeIterator<Item = &Spanned<Tag<'a>>> {
self.tags.iter()
}
pub fn links(&self) -> impl ExactSizeIterator<Item = &Spanned<Link<'a>>> {
self.links.iter()
}
pub(crate) fn fmt_tags_links_inline(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(f, &self.tags, plain, SPACE, Some(SPACE))?;
format(f, &self.links, plain, SPACE, Some(SPACE))
}
pub(crate) fn fmt_tags_links_on_separate_lines(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(f, &self.tags, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))?;
format(f, &self.links, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))
}
pub(crate) fn fmt_keys_values(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(
f,
&self.key_values,
key_value,
NEWLINE_INDENT,
Some(NEWLINE_INDENT),
)
}
}
impl Display for Metadata<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(f, &self.tags, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))?;
format(f, &self.links, plain, NEWLINE_INDENT, Some(NEWLINE_INDENT))?;
self.fmt_keys_values(f)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub(crate) struct MetaKeyValue<'a> {
pub(crate) key: Spanned<Key<'a>>,
pub(crate) value: Spanned<MetaValue<'a>>,
}
impl Display for MetaKeyValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}: \"{}\"", &self.key, &self.value)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum MetaValue<'a> {
Simple(SimpleValue<'a>),
Amount(Amount<'a>),
}
impl Display for MetaValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use MetaValue::*;
match self {
Simple(simple_value) => simple_value.fmt(f),
Amount(amount) => amount.fmt(f),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum SimpleValue<'a> {
String(&'a str),
Currency(Currency<'a>),
Account(Account<'a>),
Tag(Tag<'a>),
Link(Link<'a>),
Date(Date),
Bool(bool),
Null,
Expr(ExprValue),
}
impl ElementType<'static> for SimpleValue<'_> {
fn element_type(&self) -> &'static str {
"simple value"
}
}
impl Display for SimpleValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use SimpleValue::*;
match self {
String(x) => write!(f, r#""{}""#, x),
Currency(x) => x.fmt(f),
Account(x) => x.fmt(f),
Tag(x) => x.fmt(f),
Link(x) => x.fmt(f),
Date(x) => x.fmt(f),
Bool(x) => f.write_str(if *x { "TRUE" } else { "FALSE" }),
Null => f.write_str("NULL"),
Expr(x) => x.fmt(f),
}
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct Tag<'a>(pub(crate) &'a str);
impl<'a> TryFrom<&'a str> for Tag<'a> {
type Error = TagOrLinkIdentifierError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
validate_tag_or_link_identifier(s)?;
Ok(Tag(s))
}
}
impl ElementType<'static> for Tag<'_> {
fn element_type(&self) -> &'static str {
"tag"
}
}
impl<'a> AsRef<str> for Tag<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<Tag<'a>> for &'a str {
fn from(value: Tag<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ Tag<'a>> for &'a str {
fn from(value: &Tag<'a>) -> Self {
value.0
}
}
impl PartialEq<&str> for Tag<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Display for Tag<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct TagOrLinkIdentifier<'a>(&'a str);
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct Link<'a>(pub(crate) &'a str);
impl<'a> TryFrom<&'a str> for Link<'a> {
type Error = TagOrLinkIdentifierError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
validate_tag_or_link_identifier(s)?;
Ok(Link(s))
}
}
impl ElementType<'static> for Link<'_> {
fn element_type(&self) -> &'static str {
"link"
}
}
impl PartialEq<&str> for Link<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Display for Link<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "^{}", self.0)
}
}
impl<'a> AsRef<str> for Link<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<Link<'a>> for &'a str {
fn from(value: Link<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ Link<'a>> for &'a str {
fn from(value: &Link<'a>) -> Self {
value.0
}
}
const TAG_OR_LINK_EXTRA_CHARS: [char; 4] = ['-', '_', '/', '.'];
fn is_valid_tag_or_link_identifier_char(c: &char) -> bool {
c.is_alphanumeric() || TAG_OR_LINK_EXTRA_CHARS.contains(c)
}
#[derive(PartialEq, Eq, Debug)]
pub struct TagOrLinkIdentifierError(Vec<char>);
impl Display for TagOrLinkIdentifierError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(f, &self.0, single_quoted, ", ", Some("invalid characters "))?;
format(
f,
TAG_OR_LINK_EXTRA_CHARS,
single_quoted,
", ",
Some(" for tag or link identifier - must be alphanumeric or one of "),
)
}
}
impl std::error::Error for TagOrLinkIdentifierError {}
fn validate_tag_or_link_identifier(s: &str) -> Result<(), TagOrLinkIdentifierError> {
let bad_chars = s
.chars()
.filter(|c| !is_valid_tag_or_link_identifier_char(c))
.collect::<Vec<char>>();
if bad_chars.is_empty() {
Ok(())
} else {
Err(TagOrLinkIdentifierError(bad_chars))
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct Key<'a>(&'a str);
impl Key<'_> {
pub(crate) fn is_valid_initial(c: &char) -> bool {
c.is_ascii_lowercase()
}
pub(crate) fn is_valid_subsequent(c: &char) -> bool {
c.is_alphanumeric() || *c == '-' || *c == '_'
}
}
impl<'a> AsRef<str> for Key<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl<'a> From<Key<'a>> for &'a str {
fn from(value: Key<'a>) -> Self {
value.0
}
}
impl<'a> From<&'_ Key<'a>> for &'a str {
fn from(value: &Key<'a>) -> Self {
value.0
}
}
impl ElementType<'static> for Key<'_> {
fn element_type(&self) -> &'static str {
"key"
}
}
impl PartialEq<&str> for Key<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl Display for Key<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct KeyError(KeyErrorKind);
#[derive(PartialEq, Eq, Debug)]
enum KeyErrorKind {
Empty,
Initial(char),
Subsequent(Vec<char>),
}
impl Display for KeyError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use KeyErrorKind::*;
match &self.0 {
Empty => write!(f, "empty key"),
Initial(bad_char) => write!(
f,
"invalid character '{}' for key initial - must be lowercase ASCII letter",
bad_char
),
Subsequent(bad_chars) => {
format(
f,
bad_chars,
single_quoted,
", ",
Some("invalid characters "),
)?;
f.write_str(" for key - must be alphanumeric or '-' or '_'")
}
}
}
}
impl std::error::Error for KeyError {}
impl<'a> TryFrom<&'a str> for Key<'a> {
type Error = KeyError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
use KeyErrorKind::*;
if s.is_empty() {
Err(KeyError(Empty))
} else {
let mut chars = s.chars();
let initial = chars.next().unwrap();
if !Key::is_valid_initial(&initial) {
Err(KeyError(Initial(initial)))
} else {
let bad_chars = chars
.filter(|c| !Key::is_valid_subsequent(c))
.collect::<Vec<char>>();
if bad_chars.is_empty() {
Ok(Key(s))
} else {
Err(KeyError(Subsequent(bad_chars)))
}
}
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ExprValue {
value: Decimal,
expr: Expr,
}
impl ExprValue {
pub fn value(&self) -> Decimal {
self.value
}
pub fn expr(&self) -> &Expr {
&self.expr
}
}
impl PartialEq<Decimal> for ExprValue {
fn eq(&self, other: &Decimal) -> bool {
&self.value == other
}
}
impl From<Expr> for ExprValue {
fn from(expr: Expr) -> Self {
let (mut value, scale) = expr.evaluate();
value.rescale(scale);
Self { value, expr }
}
}
impl ElementType<'static> for ExprValue {
fn element_type(&self) -> &'static str {
"amount" }
}
impl Display for ExprValue {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.expr, format)
}
}
#[derive(PartialEq, Eq, Clone)]
pub enum Expr {
Value(Decimal),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
Neg(Box<Expr>),
Paren(Box<Expr>),
}
impl Expr {
fn evaluate(&self) -> (Decimal, u32) {
fn evaluate_unary<F>(op: F, e1: &Expr) -> (Decimal, u32)
where
F: Fn(Decimal) -> Decimal,
{
let (d1, s1) = e1.evaluate();
(op(d1), s1)
}
fn evaluate_binary<F>(op: F, e1: &Expr, e2: &Expr) -> (Decimal, u32)
where
F: Fn(Decimal, Decimal) -> Decimal,
{
let (d1, s1) = e1.evaluate();
let (d2, s2) = e2.evaluate();
(op(d1, d2), max(s1, s2))
}
use Expr::*;
match self {
Value(d) => (*d, d.scale()),
Add(e1, e2) => evaluate_binary(std::ops::Add::add, e1, e2),
Sub(e1, e2) => evaluate_binary(std::ops::Sub::sub, e1, e2),
Mul(e1, e2) => evaluate_binary(std::ops::Mul::mul, e1, e2),
Div(e1, e2) => evaluate_binary(std::ops::Div::div, e1, e2),
Neg(e1) => evaluate_unary(std::ops::Neg::neg, e1),
Paren(e) => e.evaluate(),
}
}
}
impl Display for Expr {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use self::Expr::*;
match *self {
Value(val) => write!(format, "{}", val),
Add(ref left, ref right) => write!(format, "{} + {}", left, right),
Sub(ref left, ref right) => write!(format, "{} - {}", left, right),
Mul(ref left, ref right) => write!(format, "{} * {}", left, right),
Div(ref left, ref right) => write!(format, "{} / {}", left, right),
Neg(ref expr) => write!(format, "-{}", expr),
Paren(ref expr) => write!(format, "({})", expr),
}
}
}
impl fmt::Debug for Expr {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use self::Expr::*;
match *self {
Value(val) => write!(format, "{}", val),
Add(ref left, ref right) => write!(format, "({:?} + {:?})", left, right),
Sub(ref left, ref right) => write!(format, "({:?} - {:?})", left, right),
Mul(ref left, ref right) => write!(format, "({:?} * {:?})", left, right),
Div(ref left, ref right) => write!(format, "({:?} / {:?})", left, right),
Neg(ref expr) => write!(format, "(-{:?})", expr),
Paren(ref expr) => write!(format, "[{:?}]", expr),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum CompoundExprValue {
PerUnit(ExprValue),
Total(ExprValue),
PerUnitAndTotal(ExprValue, ExprValue),
}
impl Display for CompoundExprValue {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use self::CompoundExprValue::*;
match self {
PerUnit(e) => write!(format, "{}", e),
Total(e) => write!(format, "# {}", e),
PerUnitAndTotal(per_unit, total) => write!(format, "{} # {}", per_unit, total),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ScopedExprValue {
PerUnit(ExprValue),
Total(ExprValue),
}
impl Display for ScopedExprValue {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use self::ScopedExprValue::*;
match self {
PerUnit(e) => write!(format, "{}", e),
Total(e) => write!(format, "# {}", e),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Amount<'a> {
number: Spanned<ExprValue>,
currency: Spanned<Currency<'a>>,
}
impl<'a> Amount<'a> {
pub(crate) fn new(amount: (Spanned<ExprValue>, Spanned<Currency<'a>>)) -> Self {
Amount {
number: amount.0,
currency: amount.1,
}
}
pub fn number(&self) -> &Spanned<ExprValue> {
&self.number
}
pub fn currency(&self) -> &Spanned<Currency<'a>> {
&self.currency
}
}
impl Display for Amount<'_> {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
write!(format, "{} {}", &self.number, &self.currency)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct AmountWithTolerance<'a> {
amount: Spanned<Amount<'a>>,
tolerance: Option<Spanned<Decimal>>,
}
impl<'a> AmountWithTolerance<'a> {
pub(crate) fn new(awt: (Spanned<Amount<'a>>, Option<Spanned<Decimal>>)) -> Self {
AmountWithTolerance {
amount: awt.0,
tolerance: awt.1,
}
}
pub fn amount(&self) -> &Spanned<Amount<'a>> {
&self.amount
}
pub fn tolerance(&self) -> Option<&Spanned<Decimal>> {
self.tolerance.as_ref()
}
}
impl Display for AmountWithTolerance<'_> {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
if let Some(tolerance) = self.tolerance {
write!(format, "{} ~ {}", &self.amount, tolerance)
} else {
write!(format, "{}", &self.amount)
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct LooseAmount<'a> {
number: Option<Spanned<ExprValue>>,
currency: Option<Spanned<Currency<'a>>>,
}
impl<'a> LooseAmount<'a> {
pub(crate) fn new(amount: (Option<Spanned<ExprValue>>, Option<Spanned<Currency<'a>>>)) -> Self {
LooseAmount {
number: amount.0,
currency: amount.1,
}
}
pub fn number(&self) -> Option<&Spanned<ExprValue>> {
self.number.as_ref()
}
pub fn currency(&self) -> Option<&Spanned<Currency<'a>>> {
self.currency.as_ref()
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum CompoundAmount<'a> {
BareCurrency(Currency<'a>),
BareAmount(CompoundExprValue),
CurrencyAmount(CompoundExprValue, Currency<'a>),
}
impl Display for CompoundAmount<'_> {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use self::CompoundAmount::*;
match self {
BareCurrency(cur) => write!(format, "{}", cur),
BareAmount(ce) => write!(format, "{}", ce),
CurrencyAmount(ce, cur) => write!(format, "{} {}", ce, cur),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CostSpec<'a> {
per_unit: Option<Spanned<ExprValue>>,
total: Option<Spanned<ExprValue>>,
currency: Option<Spanned<Currency<'a>>>,
date: Option<Spanned<Date>>,
label: Option<Spanned<&'a str>>,
merge: bool,
}
impl<'a> CostSpec<'a> {
pub fn per_unit(&self) -> Option<&Spanned<ExprValue>> {
self.per_unit.as_ref()
}
pub fn total(&self) -> Option<&Spanned<ExprValue>> {
self.total.as_ref()
}
pub fn currency(&self) -> Option<&Spanned<Currency<'a>>> {
self.currency.as_ref()
}
pub fn date(&self) -> Option<&Spanned<Date>> {
self.date.as_ref()
}
pub fn label(&self) -> Option<&Spanned<&'a str>> {
self.label.as_ref()
}
pub fn merge(&self) -> bool {
self.merge
}
}
impl ElementType<'static> for CostSpec<'_> {
fn element_type(&self) -> &'static str {
"cost specification"
}
}
impl Display for CostSpec<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut sep = "";
let space = " ";
let comma_space = ", ";
f.write_str("{")?;
if let Some(per_unit) = &self.per_unit {
write!(f, "{}{}", sep, per_unit)?;
sep = space;
}
if let Some(total) = &self.total {
write!(f, "{}# {}", sep, total)?;
sep = space;
}
if let Some(currency) = &self.currency {
write!(f, "{}{}", sep, currency)?;
sep = space;
}
if sep == space {
sep = comma_space
}
if let Some(date) = &self.date {
write!(f, "{}{}", sep, date)?;
sep = comma_space;
}
if let Some(label) = &self.label {
write!(f, "{}\"{}\"", sep, label)?;
sep = comma_space;
}
if self.merge {
write!(f, "{}*", sep)?;
}
f.write_str("}")
}
}
#[derive(Default, Debug)]
pub(crate) struct CostSpecBuilder<'a> {
per_unit: Option<Spanned<ExprValue>>,
total: Option<Spanned<ExprValue>>,
currency: Option<Spanned<Currency<'a>>>,
date: Option<Spanned<Date>>,
label: Option<Spanned<&'a str>>,
merge: bool,
errors: Vec<CostSpecError>,
}
impl<'a> CostSpecBuilder<'a> {
pub(crate) fn compound_expr(self, value: CompoundExprValue, span: Span) -> Self {
use CompoundExprValue::*;
match value {
PerUnit(value) => self.per_unit(spanned(value, span)),
Total(value) => self.total(spanned(value, span)),
PerUnitAndTotal(per_unit, total) => self
.per_unit(spanned(per_unit, span))
.total(spanned(total, span)),
}
}
fn per_unit(mut self, value: Spanned<ExprValue>) -> Self {
if self.per_unit.is_none() {
self.per_unit = Some(value);
} else {
self.errors.push(CostSpecError(CostSpecErrorKind::PerUnit))
}
self
}
fn total(mut self, value: Spanned<ExprValue>) -> Self {
if self.total.is_none() {
self.total = Some(value);
} else {
self.errors.push(CostSpecError(CostSpecErrorKind::Total))
}
self
}
pub(crate) fn currency(mut self, value: Currency<'a>, span: Span) -> Self {
if self.currency.is_none() {
self.currency = Some(spanned(value, span));
} else {
self.errors.push(CostSpecError(CostSpecErrorKind::Currency))
}
self
}
pub(crate) fn date(mut self, value: Date, span: Span) -> Self {
if self.date.is_none() {
self.date = Some(spanned(value, span));
} else {
self.errors.push(CostSpecError(CostSpecErrorKind::Date))
}
self
}
pub(crate) fn label(mut self, value: &'a str, span: Span) -> Self {
if self.label.is_none() {
self.label = Some(spanned(value, span));
} else {
self.errors.push(CostSpecError(CostSpecErrorKind::Label))
}
self
}
pub(crate) fn merge(mut self, _span: Span) -> Self {
if !self.merge {
self.merge = true;
} else {
self.errors
.push(CostSpecError(CostSpecErrorKind::MergeCost))
}
self
}
pub(crate) fn build<'b>(&'b mut self) -> Result<CostSpec<'a>, CostSpecErrors>
where
'a: 'b,
{
let per_unit = self.per_unit.take();
let total = self.total.take();
let currency = self.currency.take();
let date = self.date.take();
let label = self.label.take();
let merge = self.merge;
self.merge = false;
if !self.errors.is_empty() {
let mut errors = Vec::new();
swap(&mut self.errors, &mut errors);
Err(CostSpecErrors(errors))
} else {
Ok(CostSpec {
per_unit,
total,
currency,
date,
label,
merge,
})
}
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct CostSpecError(CostSpecErrorKind);
#[derive(PartialEq, Eq, Display, Debug)]
#[strum(serialize_all = "kebab-case")]
enum CostSpecErrorKind {
PerUnit,
Total,
Currency,
Date,
Label,
MergeCost,
}
impl CostSpecErrorKind {
fn unless(self, condition: bool) -> Result<(), CostSpecError> {
if condition {
Ok(())
} else {
Err(CostSpecError(self))
}
}
}
impl Display for CostSpecError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "duplicate {} field in cost specification", self.0)
}
}
impl std::error::Error for CostSpecError {}
#[derive(PartialEq, Eq, Debug)]
pub struct CostSpecErrors(Vec<CostSpecError>);
impl Display for CostSpecErrors {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(f, &self.0, plain, ", ", None)
}
}
impl std::error::Error for CostSpecErrors {}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum PriceSpec<'a> {
Unspecified,
BareCurrency(Currency<'a>),
BareAmount(ScopedExprValue),
CurrencyAmount(ScopedExprValue, Currency<'a>),
}
impl Display for PriceSpec<'_> {
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
use self::PriceSpec::*;
match self {
Unspecified => Ok(()),
BareCurrency(cur) => write!(format, "{}", cur),
BareAmount(ce) => write!(format, "{}", ce),
CurrencyAmount(ce, cur) => write!(format, "{} {}", ce, cur),
}
}
}
#[derive(Debug)]
pub(crate) struct AccountTypeNames<'a> {
pub(crate) name_by_type: Vec<AccountTypeName<'a>>,
pub(crate) type_by_name: HashMap<AccountTypeName<'a>, AccountType>,
}
impl<'a> AccountTypeNames<'a> {
pub(crate) fn name(&self, account_type: AccountType) -> AccountTypeName<'a> {
self.name_by_type[account_type as usize]
}
pub(crate) fn get(&self, name: &AccountTypeName) -> Option<AccountType> {
self.type_by_name.get(name).copied()
}
pub(crate) fn update(
&mut self,
account_type: AccountType,
name: AccountTypeName<'a>,
) -> Result<(), AccountTypeNamesError> {
use hash_map::Entry::*;
match self.type_by_name.entry(name) {
Vacant(e) => {
e.insert(account_type);
let old_name = self.name_by_type[account_type as usize];
self.name_by_type[account_type as usize] = name;
self.type_by_name.remove(&old_name);
Ok(())
}
Occupied(o) => {
let existing_account_type = *o.get();
if existing_account_type == account_type {
Ok(())
} else {
Err(AccountTypeNamesError(AccountTypeNamesErrorKind::NameInUse(
existing_account_type,
)))
}
}
}
}
}
impl<'a> Default for AccountTypeNames<'a> {
fn default() -> Self {
use AccountType::*;
let names_types = vec![
("Assets", Assets),
("Liabilities", Liabilities),
("Equity", Equity),
("Income", Income),
("Expenses", Expenses),
];
let mut names_type_indices = names_types
.iter()
.map(|(n, t)| (*n, *t as usize))
.collect::<Vec<_>>();
names_type_indices.sort_by_key(|(_n, t)| *t);
let name_by_type = names_type_indices
.into_iter()
.map(|(n, _t)| AccountTypeName::try_from(n).unwrap())
.collect::<Vec<_>>();
let type_by_name: HashMap<AccountTypeName<'a>, AccountType> = HashMap::from_iter(
names_types
.into_iter()
.map(|(n, t)| (AccountTypeName::try_from(n).unwrap(), t)),
);
AccountTypeNames {
name_by_type,
type_by_name,
}
}
}
impl Display for AccountTypeNames<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
format(f, &self.name_by_type, plain, ", ", None)
}
}
#[derive(PartialEq, Eq, Debug)]
pub(crate) struct AccountTypeNamesError(AccountTypeNamesErrorKind);
#[derive(PartialEq, Eq, Debug)]
enum AccountTypeNamesErrorKind {
NameInUse(AccountType),
}
impl Display for AccountTypeNamesError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use AccountTypeNamesErrorKind::*;
match &self.0 {
NameInUse(t) => write!(f, "account type name in use for {}", t.as_ref()),
}
}
}
impl std::error::Error for AccountTypeNamesError {}
mod tests;