avaro 0.0.2-alpha

a plain text double-accounting tool which is compatible with avaro but more powerful
Documentation

use crate::models::*;
use chrono::{NaiveDate};
use crate::error::AvaroError;
use lalrpop_util::ParseError;
use snailquote::unescape;
use std::str::FromStr;
use bigdecimal::BigDecimal;
grammar;

extern {
    type Location = usize;
    type Error = AvaroError;
}

match {
    // Ignore C++-style comments
    r"///[^\n\r]*[\n\r]*" => { },

    r"\d{4}-\d{1,2}-\d{1,2}",

    r"[A-Z][A-Z0-9_\-'\.]{0,22}[A-Z0-9]"

} else {
    // Other things you want to match
}
else {
    _ // Everything else
}

Comma<T>: Vec<T> = { // (1)
    <v:(<T> "," SPACE*)*> <e:T> => { // (2)
        let mut v = v;
        v.push(e);
        v
    }
};


pub Entry: Vec<Directive> = {
    <v: ("\n"* <(<DirectiveExpression> "\n"+)*>)> => v
}


pub DirectiveExpression: Directive = {
    OptionExpression,
    OpenExpression,
    NoteExpression,
    CloseExpression,
    CommodityExpression,
    <TransactionExpression> => Directive::Transaction(<>),
    PadExpression,
    BalanceExpression,
    DocumentExpression,
    PriceExpression,
    EventExpression,
    PluginExpression,
    IncludeExpression,
    CustomExpression,
    CommentExpression,
}

pub OptionExpression:  Directive = {
    "option" SPACE+ <key: StringExpression> SPACE+ <value: StringExpression> => Directive::Option{key, value}
}

pub PluginExpression:    Directive = {
    "plugin" SPACE+ <module: StringExpression> <value: (SPACE+ <StringExpression>)?> => Directive::Plugin{module, value}
}

pub IncludeExpression: Directive = {
    "include" SPACE+ <file: StringExpression> => Directive::Include{file}
}

pub OpenExpression: Directive = {
    <date: DateExpression> SPACE+ "open" SPACE+ <account: AccountExpression> <commodities: (SPACE+ <Comma<Commodity>>)?> SPACE*  => Directive::Open{date, account, commodities}
}

pub NoteExpression: Directive = {
    <date: DateExpression> SPACE+ "note" SPACE+ <account: AccountExpression> SPACE+ <description: StringExpression> SPACE* => Directive::Note{date, account, description}
}

pub CloseExpression: Directive = {
    <date: DateExpression> SPACE+ "close" SPACE+ <account: AccountExpression> SPACE*  => Directive::Close{date, account}
}

pub CommodityExpression: Directive = {
    <date: DateExpression> SPACE+ "commodity" SPACE+ <name: Commodity> SPACE*
      <v:("\n  " <CommodityLine>)*>

      => Directive::Commodity{date, name,  metas:v.into_iter().collect()}
}

    CommodityLine: (String, String) = {
         SPACE* <AttributeKey> SPACE* ":" SPACE* <StringExpression>
    }


pub TransactionExpression: Transaction = {
    <date: DateExpression> SPACE+ <flag: FlagExpression> SPACE+ <options: TransactionHeadOptions>
    <v:("\n  " <TransactionLineExpression> )*>

    => {
        let lines = v.into_iter().map(|line|TransactionLine::from_parser(line.0, line.1, line.2)).collect();
        Transaction::from_parser(date, flag, options.0, options.1, options.2, lines)
    }
}
    TransactionHeadOptions: (Option<(String, Option<String>)>, Vec<String>, Vec<String>) = { <PnExpression?> <TagsExpression> <LinksExpression> }
    PnExpression: (String, Option<String>) = {
        <first: StringExpression> SPACE*  <second: NarrationExpression?> => (first, second)
    }
    NarrationExpression: String = { ("|" SPACE+)? <StringExpression> SPACE* }
    TagsExpression: Vec<String> = { <TagExpression*> }
    TagExpression: String = { "#" <AttributeKey> SPACE* }
    LinksExpression: Vec<String> = { <LinkExpression*> }
    LinkExpression: String = { "^" <AttributeKey> SPACE* }

    TransactionLineExpression: (Option<Flag>, Account, Option<(Amount, Option<(Amount, Option<String>)>, Option<Amount>, Option<Amount>)>) = {
        SPACE* <(<FlagExpression> SPACE*)?> <AccountExpression> <TLOptions>
    }
    TLOptions:Option<(Amount, Option<(Amount, Option<String>)>, Option<Amount>, Option<Amount>)> = {
        (
            SPACE+ <Amount> SPACE*
            <CostWithComment?>
            <SinglePrice?>
            <TotalPrice?>
        )?
    }

    CostWithComment: (Amount, Option<String>) = { "{" <Amount> SPACE* <Cost?> "}" SPACE* }
    Cost: String = { "," SPACE* <StringExpression> SPACE* }
    SinglePrice: Amount = { "@" SPACE* <Amount> SPACE* }
    TotalPrice: Amount = { "@@" SPACE* <Amount> }

pub PadExpression: Directive = {
    <date: DateExpression> SPACE+ "pad" SPACE+ <from: AccountExpression> SPACE+ <to: AccountExpression> SPACE* => Directive::Pad{date, from, to}
}

pub BalanceExpression: Directive = {
     <date: DateExpression> SPACE+ "balance" SPACE+ <account: AccountExpression> SPACE+ <amount: Amount> SPACE* => Directive::Balance{date, account, amount}
}


pub DocumentExpression: Directive = {
    <date: DateExpression> SPACE+ "document" SPACE+ <account: AccountExpression> SPACE+ <path: StringExpression> SPACE* => Directive::Document{date, account, path}
}


pub PriceExpression: Directive = {
    <date: DateExpression> SPACE+ "price" SPACE+ <commodity: Commodity> SPACE+ <amount: Amount> SPACE* => Directive::Price{date, commodity, amount}
}

pub EventExpression: Directive = {
    <date: DateExpression> SPACE+ "event" SPACE+ <name: StringExpression> SPACE+ <value: StringExpression> SPACE* => Directive::Event{date, name, value}
}

pub CustomExpression: Directive = {
    <date: DateExpression> SPACE+ "custom" SPACE+ <type_name: StringExpression> <values: (SPACE+ <CustomConfigItem>)+> => Directive::Custom{date, type_name, values}
}

pub CustomConfigItem: String = {
    StringExpression,
    AccountExpression => <>.to_string(),
    Commodity,
    AttributeKey,
}


pub CommentExpression: Directive = {
    r";[^\n]*" => Directive::Comment{content:<>.to_owned()}
}


pub FlagExpression: Flag = {
    "!" => Flag::from_str(<>).unwrap(),
    "*" => Flag::from_str(<>).unwrap()
}

pub Amount: (BigDecimal, String) = {
    r"[+-]?\d+(\.\d+)? +[A-Z][A-Z0-9_\-'\.]{0,22}[A-Z0-9]"    => amount_parse(<>)
}


pub AttributeKey: String = {
 <r"([^\x00-\x9F]|[A-Za-z0-9-_])+"> => <>.to_owned()
}


pub StringExpression: String = {
    <r#""(\\(["\\/bfnrt]|u[a-fA-F0-9]{4})|[^"\\\x00-\x1F\x7F]+)*""#> => unescape(<>).unwrap()
}

pub AccountExpression: Account = {
    <account_type: AccountType> <v:AccountName+>  => Account::new(AccountType::from_str(&account_type).unwrap(), v)
};

AccountType: String = {
    <"Assets"> => <>.to_owned(),
    <"Liabilities"> => <>.to_owned(),
    <"Equity"> => <>.to_owned(),
    <"Income"> => <>.to_owned(),
    <"Expenses"> => <>.to_owned()
}


pub AccountName: String = {
    <r":([^\x00-\x9F]|[A-Za-z0-9-])+"> => <>[1..<>.len()].to_owned()
}



pub Commodity: String = {
    <r"[A-Z][A-Z0-9_\-'\.]{0,22}[A-Z0-9]"> => <>.to_owned()
}


pub DateExpression: NaiveDate = {
    <date:r"\d{4}-\d{1,2}-\d{1,2}"> =>? NaiveDate::parse_from_str(date, "%Y-%m-%d").map_err(|_|ParseError::User {error: AvaroError::InvalidDate})
}

pub SPACE = {
    " ",
    r"\t"
}