57 files changed,
503 insertions(+),
172 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-06-22 18:00:13 +0300
Authored at:
2026-06-11 10:32:20 +0300
Change ID:
pwnuqswmzznnptmoknzuormlqxqqwxmp
Parent:
15cdd16
jump to
M
journal/ast/ast.go
··· 1 1 package ast 2 2 3 -import "olexsmir.xyz/clerk/journal/token" 3 +import ( 4 + "strings" 5 + 6 + "olexsmir.xyz/clerk/journal/token" 7 +) 4 8 5 9 type Journal struct { 6 10 Entries []Entry ··· 69 73 ) 70 74 71 75 type Status struct { 72 - // Value byte // '!' '*' 73 76 Value StatusType 74 77 Span token.Span 75 78 } 76 79 77 80 type Payee struct { 81 + Name string 82 + Span token.Span 83 +} 84 + 85 +type SubAccount struct { 78 86 Name string 79 87 Span token.Span 80 88 } 81 89 82 90 type Account struct { 83 - Name string // 'expenses:food' 91 + Name []SubAccount // ['expenses' 'food'] 84 92 Span token.Span 85 93 } 94 + 95 +func (a Account) String() string { 96 + if len(a.Name) == 0 { 97 + return "" 98 + } 99 + var name strings.Builder 100 + name.Grow(len(a.Name)) 101 + name.WriteString(a.Name[0].Name) 102 + for _, s := range a.Name[1:] { 103 + name.WriteByte(':') 104 + name.WriteString(s.Name) 105 + } 106 + return name.String() 107 +}
M
journal/ast/dump.go
··· 296 296 297 297 func dumpAccount(b *strings.Builder, a Account, depth int) { 298 298 indent(b, depth) 299 - fmt.Fprintf(b, "Account %q %s\n", a.Name, a.Span) 299 + fmt.Fprintf(b, "Account %s\n", a.Span) 300 + for _, sub := range a.Name { 301 + indent(b, depth+1) 302 + fmt.Fprintf(b, "SubAccount: %q %s\n", sub.Name, sub.Span) 303 + } 300 304 } 301 305 302 306 func dumpAccountDirective(b *strings.Builder, a *AccountDirective, depth int) {
M
journal/journal_test.go
··· 20 20 "actual-business.journal": {desc: "hledger: simple business transactions with commodities"}, 21 21 "actual-goal-budget.journal": {desc: "hledger: goal budget using periodic transactions"}, 22 22 "actual-i18n-en.journal": {desc: "hledger: internationalization with account types in English"}, 23 - "actual-ledger-baseline-opt-lots-basis.dat": {desc: "ledger: G/S prefixes"}, 23 + "actual-ledger-baseline-opt-lots-basis.dat": {err: true, desc: "ledger: G/S prefixes"}, // no lot support yet 24 24 "actual-ledger-input-divzero.dat": {desc: "ledger: fuzz corpus, designed to cause divide-by-zero"}, 25 25 "actual-ledger-input-parsing.dat": {desc: "ledger: fuzz corpus, tests EOF without newline"}, 26 26 "actual-ledger-input-sample.dat": {desc: "ledger: fuzz corpus, default commodity directive"}, 27 27 "actual-ledger-input-standard.dat": {desc: "ledger: fuzz corpus, standard ledger format"}, 28 28 "actual-ledger-input-transfer.dat": {desc: "ledger: fuzz corpus, byte quantity (non-monetary)"}, 29 - "actual-ledger-input-wow.dat": {desc: "ledger-cli: fuzz corpus, World of Warcraft currency (1G=100s)"}, 29 + "actual-ledger-input-wow.dat": {err: true, desc: "ledger-cli: fuzz corpus, World of Warcraft currency (1G=100s)"}, // no lot support ye 30 30 "actual-multicurrency.journal": {desc: "hledger: multi-currency transactions with HRK/EUR"}, 31 31 "actual-personal.journal": {desc: "hledger: simple personal finance example"}, 32 32 "actual-quickstart.journal": {desc: "hledger: quickstart guide with commodity directive"},
M
journal/lexer/lexer.go
··· 51 51 col int // current column (1-based) 52 52 line int // current line (1-based) 53 53 54 - transactionPastStatus bool 55 - postingExpectAccount bool 54 + transactionPastStatus bool 55 + postingExpectAccount bool 56 56 readingNoteAfterPipe bool 57 57 } 58 58 ··· 321 321 return l.lexSingle(token.LBRACKET) 322 322 case l.ch == ']': 323 323 return l.lexSingle(token.RBRACKET) 324 + case l.ch == ':': 325 + return l.lexSingle(token.COLON) 324 326 case l.postingExpectAccount && l.ch != '*' && l.ch != '!' && l.ch != '(' && l.ch != '[': 325 - l.postingExpectAccount = false 326 - return l.lexAccountText() 327 + return l.lexAccountNamePosting() 327 328 case l.ch == '*': // after account name 328 329 return l.lexSingle(token.STAR) 329 330 case l.isDigit(), l.ch == '.': ··· 334 335 return l.lexSingle(token.PLUS) 335 336 case l.isCommodityStart(): 336 337 return l.lexCommodityMark() 338 + case l.ch == '"' || l.ch == '\'': 339 + return l.lexString() 337 340 case l.ch >= 'a' && l.ch <= 'z': 338 341 return l.lexCommodityMark() 339 342 default: 340 - return l.lexAccountText() 343 + return l.lexAccountNamePosting() 341 344 } 342 345 } 343 346 ··· 359 362 return l.lexSingle(token.MINUS) 360 363 case '"', '\'': 361 364 return l.lexString() 365 + case ':': 366 + return l.lexSingle(token.COLON) 362 367 default: 363 368 if l.isCommodityStart() { 364 369 return l.lexCommodityMark() ··· 372 377 if l.isDigit() { 373 378 return l.lexNumber() 374 379 } 375 - return l.lexText() 380 + return l.lexAccountNameDirective() 376 381 } 377 - // case l.ch == '/': // regex in 'alias' 378 - // return l.lexSingle(token.SLASH) 379 382 } 380 383 381 384 func (l *Lexer) lexSingle(kind token.Type) token.Token { ··· 452 455 return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)} 453 456 } 454 457 455 -func (l *Lexer) lexAccountText() token.Token { 458 +// lexAccountNameDirective reads accout name in directive context. 459 +// stops at any whitespace, supports multi-word names("Taxi Fare"). 460 +func (l *Lexer) lexAccountNameDirective() token.Token { 456 461 s := l.save() 457 - for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ')' && l.ch != ']' { 458 - // two spaces = end of account name 462 + for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ')' && l.ch != ']' && l.ch != ':' && l.ch != ' ' && l.ch != '\t' { 463 + l.advance() 464 + } 465 + lit := string(l.input[s.offset:l.pos]) 466 + return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)} 467 +} 468 + 469 +// lexAccountNamePosting reads an account name in posting context. 470 +// stops at two consecutive spaces. 471 +func (l *Lexer) lexAccountNamePosting() token.Token { 472 + s := l.save() 473 + for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ')' && l.ch != ']' && l.ch != ':' { 459 474 if l.isTwoSpaces() { 460 475 break 461 476 } 462 477 l.advance() 478 + } 479 + if l.ch != ':' { 480 + l.postingExpectAccount = false 463 481 } 464 482 lit := string(l.input[s.offset:l.pos]) 465 483 return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)}
M
journal/lexer/lexer_test.go
··· 39 39 `}, 40 40 {"transaction with virtual accounts", `2024/01/01 * groceries 41 41 (virtual:account) 1 PESO 42 - [something:else] 5 PESO 42 + [some thing:else] 5 PESO 43 43 `}, 44 44 {"transaction with unicode commodity symbols", `2024/01/01 groceries 45 45 expenses:food €10.00
M
journal/lexer/testdata/automated_transaction.golden
··· 4 4 NEWLINE "\n" 2:0-2:1 5 5 INDENT " " 2:1-2:5 6 6 LPAREN "(" 2:5-2:6 7 -TEXT "liabilities:tax" 2:6-2:21 7 +TEXT "liabilities" 2:6-2:17 8 +COLON ":" 2:17-2:18 9 +TEXT "tax" 2:18-2:21 8 10 RPAREN ")" 2:21-2:22 9 11 WHITESPACE " " 2:22-2:24 10 12 STAR "*" 2:24-2:25 ··· 16 18 TEXT "expenses:gifts" 4:3-5:0 17 19 NEWLINE "\n" 5:0-5:1 18 20 INDENT " " 5:1-5:5 19 -TEXT "budget:gifts" 5:5-5:17 21 +TEXT "budget" 5:5-5:11 22 +COLON ":" 5:11-5:12 23 +TEXT "gifts" 5:12-5:17 20 24 WHITESPACE " " 5:17-5:19 21 25 PARENEXPR "(amount * -1)" 5:19-6:0 22 26 NEWLINE "\n" 6:0-6:1
M
journal/lexer/testdata/cleared_transaction.golden
··· 5 5 TEXT "groceries" 1:14-2:0 6 6 NEWLINE "\n" 2:0-2:1 7 7 INDENT " " 2:1-2:5 8 -TEXT "expenses:food" 2:5-2:18 8 +TEXT "expenses" 2:5-2:13 9 +COLON ":" 2:13-2:14 10 +TEXT "food" 2:14-2:18 9 11 WHITESPACE " " 2:18-2:20 10 12 COMMODITYMARK "$" 2:20-2:21 11 13 DECIMAL "10.00" 2:21-3:0 12 14 NEWLINE "\n" 3:0-3:1 13 15 INDENT " " 3:1-3:5 14 -TEXT "assets:checking" 3:5-4:0 16 +TEXT "assets" 3:5-3:11 17 +COLON ":" 3:11-3:12 18 +TEXT "checking" 3:12-4:0 15 19 NEWLINE "\n" 4:0-4:1 16 20 EOF "" 4:1-4:1
M
journal/lexer/testdata/simple_transaction.golden
··· 3 3 TEXT "groceries" 1:12-2:0 4 4 NEWLINE "\n" 2:0-2:1 5 5 INDENT " " 2:1-2:5 6 -TEXT "expenses:food" 2:5-2:18 6 +TEXT "expenses" 2:5-2:13 7 +COLON ":" 2:13-2:14 8 +TEXT "food" 2:14-2:18 7 9 WHITESPACE " " 2:18-2:20 8 10 COMMODITYMARK "$" 2:20-2:21 9 11 DECIMAL "10.00" 2:21-3:0 10 12 NEWLINE "\n" 3:0-3:1 11 13 INDENT " " 3:1-3:5 12 -TEXT "assets:checking" 3:5-4:0 14 +TEXT "assets" 3:5-3:11 15 +COLON ":" 3:11-3:12 16 +TEXT "checking" 3:12-4:0 13 17 NEWLINE "\n" 4:0-4:1 14 18 EOF "" 4:1-4:1
M
journal/lexer/testdata/special_chars_in_description.golden
··· 159 159 TEXT "something *important*" 15:27-16:0 160 160 NEWLINE "\n" 16:0-16:1 161 161 INDENT " " 16:1-16:5 162 -TEXT "expenses:food" 16:5-16:18 162 +TEXT "expenses" 16:5-16:13 163 +COLON ":" 16:13-16:14 164 +TEXT "food" 16:14-16:18 163 165 WHITESPACE " " 16:18-16:20 164 166 DECIMAL "40.00" 16:20-17:0 165 167 NEWLINE "\n" 17:0-17:1 166 168 INDENT " " 17:1-17:5 167 -TEXT "assets:cash" 17:5-18:0 169 +TEXT "assets" 17:5-17:11 170 +COLON ":" 17:11-17:12 171 +TEXT "cash" 17:12-18:0 168 172 NEWLINE "\n" 18:0-18:1 169 173 EOF "" 18:1-18:1
M
journal/lexer/testdata/transaction,_accounts_with_uppercase_latters.golden
··· 6 6 TEXT "Store" 2:17-3:0 7 7 NEWLINE "\n" 3:0-3:1 8 8 INDENT " " 3:1-3:5 9 -TEXT "Expenses:Books" 3:5-3:19 9 +TEXT "Expenses" 3:5-3:13 10 +COLON ":" 3:13-3:14 11 +TEXT "Books" 3:14-3:19 10 12 WHITESPACE " " 3:19-3:42 11 13 COMMODITYMARK "$" 3:42-3:43 12 14 DECIMAL "20.00" 3:43-4:0 13 15 NEWLINE "\n" 4:0-4:1 14 16 INDENT " " 4:1-4:5 15 -TEXT "Liabilities:MasterCard" 4:5-5:0 17 +TEXT "Liabilities" 4:5-4:16 18 +COLON ":" 4:16-4:17 19 +TEXT "MasterCard" 4:17-5:0 16 20 NEWLINE "\n" 5:0-5:1 17 21 EOF "" 5:1-5:1
M
journal/lexer/testdata/transaction_with_code.golden
··· 5 5 TEXT "groceries" 1:18-2:0 6 6 NEWLINE "\n" 2:0-2:1 7 7 INDENT " " 2:1-2:5 8 -TEXT "expenses:food" 2:5-2:18 8 +TEXT "expenses" 2:5-2:13 9 +COLON ":" 2:13-2:14 10 +TEXT "food" 2:14-2:18 9 11 WHITESPACE " " 2:18-2:20 10 12 COMMODITYMARK "$" 2:20-2:21 11 13 DECIMAL "10.00" 2:21-3:0 12 14 NEWLINE "\n" 3:0-3:1 13 15 INDENT " " 3:1-3:5 14 -TEXT "assets:checking" 3:5-4:0 16 +TEXT "assets" 3:5-3:11 17 +COLON ":" 3:11-3:12 18 +TEXT "checking" 3:12-4:0 15 19 NEWLINE "\n" 4:0-5:0 16 20 NEWLINE "\n" 5:0-5:1 17 21 DATE "2024/01/02" 5:1-5:11
M
journal/lexer/testdata/transaction_with_unicode_commodity_symbols.golden
··· 3 3 TEXT "groceries" 1:12-2:0 4 4 NEWLINE "\n" 2:0-2:1 5 5 INDENT " " 2:1-2:5 6 -TEXT "expenses:food" 2:5-2:18 6 +TEXT "expenses" 2:5-2:13 7 +COLON ":" 2:13-2:14 8 +TEXT "food" 2:14-2:18 7 9 WHITESPACE " " 2:18-2:20 8 10 COMMODITYMARK "€" 2:20-2:21 9 11 DECIMAL "10.00" 2:21-3:0 10 12 NEWLINE "\n" 3:0-3:1 11 13 INDENT " " 3:1-3:5 12 -TEXT "expenses:food" 3:5-3:18 14 +TEXT "expenses" 3:5-3:13 15 +COLON ":" 3:13-3:14 16 +TEXT "food" 3:14-3:18 13 17 WHITESPACE " " 3:18-3:20 14 18 COMMODITYMARK "£" 3:20-3:21 15 19 DECIMAL "5.00" 3:21-4:0 16 20 NEWLINE "\n" 4:0-4:1 17 21 INDENT " " 4:1-4:5 18 -TEXT "expenses:food" 4:5-4:18 22 +TEXT "expenses" 4:5-4:13 23 +COLON ":" 4:13-4:14 24 +TEXT "food" 4:14-4:18 19 25 WHITESPACE " " 4:18-4:20 20 26 COMMODITYMARK "₹" 4:20-4:21 21 27 DECIMAL "700.00" 4:21-5:0 22 28 NEWLINE "\n" 5:0-5:1 23 29 INDENT " " 5:1-5:5 24 -TEXT "expenses:food" 5:5-5:18 30 +TEXT "expenses" 5:5-5:13 31 +COLON ":" 5:13-5:14 32 +TEXT "food" 5:14-5:18 25 33 WHITESPACE " " 5:18-5:20 26 34 DECIMAL "40.00" 5:20-5:25 27 35 WHITESPACE " " 5:25-5:26 28 36 COMMODITYMARK "гривні" 5:26-6:0 29 37 NEWLINE "\n" 6:0-6:1 30 38 INDENT " " 6:1-6:5 31 -TEXT "assets:cash" 6:5-7:0 39 +TEXT "assets" 6:5-6:11 40 +COLON ":" 6:11-6:12 41 +TEXT "cash" 6:12-7:0 32 42 NEWLINE "\n" 7:0-7:1 33 43 EOF "" 7:1-7:1
M
journal/lexer/testdata/transaction_with_virtual_accounts.golden
··· 6 6 NEWLINE "\n" 2:0-2:1 7 7 INDENT "\t" 2:1-2:2 8 8 LPAREN "(" 2:2-2:3 9 -TEXT "virtual:account" 2:3-2:18 9 +TEXT "virtual" 2:3-2:10 10 +COLON ":" 2:10-2:11 11 +TEXT "account" 2:11-2:18 10 12 RPAREN ")" 2:18-2:19 11 13 WHITESPACE " " 2:19-2:21 12 14 INT "1" 2:21-2:22 ··· 15 17 NEWLINE "\n" 3:0-3:1 16 18 INDENT "\t" 3:1-3:2 17 19 LBRACKET "[" 3:2-3:3 18 -TEXT "something:else" 3:3-3:17 19 -RBRACKET "]" 3:17-3:18 20 -WHITESPACE " " 3:18-3:21 21 -INT "5" 3:21-3:22 22 -WHITESPACE " " 3:22-3:23 23 -COMMODITYMARK "PESO" 3:23-4:0 20 +TEXT "some thing" 3:3-3:13 21 +COLON ":" 3:13-3:14 22 +TEXT "else" 3:14-3:18 23 +RBRACKET "]" 3:18-3:19 24 +WHITESPACE " " 3:19-3:22 25 +INT "5" 3:22-3:23 26 +WHITESPACE " " 3:23-3:24 27 +COMMODITYMARK "PESO" 3:24-4:0 24 28 NEWLINE "\n" 4:0-4:1 25 29 EOF "" 4:1-4:1
M
journal/parser/parser.go
··· 455 455 alias := &ast.AliasDirective{} 456 456 p.expect(token.ALIAS) 457 457 p.skipWhitespace() 458 - alias.From = p.parseAccount().Name 458 + alias.From = p.parseAccount() 459 459 p.skipWhitespace() 460 460 p.expect(token.EQ) 461 461 p.skipWhitespace() 462 - alias.To = p.parseAccount().Name 462 + alias.To = p.parseAccount() 463 463 p.skipWhitespace() 464 464 alias.Comment = p.parseOptInlineComment() 465 465 p.expectNewline() ··· 627 627 tok, _ := p.expect(token.COMMODITYMARK) 628 628 mp.Commodity = tok.Literal 629 629 p.advance() 630 - p.skipWhitespace() 631 630 632 631 mp.Amount = *p.parseAmount() 633 632 ··· 931 930 // optional amount - after two spaces 932 931 if p.got(token.WHITESPACE) { 933 932 p.skipWhitespace() 933 + if p.got(token.STRING) { // e.g "Plans: Wildthorn Mail" 1 @ 125s 934 + p.advance() 935 + p.skipWhitespace() 936 + } 934 937 if p.isAmountStart() || p.got(token.STAR) { 935 938 posting.Amount = p.parseAmountWithOptExpr() 936 939 } ··· 1002 1005 return ba 1003 1006 } 1004 1007 1005 -func (p *Parser) parseAccount() ast.Account { 1006 - s := p.cur.Span 1007 - var name strings.Builder 1008 - 1008 +func (p *Parser) readAccountSegment() (ast.SubAccount, bool) { 1009 1009 switch p.cur.Type { 1010 1010 case token.TEXT: 1011 - _, _ = name.WriteString(p.cur.Literal) 1011 + sub := ast.SubAccount{Name: p.cur.Literal, Span: p.cur.Span} 1012 1012 p.advance() 1013 - if p.got(token.WHITESPACE) && p.willGet(token.TEXT) && p.peek.Literal[0] != '(' { 1014 - _, _ = name.WriteString(" ") 1013 + 1014 + // handle multi work segment, e.g: "credit card" 1015 + if p.got(token.WHITESPACE) && p.willGet(token.TEXT) && len(p.peek.Literal) > 0 && p.peek.Literal[0] != '(' { 1016 + sub.Name += " " 1015 1017 p.advance() 1016 - _, _ = name.WriteString(p.cur.Literal) 1018 + sub.Name += p.cur.Literal 1017 1019 p.advance() 1018 1020 } 1021 + return sub, true 1022 + 1019 1023 case token.COMMODITYMARK: 1020 - _, _ = name.WriteString(p.cur.Literal) 1024 + sub := ast.SubAccount{Name: p.cur.Literal, Span: p.cur.Span} 1021 1025 p.advance() 1026 + // merge "EUR" + "-HRK" to "EUR-HRK" 1022 1027 for p.got(token.TEXT) { 1023 - _, _ = name.WriteString(p.cur.Literal) 1028 + sub.Name += p.cur.Literal 1024 1029 p.advance() 1025 1030 } 1031 + return sub, true 1032 + 1033 + default: 1034 + return ast.SubAccount{}, false 1026 1035 } 1027 - return ast.Account{Name: name.String(), Span: p.span(s)} 1036 +} 1037 + 1038 +func (p *Parser) parseAccount() ast.Account { 1039 + s := p.cur.Span 1040 + acc := ast.Account{} 1041 + 1042 + sub, ok := p.readAccountSegment() 1043 + if !ok { 1044 + p.errorf("expected account, got %s", p.cur.Type) 1045 + return ast.Account{} 1046 + } 1047 + acc.Name = append(acc.Name, sub) 1048 + 1049 + for p.got(token.COLON) { 1050 + p.advance() 1051 + sub, ok := p.readAccountSegment() 1052 + if !ok { 1053 + break 1054 + } 1055 + acc.Name = append(acc.Name, sub) 1056 + } 1057 + 1058 + acc.Span = p.span(s) 1059 + return acc 1028 1060 } 1029 1061 1030 1062 func (p *Parser) parseDate() ast.Date {
M
journal/parser/testdata/account_directive_with_comment.golden
··· 1 1 Journal 2 2 AccountDirective j:1:1-2:1 3 - Account "expenses:food" j:1:9-1:22 3 + Account j:1:9-1:22 4 + SubAccount: "expenses" j:1:9-1:17 5 + SubAccount: "food" j:1:18-1:22 4 6 Comment j:1:23-2:0 5 7 Marker: ";" 6 8 Text: "my account"
M
journal/parser/testdata/account_with_spaces.golden
··· 3 3 Date: 2026-05-11 4 4 Payee: "testies" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:account name" j:2:2-2:23 6 + Account j:2:2-2:23 7 + SubAccount: "expenses" j:2:2-2:10 8 + SubAccount: "account name" j:2:11-2:23 7 9 Amount j:2:25-2:25 8 10 Quantity: 20 9 11 Commodity: "" ··· 12 14 Precision: 2 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:bank" j:3:2-4:0 17 + Account j:3:2-4:0 18 + SubAccount: "assets" j:3:2-3:8 19 + SubAccount: "bank" j:3:9-4:0 16 20 Amount: <elided>
M
journal/parser/testdata/automated_transaction.golden
··· 3 3 Expr: "^income" 4 4 Posting j:2:1-4:0 5 5 Type: unbalanced virtual 6 - Account "liabilities:tax" j:2:6-2:21 6 + Account j:2:6-2:21 7 + SubAccount: "liabilities" j:2:6-2:17 8 + SubAccount: "tax" j:2:18-2:21 7 9 Amount j:2:25-2:25 8 10 Quantity: 0.33 9 11 Commodity: "" ··· 16 18 AutomatedTransaction j:4:1-4:3 17 19 Expr: "expenses:gifts" 18 20 Posting j:5:1-6:1 19 - Account "budget:gifts" j:5:5-5:17 21 + Account j:5:5-5:17 22 + SubAccount: "budget" j:5:5-5:11 23 + SubAccount: "gifts" j:5:12-5:17 20 24 Amount j:5:20-5:20 21 25 Quantity: -1 22 26 Commodity: "" ··· 26 30 Precision: 0 27 31 Decimal: "." 28 32 Posting j:6:1-8:0 29 - Account "assets:budget" j:6:5-6:18 33 + Account j:6:5-6:18 34 + SubAccount: "assets" j:6:5-6:11 35 + SubAccount: "budget" j:6:12-6:18 30 36 Amount j:6:21-6:21 31 37 Quantity: 1 32 38 Commodity: "" ··· 39 45 AutomatedTransaction j:8:1-8:3 40 46 Expr: "income:salary" 41 47 Posting j:9:1-10:1 42 - Account "budget:savings" j:9:5-9:19 48 + Account j:9:5-9:19 49 + SubAccount: "budget" j:9:5-9:11 50 + SubAccount: "savings" j:9:12-9:19 43 51 Amount j:9:21-10:0 44 52 Quantity: 0 45 53 Commodity: ""
M
journal/parser/testdata/bad_between_good.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:4:1-5:1 15 - Account "assets:cash" j:4:5-4:16 17 + Account j:4:5-4:16 18 + SubAccount: "assets" j:4:5-4:11 19 + SubAccount: "cash" j:4:12-4:16 16 20 Amount j:4:18-4:18 17 21 Quantity: 5 18 22 Commodity: "$"
M
journal/parser/testdata/bad_posting_at_end.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 16 18 Date: 2024/01/02 17 19 Payee: "salary" j:5:12-6:0 18 20 Posting j:6:1-7:1 19 - Account "income:salary" j:6:5-6:18 21 + Account j:6:5-6:18 22 + SubAccount: "income" j:6:5-6:11 23 + SubAccount: "salary" j:6:12-6:18 20 24 Amount j:6:20-6:20 21 25 Quantity: 1000 22 26 Commodity: "$" ··· 25 29 Precision: 0 26 30 Decimal: "." 27 31 Posting j:7:1-8:1 28 - Account "assets:checking" j:7:5-8:0 32 + Account j:7:5-8:0 33 + SubAccount: "assets" j:7:5-7:11 34 + SubAccount: "checking" j:7:12-8:0 29 35 Amount: <elided> 30 36 Errors 31 37 j:3:5-3:7: expected account name, got ATAT
M
journal/parser/testdata/bad_then_next_transaction.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 16 18 Date: 2024/01/02 17 19 Payee: "salary" j:5:12-6:0 18 20 Posting j:6:1-7:1 19 - Account "income:salary" j:6:5-6:18 21 + Account j:6:5-6:18 22 + SubAccount: "income" j:6:5-6:11 23 + SubAccount: "salary" j:6:12-6:18 20 24 Amount j:6:20-6:20 21 25 Quantity: 1000 22 26 Commodity: "$" ··· 25 29 Precision: 0 26 30 Decimal: "." 27 31 Posting j:7:1-8:1 28 - Account "assets:checking" j:7:5-8:0 32 + Account j:7:5-8:0 33 + SubAccount: "assets" j:7:5-7:11 34 + SubAccount: "checking" j:7:12-8:0 29 35 Amount: <elided> 30 36 Errors 31 37 j:3:5-3:7: expected account name, got ATAT
M
journal/parser/testdata/comment_between_bad_postings.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:6:1-7:1 15 - Account "assets:checking" j:6:5-7:0 17 + Account j:6:5-7:0 18 + SubAccount: "assets" j:6:5-6:11 19 + SubAccount: "checking" j:6:12-7:0 16 20 Amount: <elided> 17 21 Errors 18 22 j:3:5-3:7: expected account name, got ATAT
M
journal/parser/testdata/digit_group_marks.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:3-2:16 6 + Account j:2:3-2:16 7 + SubAccount: "expenses" j:2:3-2:11 8 + SubAccount: "food" j:2:12-2:16 7 9 Amount j:2:18-2:18 8 10 Quantity: 1000 9 11 Commodity: "UAH" ··· 13 15 Decimal: "." 14 16 Thousands: " " 15 17 Posting j:3:1-4:1 16 - Account "expenses:supplies" j:3:3-3:20 18 + Account j:3:3-3:20 19 + SubAccount: "expenses" j:3:3-3:11 20 + SubAccount: "supplies" j:3:12-3:20 17 21 Amount j:3:22-3:22 18 22 Quantity: 1000 19 23 Commodity: "USD" ··· 23 27 Decimal: "." 24 28 Thousands: "'" 25 29 Posting j:4:1-5:1 26 - Account "assets:checking" j:4:3-4:18 30 + Account j:4:3-4:18 31 + SubAccount: "assets" j:4:3-4:9 32 + SubAccount: "checking" j:4:10-4:18 27 33 Amount j:4:20-4:20 28 34 Quantity: -2000 29 35 Commodity: "USD"
M
journal/parser/testdata/illegal_between_transactions.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:checking" j:3:5-4:0 17 + Account j:3:5-4:0 18 + SubAccount: "assets" j:3:5-3:11 19 + SubAccount: "checking" j:3:12-4:0 16 20 Amount: <elided> 17 21 Transaction j:5:1-8:1 18 22 Date: 2024/01/02 19 23 Payee: "salary" j:5:12-6:0 20 24 Posting j:6:1-7:1 21 - Account "income:salary" j:6:5-6:18 25 + Account j:6:5-6:18 26 + SubAccount: "income" j:6:5-6:11 27 + SubAccount: "salary" j:6:12-6:18 22 28 Amount j:6:20-6:20 23 29 Quantity: 1000 24 30 Commodity: "$" ··· 27 33 Precision: 0 28 34 Decimal: "." 29 35 Posting j:7:1-8:1 30 - Account "assets:checking" j:7:5-8:0 36 + Account j:7:5-8:0 37 + SubAccount: "assets" j:7:5-7:11 38 + SubAccount: "checking" j:7:12-8:0 31 39 Amount: <elided> 32 40 Errors 33 41 j:4:1-4:2: unexpected token AT
M
journal/parser/testdata/illegal_in_posting.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:3:1-4:1 6 - Account "assets:checking" j:3:5-4:0 6 + Account j:3:5-4:0 7 + SubAccount: "assets" j:3:5-3:11 8 + SubAccount: "checking" j:3:12-4:0 7 9 Amount: <elided> 8 10 Errors 9 11 j:2:5-2:7: expected account name, got ATAT
M
journal/parser/testdata/multiple_bad_postings.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:5:1-6:1 15 - Account "assets:checking" j:5:5-6:0 17 + Account j:5:5-6:0 18 + SubAccount: "assets" j:5:5-5:11 19 + SubAccount: "checking" j:5:12-6:0 16 20 Amount: <elided> 17 21 Errors 18 22 j:3:5-3:7: expected account name, got ATAT
M
journal/parser/testdata/nested_apply_tag_directives.golden
··· 7 7 Date: 2011/01/27 8 8 Payee: "Book Store" j:3:12-4:0 9 9 Posting j:4:1-5:1 10 - Account "expenses:books" j:4:3-4:17 10 + Account j:4:3-4:17 11 + SubAccount: "expenses" j:4:3-4:11 12 + SubAccount: "books" j:4:12-4:17 11 13 Amount j:4:19-4:19 12 14 Quantity: 20 13 15 Commodity: "$" ··· 16 18 Precision: 2 17 19 Decimal: "." 18 20 Posting j:5:1-6:1 19 - Account "liabilities:mastercard" j:5:3-6:0 21 + Account j:5:3-6:0 22 + SubAccount: "liabilities" j:5:3-5:14 23 + SubAccount: "mastercard" j:5:15-6:0 20 24 Amount: <elided> 21 25 EndDirective j:6:1-7:1 22 26 Expr: "apply tag"
M
journal/parser/testdata/period_transaction_expressions.golden
··· 3 3 PeriodicTransaction j:2:1-2:3 4 4 Period: "monthly" 5 5 Posting j:3:1-4:1 6 - Account "expenses:rent" j:3:5-3:18 6 + Account j:3:5-3:18 7 + SubAccount: "expenses" j:3:5-3:13 8 + SubAccount: "rent" j:3:14-3:18 7 9 Amount j:3:28-3:28 8 10 Quantity: 2000 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:4:1-6:0 15 - Account "assets:bank:checking" j:4:5-5:0 17 + Account j:4:5-5:0 18 + SubAccount: "assets" j:4:5-4:11 19 + SubAccount: "bank" j:4:12-4:16 20 + SubAccount: "checking" j:4:17-5:0 16 21 Amount: <elided> 17 22 BlankLine j:6:0-6:1 18 23 PeriodicTransaction j:6:1-6:3 ··· 20 25 From: 2023-04-15 21 26 To: 2023-06-16 22 27 Posting j:7:1-8:1 23 - Account "expenses:utilities" j:7:5-7:23 28 + Account j:7:5-7:23 29 + SubAccount: "expenses" j:7:5-7:13 30 + SubAccount: "utilities" j:7:14-7:23 24 31 Amount j:7:33-7:33 25 32 Quantity: 400 26 33 Commodity: "$" ··· 29 36 Precision: 0 30 37 Decimal: "." 31 38 Posting j:8:1-10:0 32 - Account "assets:bank:checking" j:8:5-9:0 39 + Account j:8:5-9:0 40 + SubAccount: "assets" j:8:5-8:11 41 + SubAccount: "bank" j:8:12-8:16 42 + SubAccount: "checking" j:8:17-9:0 33 43 Amount: <elided> 34 44 BlankLine j:10:0-10:1 35 45 PeriodicTransaction j:10:1-10:3 36 46 Period: "every 2 months" 37 47 Description: "in 2023, we will review" 38 48 Posting j:11:1-12:1 39 - Account "expenses:utilities" j:11:5-11:23 49 + Account j:11:5-11:23 50 + SubAccount: "expenses" j:11:5-11:13 51 + SubAccount: "utilities" j:11:14-11:23 40 52 Amount j:11:33-11:33 41 53 Quantity: 400 42 54 Commodity: "$" ··· 45 57 Precision: 0 46 58 Decimal: "." 47 59 Posting j:12:1-14:0 48 - Account "assets:bank:checking" j:12:5-13:0 60 + Account j:12:5-13:0 61 + SubAccount: "assets" j:12:5-12:11 62 + SubAccount: "bank" j:12:12-12:16 63 + SubAccount: "checking" j:12:17-13:0 49 64 Amount: <elided> 50 65 BlankLine j:14:0-14:1 51 66 PeriodicTransaction j:14:1-14:3 52 67 Period: "monthly" 53 68 Description: "Next year blah blah" 54 69 Posting j:15:1-16:1 55 - Account "expenses:food" j:15:5-15:18 70 + Account j:15:5-15:18 71 + SubAccount: "expenses" j:15:5-15:13 72 + SubAccount: "food" j:15:14-15:18 56 73 Amount j:15:20-15:20 57 74 Quantity: 100 58 75 Commodity: "$" ··· 61 78 Precision: 0 62 79 Decimal: "." 63 80 Posting j:16:1-18:0 64 - Account "assets:checking" j:16:5-17:0 81 + Account j:16:5-17:0 82 + SubAccount: "assets" j:16:5-16:11 83 + SubAccount: "checking" j:16:12-17:0 65 84 Amount: <elided> 66 85 BlankLine j:18:0-18:1 67 86 PeriodicTransaction j:18:1-18:3 ··· 70 89 Marker: ";" 71 90 Text: "In 2019 we will change this" 72 91 Posting j:19:1-20:1 73 - Account "expenses:food" j:19:5-19:18 92 + Account j:19:5-19:18 93 + SubAccount: "expenses" j:19:5-19:13 94 + SubAccount: "food" j:19:14-19:18 74 95 Amount j:19:20-19:20 75 96 Quantity: 100 76 97 Commodity: "$" ··· 79 100 Precision: 0 80 101 Decimal: "." 81 102 Posting j:20:1-21:1 82 - Account "assets:checking" j:20:5-21:0 103 + Account j:20:5-21:0 104 + SubAccount: "assets" j:20:5-20:11 105 + SubAccount: "checking" j:20:12-21:0 83 106 Amount: <elided>
M
journal/parser/testdata/quoted_payee_names.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "groceries store" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:3-2:16 6 + Account j:2:3-2:16 7 + SubAccount: "expenses" j:2:3-2:11 8 + SubAccount: "food" j:2:12-2:16 7 9 Amount j:2:18-2:18 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:checking" j:3:3-4:0 17 + Account j:3:3-4:0 18 + SubAccount: "assets" j:3:3-3:9 19 + SubAccount: "checking" j:3:10-4:0 16 20 Amount: <elided>
M
journal/parser/testdata/recovery_after_bad_posting.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:3:1-5:0 6 - Account "assets:checking" j:3:5-4:0 6 + Account j:3:5-4:0 7 + SubAccount: "assets" j:3:5-3:11 8 + SubAccount: "checking" j:3:12-4:0 7 9 Amount: <elided> 8 10 BlankLine j:5:0-5:1 9 11 Transaction j:5:1-8:1 10 12 Date: 2024/01/02 11 13 Payee: "salary" j:5:12-6:0 12 14 Posting j:6:1-7:1 13 - Account "income:salary" j:6:5-6:18 15 + Account j:6:5-6:18 16 + SubAccount: "income" j:6:5-6:11 17 + SubAccount: "salary" j:6:12-6:18 14 18 Amount j:6:20-6:20 15 19 Quantity: 1000 16 20 Commodity: "$" ··· 19 23 Precision: 0 20 24 Decimal: "." 21 25 Posting j:7:1-8:1 22 - Account "assets:checking" j:7:5-8:0 26 + Account j:7:5-8:0 27 + SubAccount: "assets" j:7:5-7:11 28 + SubAccount: "checking" j:7:12-8:0 23 29 Amount: <elided> 24 30 Errors 25 31 j:2:5-2:7: expected account name, got ATAT
M
journal/parser/testdata/special_chars_in_description.golden
··· 58 58 Payee: "payee !one" j:15:14-15:24 59 59 Note: "something *important*" 60 60 Posting j:16:1-17:1 61 - Account "expenses:food" j:16:5-16:18 61 + Account j:16:5-16:18 62 + SubAccount: "expenses" j:16:5-16:13 63 + SubAccount: "food" j:16:14-16:18 62 64 Amount j:16:20-16:20 63 65 Quantity: 40 64 66 Commodity: "" ··· 67 69 Precision: 2 68 70 Decimal: "." 69 71 Posting j:17:1-18:1 70 - Account "assets:cash" j:17:5-18:0 72 + Account j:17:5-18:0 73 + SubAccount: "assets" j:17:5-17:11 74 + SubAccount: "cash" j:17:12-18:0 71 75 Amount: <elided>
M
journal/parser/testdata/three_bad_postings.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:6:1-7:1 15 - Account "assets:checking" j:6:5-7:0 17 + Account j:6:5-7:0 18 + SubAccount: "assets" j:6:5-6:11 19 + SubAccount: "checking" j:6:12-7:0 16 20 Amount: <elided> 17 21 Errors 18 22 j:3:5-3:7: expected account name, got ATAT
M
journal/parser/testdata/transaction_in_ukrainian.golden
··· 3 3 Date: 2024/03/02 4 4 Payee: "Обід" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "витрати:їжа" j:2:3-2:14 6 + Account j:2:3-2:14 7 + SubAccount: "витрати" j:2:3-2:10 8 + SubAccount: "їжа" j:2:11-2:14 7 9 Amount j:2:16-2:16 8 10 Quantity: 350 9 11 Commodity: "UAH" ··· 12 14 Precision: 0 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "активи:готівка" j:3:3-4:0 17 + Account j:3:3-4:0 18 + SubAccount: "активи" j:3:3-3:9 19 + SubAccount: "готівка" j:3:10-4:0 16 20 Amount: <elided>
M
journal/parser/testdata/transaction_with_balance_assertion.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:2-2:15 6 + Account j:2:2-2:15 7 + SubAccount: "expenses" j:2:2-2:10 8 + SubAccount: "food" j:2:11-2:15 7 9 Amount j:2:17-2:17 8 10 Quantity: 10 9 11 Commodity: "$" ··· 22 24 Precision: 2 23 25 Decimal: "." 24 26 Posting j:3:1-5:0 25 - Account "assets:checking" j:3:2-4:0 27 + Account j:3:2-4:0 28 + SubAccount: "assets" j:3:2-3:8 29 + SubAccount: "checking" j:3:9-4:0 26 30 Amount: <elided> 27 31 BlankLine j:5:0-5:1 28 32 Transaction j:5:1-9:0 29 33 Date: 2025/03/07 30 34 Payee: "groceries2" j:5:12-6:0 31 35 Posting j:6:1-7:1 32 - Account "expenses:food" j:6:2-6:15 36 + Account j:6:2-6:15 37 + SubAccount: "expenses" j:6:2-6:10 38 + SubAccount: "food" j:6:11-6:15 33 39 Amount j:6:17-6:17 34 40 Quantity: 10 35 41 Commodity: "$" ··· 48 54 Precision: 2 49 55 Decimal: "." 50 56 Posting j:7:1-9:0 51 - Account "assets:checking" j:7:2-7:17 57 + Account j:7:2-7:17 58 + SubAccount: "assets" j:7:2-7:8 59 + SubAccount: "checking" j:7:9-7:17 52 60 Amount: <elided> 53 61 BalanceAssertion j:7:19-8:0 54 62 IsStrict: true ··· 65 73 Date: 2025/04/08 66 74 Payee: "groceries3" j:9:12-10:0 67 75 Posting j:10:1-11:1 68 - Account "expenses:food" j:10:2-10:15 76 + Account j:10:2-10:15 77 + SubAccount: "expenses" j:10:2-10:10 78 + SubAccount: "food" j:10:11-10:15 69 79 Amount j:10:17-10:17 70 80 Quantity: 10 71 81 Commodity: "$" ··· 74 84 Precision: 2 75 85 Decimal: "." 76 86 Posting j:11:1-12:1 77 - Account "assets:checking" j:11:2-11:17 87 + Account j:11:2-11:17 88 + SubAccount: "assets" j:11:2-11:8 89 + SubAccount: "checking" j:11:9-11:17 78 90 Amount: <elided> 79 91 BalanceAssertion j:11:19-12:0 80 92 IsStrict: false
M
journal/parser/testdata/transaction_with_code.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "(123) groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 2 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:checking" j:3:5-4:0 17 + Account j:3:5-4:0 18 + SubAccount: "assets" j:3:5-3:11 19 + SubAccount: "checking" j:3:12-4:0 16 20 Amount: <elided>
M
journal/parser/testdata/transaction_with_cost_and_assertion.golden
··· 3 3 Date: 2026-05-11 4 4 Payee: "testies" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:atm" j:2:2-2:14 6 + Account j:2:2-2:14 7 + SubAccount: "expenses" j:2:2-2:10 8 + SubAccount: "atm" j:2:11-2:14 7 9 Amount j:2:16-2:16 8 10 Quantity: 20 9 11 Commodity: "UAH" ··· 30 32 Precision: 0 31 33 Decimal: "." 32 34 Posting j:3:1-4:1 33 - Account "assets:bank" j:3:2-4:0 35 + Account j:3:2-4:0 36 + SubAccount: "assets" j:3:2-3:8 37 + SubAccount: "bank" j:3:9-4:0 34 38 Amount: <elided>
M
journal/parser/testdata/transaction_with_costs.golden
··· 3 3 Date: 2026-05-11 4 4 Payee: "testies" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:atm" j:2:2-2:14 6 + Account j:2:2-2:14 7 + SubAccount: "expenses" j:2:2-2:10 8 + SubAccount: "atm" j:2:11-2:14 7 9 Amount j:2:16-2:16 8 10 Quantity: 20 9 11 Commodity: "UAH" ··· 20 22 Precision: 0 21 23 Decimal: "." 22 24 Posting j:3:1-5:0 23 - Account "assets:bank" j:3:2-4:0 25 + Account j:3:2-4:0 26 + SubAccount: "assets" j:3:2-3:8 27 + SubAccount: "bank" j:3:9-4:0 24 28 Amount: <elided> 25 29 BlankLine j:5:0-5:1 26 30 Transaction j:5:1-9:0 27 31 Date: 2026-05-11 28 32 Payee: "testies2" j:5:12-6:0 29 33 Posting j:6:1-7:1 30 - Account "expenses:atm" j:6:2-6:14 34 + Account j:6:2-6:14 35 + SubAccount: "expenses" j:6:2-6:10 36 + SubAccount: "atm" j:6:11-6:14 31 37 Amount j:6:16-6:16 32 38 Quantity: 20 33 39 Commodity: "UAH" ··· 44 50 Precision: 0 45 51 Decimal: "." 46 52 Posting j:7:1-9:0 47 - Account "assets:bank" j:7:2-8:0 53 + Account j:7:2-8:0 54 + SubAccount: "assets" j:7:2-7:8 55 + SubAccount: "bank" j:7:9-8:0 48 56 Amount: <elided> 49 57 BlankLine j:9:0-9:1 50 58 Transaction j:9:1-13:0 51 59 Date: 2026-05-12 52 60 Payee: "testies3" j:9:12-10:0 53 61 Posting j:10:1-11:1 54 - Account "expenses:atm" j:10:2-10:14 62 + Account j:10:2-10:14 63 + SubAccount: "expenses" j:10:2-10:10 64 + SubAccount: "atm" j:10:11-10:14 55 65 Amount j:10:16-10:16 56 66 Quantity: 20 57 67 Commodity: "UAH" ··· 78 88 Precision: 2 79 89 Decimal: "." 80 90 Posting j:11:1-13:0 81 - Account "assets:bank" j:11:2-12:0 91 + Account j:11:2-12:0 92 + SubAccount: "assets" j:11:2-11:8 93 + SubAccount: "bank" j:11:9-12:0 82 94 Amount: <elided> 83 95 BlankLine j:13:0-13:1 84 96 Transaction j:13:1-16:1 85 97 Date: 2015-01-03 86 98 Payee: "money exchange office" j:13:12-14:0 87 99 Posting j:14:1-15:1 88 - Account "assets:cash" j:14:5-14:16 100 + Account j:14:5-14:16 101 + SubAccount: "assets" j:14:5-14:11 102 + SubAccount: "cash" j:14:12-14:16 89 103 Amount j:14:18-14:18 90 104 Quantity: -20 91 105 Commodity: "EUR" ··· 102 116 Precision: 2 103 117 Decimal: "." 104 118 Posting j:15:1-16:1 105 - Account "assets:cash" j:15:5-15:16 119 + Account j:15:5-15:16 120 + SubAccount: "assets" j:15:5-15:11 121 + SubAccount: "cash" j:15:12-15:16 106 122 Amount j:15:18-15:18 107 123 Quantity: 150.6 108 124 Commodity: "HRK"
M
journal/parser/testdata/transaction_with_digit_payees.golden
··· 4 4 State: "*" 5 5 Payee: "1a1a6305d06ce4b284dba0d267c23f69d70c20be" j:1:14-2:0 6 6 Posting j:2:1-3:1 7 - Account "af0628973ff35bd62ddb048fa41dd8d83c1c46fe" j:2:5-2:45 7 + Account j:2:5-2:45 8 + SubAccount: "af0628973ff35bd62ddb048fa41dd8d83c1c46fe" j:2:5-2:45 8 9 Amount j:2:52-2:52 9 10 Quantity: 474.31 10 11 Commodity: "$" ··· 13 14 Precision: 2 14 15 Decimal: "." 15 16 Posting j:3:1-5:0 16 - Account "fc6f6f10f627ad1a5af9d488c98405a1498d019d" j:3:5-4:0 17 + Account j:3:5-4:0 18 + SubAccount: "fc6f6f10f627ad1a5af9d488c98405a1498d019d" j:3:5-4:0 17 19 Amount: <elided> 18 20 BlankLine j:5:0-5:1 19 21 Transaction j:5:1-8:1 ··· 21 23 State: "*" 22 24 Payee: "9861ce541c17b11f627e71c26bf350b33141f62b" j:5:14-6:0 23 25 Posting j:6:1-7:1 24 - Account "0ecbb1b15e2cf3e515cc0f8533e5bb0fb2326728" j:6:5-6:45 26 + Account j:6:5-6:45 27 + SubAccount: "0ecbb1b15e2cf3e515cc0f8533e5bb0fb2326728" j:6:5-6:45 25 28 Amount j:6:53-6:53 26 29 Quantity: 14.91 27 30 Commodity: "$" ··· 30 33 Precision: 2 31 34 Decimal: "." 32 35 Posting j:7:1-8:1 33 - Account "fc6f6f10f627ad1a5af9d488c98405a1498d019d" j:7:5-8:0 36 + Account j:7:5-8:0 37 + SubAccount: "fc6f6f10f627ad1a5af9d488c98405a1498d019d" j:7:5-8:0 34 38 Amount: <elided>
M
journal/parser/testdata/transaction_with_header_comment.golden
··· 7 7 Marker: ";" 8 8 Text: "header comment" 9 9 Posting j:3:1-4:1 10 - Account "expenses:food" j:3:2-3:15 10 + Account j:3:2-3:15 11 + SubAccount: "expenses" j:3:2-3:10 12 + SubAccount: "food" j:3:11-3:15 11 13 Amount j:3:17-3:17 12 14 Quantity: 10 13 15 Commodity: "$" ··· 16 18 Precision: 2 17 19 Decimal: "." 18 20 Posting j:4:1-5:1 19 - Account "assets:checking" j:4:2-5:0 21 + Account j:4:2-5:0 22 + SubAccount: "assets" j:4:2-4:8 23 + SubAccount: "checking" j:4:9-5:0 20 24 Amount: <elided>
M
journal/parser/testdata/transaction_with_inline_comment.golden
··· 3 3 Date: 2024/01/01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "Expenses:Good" j:2:2-2:15 6 + Account j:2:2-2:15 7 + SubAccount: "Expenses" j:2:2-2:10 8 + SubAccount: "Good" j:2:11-2:15 7 9 Amount j:2:17-2:17 8 10 Quantity: 10 9 11 Commodity: "$" ··· 15 17 Marker: ";" 16 18 Text: "food" 17 19 Posting j:3:1-4:1 18 - Account "Assets:Checking" j:3:2-4:0 20 + Account j:3:2-4:0 21 + SubAccount: "Assets" j:3:2-3:8 22 + SubAccount: "Checking" j:3:9-4:0 19 23 Amount: <elided>
M
journal/parser/testdata/transaction_with_posting.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 2 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:checking" j:3:5-4:0 17 + Account j:3:5-4:0 18 + SubAccount: "assets" j:3:5-3:11 19 + SubAccount: "checking" j:3:12-4:0 16 20 Amount: <elided>
M
journal/parser/testdata/transaction_with_posting_amounts.golden
··· 3 3 Date: 2024.01.01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "UAH" ··· 12 14 Precision: 2 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:checking" j:3:5-3:20 17 + Account j:3:5-3:20 18 + SubAccount: "assets" j:3:5-3:11 19 + SubAccount: "checking" j:3:12-3:20 16 20 Amount j:3:22-3:22 17 21 Quantity: -10 18 22 Commodity: "UAH"
M
journal/parser/testdata/transaction_with_spaced_account_name.golden
··· 3 3 Date: 2022-01-01 4 4 Payee: "opening balances" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "assets" j:2:5-2:11 6 + Account j:2:5-2:11 7 + SubAccount: "assets" j:2:5-2:11 7 8 Amount j:2:31-2:31 8 9 Quantity: 21 9 10 Commodity: "" ··· 22 23 Precision: 0 23 24 Decimal: "." 24 25 Posting j:3:1-4:1 25 - Account "equity:opening/closing balances" j:3:5-4:0 26 + Account j:3:5-4:0 27 + SubAccount: "equity" j:3:5-3:11 28 + SubAccount: "opening/closing balances" j:3:12-4:0 26 29 Amount: <elided>
M
journal/parser/testdata/transaction_with_strange_commodity_symbols.golden
··· 5 5 Transaction j:2:1-6:0 6 6 Date: 2026-05-20 7 7 Posting j:3:1-4:1 8 - Account "asdf" j:3:3-3:7 8 + Account j:3:3-3:7 9 + SubAccount: "asdf" j:3:3-3:7 9 10 Amount j:3:9-3:9 10 11 Quantity: 123 11 12 Commodity: "$€£" ··· 14 15 Precision: 0 15 16 Decimal: "." 16 17 Posting j:4:1-6:0 17 - Account "asdf2" j:4:3-5:0 18 + Account j:4:3-5:0 19 + SubAccount: "asdf2" j:4:3-5:0 18 20 Amount: <elided> 19 21 BlankLine j:6:0-6:1 20 22 Transaction j:6:1-9:1 21 23 Date: 2026-05-20 22 24 Posting j:7:1-8:1 23 - Account "asdf" j:7:3-7:7 25 + Account j:7:3-7:7 26 + SubAccount: "asdf" j:7:3-7:7 24 27 Amount j:7:9-7:9 25 28 Quantity: 123 26 29 Commodity: "bytes" ··· 29 32 Precision: 0 30 33 Decimal: "." 31 34 Posting j:8:1-9:1 32 - Account "asdf2" j:8:3-9:0 35 + Account j:8:3-9:0 36 + SubAccount: "asdf2" j:8:3-9:0 33 37 Amount: <elided>
M
journal/parser/testdata/transaction_with_tabs.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:2-2:15 6 + Account j:2:2-2:15 7 + SubAccount: "expenses" j:2:2-2:10 8 + SubAccount: "food" j:2:11-2:15 7 9 Amount j:2:17-2:17 8 10 Quantity: 10 9 11 Commodity: "$" ··· 12 14 Precision: 2 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "assets:checking" j:3:2-4:0 17 + Account j:3:2-4:0 18 + SubAccount: "assets" j:3:2-3:8 19 + SubAccount: "checking" j:3:9-4:0 16 20 Amount: <elided>
M
journal/parser/testdata/transaction_with_trailing_indent.golden
··· 5 5 State: "*" 6 6 Payee: "pay taxes" j:2:12-3:0 7 7 Posting j:3:1-4:1 8 - Account "expenses:personal:tax" j:3:5-3:26 8 + Account j:3:5-3:26 9 + SubAccount: "expenses" j:3:5-3:13 10 + SubAccount: "personal" j:3:14-3:22 11 + SubAccount: "tax" j:3:23-3:26 9 12 Amount j:3:40-3:40 10 13 Quantity: 1250 11 14 Commodity: "$" ··· 14 17 Precision: 0 15 18 Decimal: "." 16 19 Posting j:4:1-6:0 17 - Account "assets:bank:checking" j:4:5-4:25 20 + Account j:4:5-4:25 21 + SubAccount: "assets" j:4:5-4:11 22 + SubAccount: "bank" j:4:12-4:16 23 + SubAccount: "checking" j:4:17-4:25 18 24 Amount j:4:40-4:40 19 25 Quantity: -1250 20 26 Commodity: "$"
M
journal/parser/testdata/transaction_with_unicode_commodity_symbols.golden
··· 3 3 Date: 2024-01-01 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 - Account "expenses:food" j:2:5-2:18 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 7 9 Amount j:2:20-2:20 8 10 Quantity: 10 9 11 Commodity: "€" ··· 12 14 Precision: 2 13 15 Decimal: "." 14 16 Posting j:3:1-4:1 15 - Account "expenses:food" j:3:5-3:18 17 + Account j:3:5-3:18 18 + SubAccount: "expenses" j:3:5-3:13 19 + SubAccount: "food" j:3:14-3:18 16 20 Amount j:3:20-3:20 17 21 Quantity: 5 18 22 Commodity: "£" ··· 21 25 Precision: 2 22 26 Decimal: "." 23 27 Posting j:4:1-5:1 24 - Account "expenses:food" j:4:5-4:18 28 + Account j:4:5-4:18 29 + SubAccount: "expenses" j:4:5-4:13 30 + SubAccount: "food" j:4:14-4:18 25 31 Amount j:4:20-4:20 26 32 Quantity: 700 27 33 Commodity: "₹" ··· 30 36 Precision: 2 31 37 Decimal: "." 32 38 Posting j:5:1-6:1 33 - Account "assets:checking" j:5:5-6:0 39 + Account j:5:5-6:0 40 + SubAccount: "assets" j:5:5-5:11 41 + SubAccount: "checking" j:5:12-6:0 34 42 Amount: <elided>
M
journal/parser/testdata/transaction_with_virtual_accounts.golden
··· 4 4 Payee: "groceries" j:1:12-2:0 5 5 Posting j:2:1-3:1 6 6 Type: unbalanced virtual 7 - Account "virtual:account" j:2:3-2:18 7 + Account j:2:3-2:18 8 + SubAccount: "virtual" j:2:3-2:10 9 + SubAccount: "account" j:2:11-2:18 8 10 Amount j:2:21-2:21 9 11 Quantity: 1 10 12 Commodity: "PESO" ··· 14 16 Decimal: "." 15 17 Posting j:3:1-4:1 16 18 Type: balanced virtual 17 - Account "something:else" j:3:3-3:17 19 + Account j:3:3-3:17 20 + SubAccount: "something" j:3:3-3:12 21 + SubAccount: "else" j:3:13-3:17 18 22 Amount j:3:21-3:21 19 23 Quantity: 5 20 24 Commodity: "PESO" ··· 23 27 Precision: 0 24 28 Decimal: "." 25 29 Posting j:4:1-5:1 26 - Account "something:else" j:4:2-5:0 30 + Account j:4:2-5:0 31 + SubAccount: "something" j:4:2-4:11 32 + SubAccount: "else" j:4:12-5:0 27 33 Amount: <elided>
M
journal/parser/testdata/virtual_postings_with_statuses.golden
··· 5 5 Posting j:2:1-3:1 6 6 Type: unbalanced virtual 7 7 Status: "!" 8 - Account "assets:cash" j:2:6-2:17 8 + Account j:2:6-2:17 9 + SubAccount: "assets" j:2:6-2:12 10 + SubAccount: "cash" j:2:13-2:17 9 11 Amount j:2:20-2:20 10 12 Quantity: 10 11 13 Commodity: "$" ··· 15 17 Decimal: "." 16 18 Posting j:3:1-5:0 17 19 Type: unbalanced virtual 18 - Account "income:gift" j:3:4-3:15 20 + Account j:3:4-3:15 21 + SubAccount: "income" j:3:4-3:10 22 + SubAccount: "gift" j:3:11-3:15 19 23 Amount j:3:18-3:18 20 24 Quantity: -10 21 25 Commodity: "$" ··· 30 34 Posting j:6:1-7:1 31 35 Type: unbalanced virtual 32 36 Status: "!" 33 - Account "assets:cash" j:6:8-6:19 37 + Account j:6:8-6:19 38 + SubAccount: "assets" j:6:8-6:14 39 + SubAccount: "cash" j:6:15-6:19 34 40 Amount j:6:22-6:22 35 41 Quantity: 10 36 42 Commodity: "$" ··· 40 46 Decimal: "." 41 47 Posting j:7:1-9:0 42 48 Type: unbalanced virtual 43 - Account "income:gift" j:7:6-7:17 49 + Account j:7:6-7:17 50 + SubAccount: "income" j:7:6-7:12 51 + SubAccount: "gift" j:7:13-7:17 44 52 Amount j:7:20-7:20 45 53 Quantity: -10 46 54 Commodity: "$" ··· 55 63 Posting j:10:1-11:1 56 64 Type: unbalanced virtual 57 65 Status: "!" 58 - Account "assets:cash" j:10:10-10:21 66 + Account j:10:10-10:21 67 + SubAccount: "assets" j:10:10-10:16 68 + SubAccount: "cash" j:10:17-10:21 59 69 Amount j:10:24-10:24 60 70 Quantity: 10 61 71 Commodity: "$" ··· 66 76 Posting j:11:1-12:1 67 77 Type: unbalanced virtual 68 78 Status: "*" 69 - Account "income:gift" j:11:8-11:19 79 + Account j:11:8-11:19 80 + SubAccount: "income" j:11:8-11:14 81 + SubAccount: "gift" j:11:15-11:19 70 82 Amount j:11:22-11:22 71 83 Quantity: -10 72 84 Commodity: "$"
M
journal/printer/directives.go
··· 15 15 16 16 func (p *printer) writeAccountDirective(a *ast.AccountDirective) { 17 17 p.buf.WriteString("account ") 18 - p.buf.WriteString(a.Account.Name) 18 + p.buf.WriteString(a.Account.String()) 19 19 p.writeInlineComment(a.Comment) 20 20 } 21 21 ··· 27 27 28 28 func (p *printer) writeAliasDirective(a *ast.AliasDirective) { 29 29 p.buf.WriteString("alias ") 30 - p.buf.WriteString(a.From) 30 + p.buf.WriteString(a.From.String()) 31 31 p.buf.WriteString(" = ") 32 - p.buf.WriteString(a.To) 32 + p.buf.WriteString(a.To.String()) 33 33 p.writeInlineComment(a.Comment) 34 34 } 35 35