all repos

clerk @ a1448b50802379b39dabce5ce0603df42b09c45d

missing tooling for ledger/hledger
233 files changed, 2458 insertions(+), 1366 deletions(-)
tests: switch to txtar
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-07-28 15:22:06 +0300
Authored at: 2026-07-28 13:17:02 +0300
Change ID: kpxnwmwtzmrrrrotouznnlywuvozrrws
Parent: 8575d55
jump to
M internal/linter/linter_test.go
D internal/linter/testdata/account-depth.golden
D internal/linter/testdata/account-depth.input
A internal/linter/testdata/account-depth.txtar
D internal/linter/testdata/correct.golden
M internal/linter/testdata/correct.input → internal/linter/testdata/correct.txtar
D internal/linter/testdata/duplicated-account.golden
D internal/linter/testdata/duplicated-account.input
A internal/linter/testdata/duplicated-account.txtar
D internal/linter/testdata/duplicated-commodity.golden
D internal/linter/testdata/duplicated-commodity.input
A internal/linter/testdata/duplicated-commodity.txtar
D internal/linter/testdata/empty-postings.golden
D internal/linter/testdata/empty-postings.input
A internal/linter/testdata/empty-postings.txtar
D internal/linter/testdata/missing-commodity.golden
D internal/linter/testdata/missing-commodity.input
A internal/linter/testdata/missing-commodity.txtar
D internal/linter/testdata/missing-payee.golden
M internal/linter/testdata/missing-payee.input → internal/linter/testdata/missing-payee.txtar
D internal/linter/testdata/missing-status.golden
D internal/linter/testdata/missing-status.input
A internal/linter/testdata/missing-status.txtar
D internal/linter/testdata/multiple-omitted-amounts.golden
D internal/linter/testdata/multiple-omitted-amounts.input
A internal/linter/testdata/multiple-omitted-amounts.txtar
D internal/linter/testdata/omitted-precision.golden
D internal/linter/testdata/omitted-precision.input
A internal/linter/testdata/omitted-precision.txtar
D internal/linter/testdata/orderdate.golden
D internal/linter/testdata/orderdate.input
A internal/linter/testdata/orderdate.txtar
D internal/linter/testdata/parse-error.golden
D internal/linter/testdata/parse-error.input
A internal/linter/testdata/parse-error.txtar
D internal/linter/testdata/unbalanced-transaction.golden
M internal/linter/testdata/unbalanced-transaction.input → internal/linter/testdata/unbalanced-transaction.txtar
D internal/linter/testdata/undeclared-account.golden
D internal/linter/testdata/undeclared-account.input
A internal/linter/testdata/undeclared-account.txtar
D internal/linter/testdata/undeclared-commodity.golden
M internal/linter/testdata/undeclared-commodity.input → internal/linter/testdata/undeclared-commodity.txtar
D internal/linter/testdata/unused-account.golden
D internal/linter/testdata/unused-account.input
A internal/linter/testdata/unused-account.txtar
M internal/testutil/golden/golden.go
A internal/testutil/txtar/archive.go
A internal/testutil/txtar/archive_test.go
A internal/testutil/txtar/fs.go
A internal/testutil/txtar/fs_test.go
M journal/lexer/lexer_test.go
D journal/lexer/testdata/account_directive.golden
A journal/lexer/testdata/account_directive.txtar
D journal/lexer/testdata/account_with_colon.golden
M journal/lexer/testdata/automated_transaction.golden → journal/lexer/testdata/automated_transaction.txtar
D journal/lexer/testdata/better_date.golden
A journal/lexer/testdata/better_date.txtar
D journal/lexer/testdata/blank_lines.golden
A journal/lexer/testdata/blank_lines.txtar
M journal/lexer/testdata/cleared_transaction.golden → journal/lexer/testdata/cleared_transaction.txtar
M journal/lexer/testdata/comment_block_directive.golden → journal/lexer/testdata/comment_block_directive.txtar
M journal/lexer/testdata/comment_block_directive_without_end.golden → journal/lexer/testdata/comment_block_directive_without_end.txtar
D journal/lexer/testdata/comment_line.golden
A journal/lexer/testdata/comment_line.txtar
D journal/lexer/testdata/commodity_directive.golden
A journal/lexer/testdata/commodity_directive.txtar
D journal/lexer/testdata/date_with_secondary.golden
A journal/lexer/testdata/date_with_secondary.txtar
M journal/lexer/testdata/empty.golden → journal/lexer/testdata/empty.txtar
D journal/lexer/testdata/hash_comment.golden
A journal/lexer/testdata/hash_comment.txtar
D journal/lexer/testdata/inline_comment.golden
A journal/lexer/testdata/inline_comment.txtar
M journal/lexer/testdata/market_price_directive.golden → journal/lexer/testdata/market_price_directive.txtar
M journal/lexer/testdata/market_price_directive_with_time.golden → journal/lexer/testdata/market_price_directive_with_time.txtar
M journal/lexer/testdata/simple_transaction.golden → journal/lexer/testdata/simple_transaction.txtar
M journal/lexer/testdata/special_chars_in_description.golden → journal/lexer/testdata/special_chars_in_description.txtar
D journal/lexer/testdata/star_comment.golden
A journal/lexer/testdata/star_comment.txtar
M journal/lexer/testdata/transaction,_accounts_with_uppercase_latters.golden → journal/lexer/testdata/transaction,_accounts_with_uppercase_latters.txtar
M journal/lexer/testdata/transaction_with_code.golden → journal/lexer/testdata/transaction_with_code.txtar
M journal/lexer/testdata/transaction_with_unicode_commodity_symbols.golden → journal/lexer/testdata/transaction_with_unicode_commodity_symbols.txtar
M journal/lexer/testdata/transaction_with_virtual_accounts.golden → journal/lexer/testdata/transaction_with_virtual_accounts.txtar
M journal/loader.go
M journal/parser/parser_test.go
M journal/parser/testdata/C_directive.golden → journal/parser/testdata/C_directive.txtar
M journal/parser/testdata/D_directive.golden → journal/parser/testdata/D_directive.txtar
M journal/parser/testdata/N_directive.golden → journal/parser/testdata/N_directive.txtar
M journal/parser/testdata/P_directive.golden → journal/parser/testdata/P_directive.txtar
M journal/parser/testdata/P_directive_with_time.golden → journal/parser/testdata/P_directive_with_time.txtar
M journal/parser/testdata/account_directive.golden → journal/parser/testdata/account_directive.txtar
M journal/parser/testdata/account_directive_with_comment.golden → journal/parser/testdata/account_directive_with_comment.txtar
M journal/parser/testdata/account_with_spaces.golden → journal/parser/testdata/account_with_spaces.txtar
D journal/parser/testdata/account_with_subdirectives.golden
A journal/parser/testdata/account_with_subdirectives.txtar
D journal/parser/testdata/alias_directive.golden
A journal/parser/testdata/alias_directive.txtar
M journal/parser/testdata/all_postings_bad.golden → journal/parser/testdata/all_postings_bad.txtar
D journal/parser/testdata/apply_fixed_directive.golden
A journal/parser/testdata/apply_fixed_directive.txtar
D journal/parser/testdata/apply_tag_directive.golden
A journal/parser/testdata/apply_tag_directive.txtar
M journal/parser/testdata/automated_transaction.golden → journal/parser/testdata/automated_transaction.txtar
M journal/parser/testdata/bad_between_good.golden → journal/parser/testdata/bad_between_good.txtar
M journal/parser/testdata/bad_posting_at_end.golden → journal/parser/testdata/bad_posting_at_end.txtar
M journal/parser/testdata/bad_then_next_transaction.golden → journal/parser/testdata/bad_then_next_transaction.txtar
D journal/parser/testdata/blank_line.golden
A journal/parser/testdata/blank_line.txtar
M journal/parser/testdata/comment_between_bad_postings.golden → journal/parser/testdata/comment_between_bad_postings.txtar
M journal/parser/testdata/comment_directive.golden → journal/parser/testdata/comment_directive.txtar
M journal/parser/testdata/comment_directive_end_alone.golden → journal/parser/testdata/comment_directive_end_alone.txtar
M journal/parser/testdata/comment_directive_with_header.golden → journal/parser/testdata/comment_directive_with_header.txtar
M journal/parser/testdata/comment_hash.golden → journal/parser/testdata/comment_hash.txtar
M journal/parser/testdata/comment_percent.golden → journal/parser/testdata/comment_percent.txtar
M journal/parser/testdata/comment_semicolon.golden → journal/parser/testdata/comment_semicolon.txtar
M journal/parser/testdata/comment_star.golden → journal/parser/testdata/comment_star.txtar
M journal/parser/testdata/commodity_quantity_after.golden → journal/parser/testdata/commodity_quantity_after.txtar
M journal/parser/testdata/commodity_quantity_first.golden → journal/parser/testdata/commodity_quantity_first.txtar
D journal/parser/testdata/commodity_with_subdirectives.golden
A journal/parser/testdata/commodity_with_subdirectives.txtar
M journal/parser/testdata/comodity_directive.golden → journal/parser/testdata/comodity_directive.txtar
M journal/parser/testdata/comodity_directive_no_space.golden → journal/parser/testdata/comodity_directive_no_space.txtar
M journal/parser/testdata/comodity_directive_word.golden → journal/parser/testdata/comodity_directive_word.txtar
D journal/parser/testdata/decimal-mark_directive.golden
A journal/parser/testdata/decimal-mark_directive.txtar
M journal/parser/testdata/digit_group_marks.golden → journal/parser/testdata/digit_group_marks.txtar
M journal/parser/testdata/directive_prefixes.golden → journal/parser/testdata/directive_prefixes.txtar
M journal/parser/testdata/empty_comment_block.golden → journal/parser/testdata/empty_comment_block.txtar
D journal/parser/testdata/end_apply_directive.golden
A journal/parser/testdata/end_apply_directive.txtar
D journal/parser/testdata/illegal_at_start.golden
A journal/parser/testdata/illegal_at_start.txtar
M journal/parser/testdata/illegal_between_transactions.golden → journal/parser/testdata/illegal_between_transactions.txtar
M journal/parser/testdata/illegal_in_posting.golden → journal/parser/testdata/illegal_in_posting.txtar
D journal/parser/testdata/illegal_only.golden
D journal/parser/testdata/inclue_directive.golden
A journal/parser/testdata/inclue_directive.txtar
M journal/parser/testdata/inclue_directive_with_comment.golden → journal/parser/testdata/inclue_directive_with_comment.txtar
M journal/parser/testdata/multiple_bad_postings.golden → journal/parser/testdata/multiple_bad_postings.txtar
M journal/parser/testdata/nested_apply_tag_directives.golden → journal/parser/testdata/nested_apply_tag_directives.txtar
M journal/parser/testdata/never_ending_comment_directive.golden → journal/parser/testdata/never_ending_comment_directive.txtar
M journal/parser/testdata/payee_directive.golden → journal/parser/testdata/payee_directive.txtar
M journal/parser/testdata/period_transaction_expressions.golden → journal/parser/testdata/period_transaction_expressions.txtar
M journal/parser/testdata/quoted_payee_names.golden → journal/parser/testdata/quoted_payee_names.txtar
M journal/parser/testdata/recovery_after_bad_posting.golden → journal/parser/testdata/recovery_after_bad_posting.txtar
M journal/parser/testdata/special_chars_in_description.golden → journal/parser/testdata/special_chars_in_description.txtar
D journal/parser/testdata/tag_directive.golden
A journal/parser/testdata/tag_directive.txtar
D journal/parser/testdata/tags.golden
M journal/parser/testdata/three_bad_postings.golden → journal/parser/testdata/three_bad_postings.txtar
D journal/parser/testdata/transaction.golden
A journal/parser/testdata/transaction.txtar
M journal/parser/testdata/transaction_clearerd.golden → journal/parser/testdata/transaction_clearerd.txtar
M journal/parser/testdata/transaction_in_ukrainian.golden → journal/parser/testdata/transaction_in_ukrainian.txtar
M journal/parser/testdata/transaction_pending.golden → journal/parser/testdata/transaction_pending.txtar
M journal/parser/testdata/transaction_with_balance_assertion.golden → journal/parser/testdata/transaction_with_balance_assertion.txtar
M journal/parser/testdata/transaction_with_code.golden → journal/parser/testdata/transaction_with_code.txtar
M journal/parser/testdata/transaction_with_comment.golden → journal/parser/testdata/transaction_with_comment.txtar
M journal/parser/testdata/transaction_with_cost_and_assertion.golden → journal/parser/testdata/transaction_with_cost_and_assertion.txtar
M journal/parser/testdata/transaction_with_costs.golden → journal/parser/testdata/transaction_with_costs.txtar
M journal/parser/testdata/transaction_with_digit_payees.golden → journal/parser/testdata/transaction_with_digit_payees.txtar
M journal/parser/testdata/transaction_with_header_comment.golden → journal/parser/testdata/transaction_with_header_comment.txtar
M journal/parser/testdata/transaction_with_inline_comment.golden → journal/parser/testdata/transaction_with_inline_comment.txtar
M journal/parser/testdata/transaction_with_multiword_payee.golden → journal/parser/testdata/transaction_with_multiword_payee.txtar
M journal/parser/testdata/transaction_with_note.golden → journal/parser/testdata/transaction_with_note.txtar
M journal/parser/testdata/transaction_with_payee.golden → journal/parser/testdata/transaction_with_payee.txtar
M journal/parser/testdata/transaction_with_posting.golden → journal/parser/testdata/transaction_with_posting.txtar
M journal/parser/testdata/transaction_with_posting_amounts.golden → journal/parser/testdata/transaction_with_posting_amounts.txtar
M journal/parser/testdata/transaction_with_quoted_commodity.golden → journal/parser/testdata/transaction_with_quoted_commodity.txtar
M journal/parser/testdata/transaction_with_secondary_date.golden → journal/parser/testdata/transaction_with_secondary_date.txtar
M journal/parser/testdata/transaction_with_spaced_account_name.golden → journal/parser/testdata/transaction_with_spaced_account_name.txtar
M journal/parser/testdata/transaction_with_strange_commodity_symbols.golden → journal/parser/testdata/transaction_with_strange_commodity_symbols.txtar
M journal/parser/testdata/transaction_with_tabs.golden → journal/parser/testdata/transaction_with_tabs.txtar
M journal/parser/testdata/transaction_with_trailing_indent.golden → journal/parser/testdata/transaction_with_trailing_indent.txtar
M journal/parser/testdata/transaction_with_unicode_commodity_symbols.golden → journal/parser/testdata/transaction_with_unicode_commodity_symbols.txtar
M journal/parser/testdata/transaction_with_virtual_accounts.golden → journal/parser/testdata/transaction_with_virtual_accounts.txtar
M journal/parser/testdata/unexpected_token.golden → journal/parser/testdata/illegal_only.txtar
A journal/parser/testdata/unexpected_token.txtar
M journal/parser/testdata/virtual_postings_with_statuses.golden → journal/parser/testdata/virtual_postings_with_statuses.txtar
D journal/parser/testdata/year_directive.golden
A journal/parser/testdata/year_directive.txtar
M journal/printer/printer_test.go
M journal/printer/testdata/align_right.golden → journal/printer/testdata/align_right.txtar
D journal/printer/testdata/align_right.input
D journal/printer/testdata/align_tab.golden
D journal/printer/testdata/align_tab.input
A journal/printer/testdata/align_tab.txtar
D journal/printer/testdata/basic_twospaces.golden
D journal/printer/testdata/commodity_before.golden
D journal/printer/testdata/commodity_before.input
A journal/printer/testdata/commodity_before.txtar
D journal/printer/testdata/directives.golden
D journal/printer/testdata/directives.input
A journal/printer/testdata/directives.txtar
D journal/printer/testdata/entries.golden
D journal/printer/testdata/entries.input
A journal/printer/testdata/entries.txtar
D journal/printer/testdata/indent_width.golden
D journal/printer/testdata/indent_width.input
A journal/printer/testdata/indent_width.txtar
D journal/printer/testdata/preserve_blanks.golden
D journal/printer/testdata/preserve_blanks.input
A journal/printer/testdata/preserve_blanks.txtar
D journal/printer/testdata/sample.golden
D journal/printer/testdata/sample.input
A journal/printer/testdata/sample.txtar
D journal/printer/testdata/tab_indent.golden
D journal/printer/testdata/tab_indent.input
A journal/printer/testdata/tab_indent.txtar
M journal/semantic/build_test.go
D journal/semantic/testdata/empty.golden
A journal/semantic/testdata/empty.txtar
M journal/semantic/testdata/include.golden → journal/semantic/testdata/include.txtar
D journal/semantic/testdata/include0.input
D journal/semantic/testdata/include1.input
M journal/semantic/testdata/journal.golden → journal/semantic/testdata/journal.txtar
D journal/semantic/testdata/journal.input
M journal/tags/tag_test.go
D journal/tags/testdata/basic.golden
D journal/tags/testdata/basic.input
A journal/tags/testdata/basic.txtar
D journal/tags/testdata/crossfile.golden
A journal/tags/testdata/crossfile.txtar
D journal/tags/testdata/crossfile1.input
D journal/tags/testdata/crossfile2.input
D journal/tags/testdata/duplicates.golden
D journal/tags/testdata/duplicates.input
A journal/tags/testdata/duplicates.txtar
D journal/tags/testdata/kinds.golden
D journal/tags/testdata/kinds.input
A journal/tags/testdata/kinds.txtar
D journal/tags/testdata/pseudo.golden
D journal/tags/testdata/pseudo.input
M internal/linter/linter_test.go
···
        31
        31
         func TestLinter(t *testing.T) {

      
        32
        32
         	for tname, trules := range tests {

      
        33
        33
         		t.Run(tname, func(t *testing.T) {

      
        34
        
        -			inp := golden.Load(t, tname)

      
        35
        
        -			pf, err := journal.NewLoader().LoadBytes(tname+".journal", inp)

      
        
        34
        +			a := golden.Read(t, tname)

      
        
        35
        +			fsys, err := a.FS()

      
        36
        36
         			if err != nil {

      
        37
        
        -				t.Fatalf("failed to load test journal: %v\n", err)

      
        
        37
        +				t.Fatal(err)

      
        
        38
        +			}

      
        
        39
        +

      
        
        40
        +			l := journal.NewLoader()

      
        
        41
        +			if _, err := l.LoadFS(fsys, "in.journal"); err != nil {

      
        
        42
        +				t.Fatalf("failed to load test journal: %v", err)

      
        38
        43
         			}

      
        39
        44
         

      
        40
        
        -			ctx := semantic.Build([]*journal.ParsedFile{pf})

      
        
        45
        +			ctx := semantic.Build(l.Ordered())

      
        41
        46
         			finds := NewLinter(trules).Run(ctx)

      
        42
        47
         

      
        43
        48
         			var b strings.Builder

      
        44
        49
         			Fprint(&b, PathBasename, finds)

      
        45
        
        -			golden.Assert(t, tname, b.String())

      
        
        50
        +			golden.Assert(t, a, b.String())

      
        46
        51
         		})

      
        47
        52
         	}

      
        48
        53
         }

      
D internal/linter/testdata/account-depth.golden
···
        1
        
        -account-depth.journal:2:9: account-depth: account "Expenses:Food:Restaurants:Pizza:Hawaiian" depth (5) exceeds max allowed depth (3)

      
        2
        
        -account-depth.journal:3:9: account-depth: account "Expenses:Tax:Income:More:Deep:Too" depth (6) exceeds max allowed depth (3)

      
        3
        
        -account-depth.journal:5:19: account-depth: account "Expenses:Food:Restaurants:Pizza:Hawaiian" depth (5) exceeds max allowed depth (3)

      
        4
        
        -account-depth.journal:12:5: account-depth: account "Income:Job:Salary:Monthly:Base" depth (5) exceeds max allowed depth (3)

      
        5
        
        -account-depth.journal:13:5: account-depth: account "Assets:Checking:Deep:Sub" depth (4) exceeds max allowed depth (3)

      
D internal/linter/testdata/account-depth.input
···
        1
        
        -account Expenses:Food:Restaurants

      
        2
        
        -account Expenses:Food:Restaurants:Pizza:Hawaiian

      
        3
        
        -account Expenses:Tax:Income:More:Deep:Too

      
        4
        
        -

      
        5
        
        -alias Fast:Food = Expenses:Food:Restaurants:Pizza:Hawaiian

      
        6
        
        -

      
        7
        
        -2024-01-01 groceries

      
        8
        
        -    Expenses:Food  $10.00

      
        9
        
        -    Assets:Checking

      
        10
        
        -

      
        11
        
        -2024-01-02 salary

      
        12
        
        -    Income:Job:Salary:Monthly:Base  $1000

      
        13
        
        -    Assets:Checking:Deep:Sub

      
A internal/linter/testdata/account-depth.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +account Expenses:Food:Restaurants

      
        
        3
        +account Expenses:Food:Restaurants:Pizza:Hawaiian

      
        
        4
        +account Expenses:Tax:Income:More:Deep:Too

      
        
        5
        +

      
        
        6
        +alias Fast:Food = Expenses:Food:Restaurants:Pizza:Hawaiian

      
        
        7
        +

      
        
        8
        +2024-01-01 groceries

      
        
        9
        +    Expenses:Food  $10.00

      
        
        10
        +    Assets:Checking

      
        
        11
        +

      
        
        12
        +2024-01-02 salary

      
        
        13
        +    Income:Job:Salary:Monthly:Base  $1000

      
        
        14
        +    Assets:Checking:Deep:Sub

      
        
        15
        +

      
        
        16
        +-- expect --

      
        
        17
        +in.journal:2:9: account-depth: account "Expenses:Food:Restaurants:Pizza:Hawaiian" depth (5) exceeds max allowed depth (3)

      
        
        18
        +in.journal:3:9: account-depth: account "Expenses:Tax:Income:More:Deep:Too" depth (6) exceeds max allowed depth (3)

      
        
        19
        +in.journal:5:19: account-depth: account "Expenses:Food:Restaurants:Pizza:Hawaiian" depth (5) exceeds max allowed depth (3)

      
        
        20
        +in.journal:12:5: account-depth: account "Income:Job:Salary:Monthly:Base" depth (5) exceeds max allowed depth (3)

      
        
        21
        +in.journal:13:5: account-depth: account "Assets:Checking:Deep:Sub" depth (4) exceeds max allowed depth (3)

      
D internal/linter/testdata/correct.golden

Not showing binary file.

M internal/linter/testdata/correct.inputinternal/linter/testdata/correct.txtar
···
        
        1
        +-- in.journal --

      
        1
        2
         account expenses:food

      
        2
        3
         account assets:cash

      
        3
        4
         

      ···
        12
        13
         2024-05-02 * "groceries"

      
        13
        14
             expenses:food   40.00 USD

      
        14
        15
             assets:cash

      
        
        16
        +

      
        
        17
        +-- expect --

      
D internal/linter/testdata/duplicated-account.golden
···
        1
        
        -duplicated-account.journal:1:9: duplicated-account: duplicated account declaration: Expenses:Food

      
        2
        
        -duplicated-account.journal:2:9: duplicated-account: duplicated account declaration: Expenses:Transport

      
        3
        
        -duplicated-account.journal:4:9: duplicated-account: duplicated account declaration: Expenses:Food

      
        4
        
        -duplicated-account.journal:6:9: duplicated-account: duplicated account declaration: Expenses:Transport

      
D internal/linter/testdata/duplicated-account.input
···
        1
        
        -account Expenses:Food

      
        2
        
        -account Expenses:Transport

      
        3
        
        -account Income:Salary

      
        4
        
        -account Expenses:Food

      
        5
        
        -account Assets:Cash

      
        6
        
        -account Expenses:Transport

      
A internal/linter/testdata/duplicated-account.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +account Expenses:Food

      
        
        3
        +account Expenses:Transport

      
        
        4
        +account Income:Salary

      
        
        5
        +account Expenses:Food

      
        
        6
        +account Assets:Cash

      
        
        7
        +account Expenses:Transport

      
        
        8
        +

      
        
        9
        +-- expect --

      
        
        10
        +in.journal:1:9: duplicated-account: duplicated account declaration: Expenses:Food

      
        
        11
        +in.journal:2:9: duplicated-account: duplicated account declaration: Expenses:Transport

      
        
        12
        +in.journal:4:9: duplicated-account: duplicated account declaration: Expenses:Food

      
        
        13
        +in.journal:6:9: duplicated-account: duplicated account declaration: Expenses:Transport

      
D internal/linter/testdata/duplicated-commodity.golden
···
        1
        
        -duplicated-commodity.journal:1:1: duplicated-commodity: duplicated commodity declaration: USD

      
        2
        
        -duplicated-commodity.journal:2:1: duplicated-commodity: duplicated commodity declaration: EUR

      
        3
        
        -duplicated-commodity.journal:4:1: duplicated-commodity: duplicated commodity declaration: USD

      
        4
        
        -duplicated-commodity.journal:6:1: duplicated-commodity: duplicated commodity declaration: EUR

      
D internal/linter/testdata/duplicated-commodity.input
···
        1
        
        -commodity USD

      
        2
        
        -commodity EUR

      
        3
        
        -commodity GBP

      
        4
        
        -commodity USD

      
        5
        
        -commodity JPY

      
        6
        
        -commodity EUR

      
A internal/linter/testdata/duplicated-commodity.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +commodity USD

      
        
        3
        +commodity EUR

      
        
        4
        +commodity GBP

      
        
        5
        +commodity USD

      
        
        6
        +commodity JPY

      
        
        7
        +commodity EUR

      
        
        8
        +

      
        
        9
        +-- expect --

      
        
        10
        +in.journal:1:1: duplicated-commodity: duplicated commodity declaration: USD

      
        
        11
        +in.journal:2:1: duplicated-commodity: duplicated commodity declaration: EUR

      
        
        12
        +in.journal:4:1: duplicated-commodity: duplicated commodity declaration: USD

      
        
        13
        +in.journal:6:1: duplicated-commodity: duplicated commodity declaration: EUR

      
D internal/linter/testdata/empty-postings.golden
···
        1
        
        -empty-postings.journal:1:1: empty-postings: transaction has no postings

      
D internal/linter/testdata/empty-postings.input
···
        1
        
        -2024-05-01 empty txn

      
        2
        
        -

      
        3
        
        -2024-05-01 "not empty txn"

      
        4
        
        -  my:account

      
A internal/linter/testdata/empty-postings.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +2024-05-01 empty txn

      
        
        3
        +

      
        
        4
        +2024-05-01 "not empty txn"

      
        
        5
        +  my:account

      
        
        6
        +

      
        
        7
        +-- expect --

      
        
        8
        +in.journal:1:1: empty-postings: transaction has no postings

      
D internal/linter/testdata/missing-commodity.golden
···
        1
        
        -missing-commodity.journal:2:21: missing-commodity: amount missing commodity

      
        2
        
        -missing-commodity.journal:10:21: missing-commodity: amount missing commodity

      
        3
        
        -missing-commodity.journal:11:20: missing-commodity: amount missing commodity

      
        4
        
        -missing-commodity.journal:11:27: missing-commodity: amount missing commodity

      
        5
        
        -missing-commodity.journal:15:18: missing-commodity: amount missing commodity

      
        6
        
        -missing-commodity.journal:19:3: missing-commodity: amount missing commodity

      
        7
        
        -missing-commodity.journal:21:3: missing-commodity: amount missing commodity

      
        8
        
        -missing-commodity.journal:26:20: missing-commodity: amount missing commodity

      
        9
        
        -missing-commodity.journal:30:20: missing-commodity: amount missing commodity

      
D internal/linter/testdata/missing-commodity.input
···
        1
        
        -2024-06-01 "no commodity"

      
        2
        
        -    expenses:food   50.00

      
        3
        
        -    assets:cash

      
        4
        
        -

      
        5
        
        -2024-06-02 "with commodity"

      
        6
        
        -    expenses:food   50.00 USD

      
        7
        
        -    assets:cash

      
        8
        
        -

      
        9
        
        -2024-06-03 "another missing"

      
        10
        
        -    expenses:food   100 @ 1$

      
        11
        
        -    expenses:rent  1000 @ 1

      
        12
        
        -    expenses:rent2  1000 USD @ 1 UAH

      
        13
        
        -    assets:cash

      
        14
        
        -

      
        15
        
        -P 2026-06-20 USD 2

      
        16
        
        -P 2026-06-20 USD 2 UAH

      
        17
        
        -

      
        18
        
        -D 100 USD

      
        19
        
        -D 100

      
        20
        
        -

      
        21
        
        -C 1 = 40 UAH

      
        22
        
        -C 1 USD = 40 UAH

      
        23
        
        -

      
        24
        
        -~ monthly

      
        25
        
        -    expenses:rent  $2000

      
        26
        
        -    expenses:food  2000

      
        27
        
        -    assets:bank

      
        28
        
        -

      
        29
        
        -= /restaurant/

      
        30
        
        -    expenses:food  20.00

      
        31
        
        -    assets:checking

      
A internal/linter/testdata/missing-commodity.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +2024-06-01 "no commodity"

      
        
        3
        +    expenses:food   50.00

      
        
        4
        +    assets:cash

      
        
        5
        +

      
        
        6
        +2024-06-02 "with commodity"

      
        
        7
        +    expenses:food   50.00 USD

      
        
        8
        +    assets:cash

      
        
        9
        +

      
        
        10
        +2024-06-03 "another missing"

      
        
        11
        +    expenses:food   100 @ 1$

      
        
        12
        +    expenses:rent  1000 @ 1

      
        
        13
        +    expenses:rent2  1000 USD @ 1 UAH

      
        
        14
        +    assets:cash

      
        
        15
        +

      
        
        16
        +P 2026-06-20 USD 2

      
        
        17
        +P 2026-06-20 USD 2 UAH

      
        
        18
        +

      
        
        19
        +D 100 USD

      
        
        20
        +D 100

      
        
        21
        +

      
        
        22
        +C 1 = 40 UAH

      
        
        23
        +C 1 USD = 40 UAH

      
        
        24
        +

      
        
        25
        +~ monthly

      
        
        26
        +    expenses:rent  $2000

      
        
        27
        +    expenses:food  2000

      
        
        28
        +    assets:bank

      
        
        29
        +

      
        
        30
        += /restaurant/

      
        
        31
        +    expenses:food  20.00

      
        
        32
        +    assets:checking

      
        
        33
        +

      
        
        34
        +-- expect --

      
        
        35
        +in.journal:2:21: missing-commodity: amount missing commodity

      
        
        36
        +in.journal:10:21: missing-commodity: amount missing commodity

      
        
        37
        +in.journal:11:20: missing-commodity: amount missing commodity

      
        
        38
        +in.journal:11:27: missing-commodity: amount missing commodity

      
        
        39
        +in.journal:15:18: missing-commodity: amount missing commodity

      
        
        40
        +in.journal:19:3: missing-commodity: amount missing commodity

      
        
        41
        +in.journal:21:3: missing-commodity: amount missing commodity

      
        
        42
        +in.journal:26:20: missing-commodity: amount missing commodity

      
        
        43
        +in.journal:30:20: missing-commodity: amount missing commodity

      
D internal/linter/testdata/missing-payee.golden
···
        1
        
        -missing-payee.journal:5:1: missing-payee: transaction has no payee

      
M internal/linter/testdata/missing-payee.inputinternal/linter/testdata/missing-payee.txtar
···
        
        1
        +-- in.journal --

      
        1
        2
         2024-05-01 "groceries"

      
        2
        3
           expenses:food  50.00 USD

      
        3
        4
           assets:cash

      ···
        9
        10
         2024-05-03 * "groceries"

      
        10
        11
           expenses:food  50.00 USD

      
        11
        12
           assets:cash

      
        
        13
        +

      
        
        14
        +-- expect --

      
        
        15
        +in.journal:5:1: missing-payee: transaction has no payee

      
D internal/linter/testdata/missing-status.golden
···
        1
        
        -missing-status.journal:1:12: missing-status: transaction has no status

      
D internal/linter/testdata/missing-status.input
···
        1
        
        -2024-05-01 "groceries"

      
        2
        
        -    expenses:food   50.00 USD

      
        3
        
        -    assets:cash

      
A internal/linter/testdata/missing-status.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +2024-05-01 "groceries"

      
        
        3
        +    expenses:food   50.00 USD

      
        
        4
        +    assets:cash

      
        
        5
        +

      
        
        6
        +-- expect --

      
        
        7
        +in.journal:1:12: missing-status: transaction has no status

      
D internal/linter/testdata/multiple-omitted-amounts.golden
···
        1
        
        -multiple-omitted-amounts.journal:2:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        2
        
        -multiple-omitted-amounts.journal:3:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        3
        
        -multiple-omitted-amounts.journal:7:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        4
        
        -multiple-omitted-amounts.journal:8:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        5
        
        -multiple-omitted-amounts.journal:9:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        6
        
        -multiple-omitted-amounts.journal:11:1: multiple-omitted-amounts: more than one posting has omitted amount

      
D internal/linter/testdata/multiple-omitted-amounts.input
···
        1
        
        -2025-01-01 "two omitted amounts"

      
        2
        
        -    Expenses:Food

      
        3
        
        -    Expenses:Fun

      
        4
        
        -    Assets:Cash  100 USD

      
        5
        
        -

      
        6
        
        -2025-01-02 "three omitted amounts"

      
        7
        
        -    Expenses:Food

      
        8
        
        -    Expenses:Fun

      
        9
        
        -    Expenses:Play

      
        10
        
        -    Assets:Cash  100 USD

      
        11
        
        -    Equity:Opening-Balances

      
        12
        
        -

      
        13
        
        -2025-01-03 "one omitted amount (ok)"

      
        14
        
        -    Expenses:Food  50 USD

      
        15
        
        -    Assets:Cash

      
        16
        
        -

      
        17
        
        -2025-01-04 "all have amounts (ok)"

      
        18
        
        -    Expenses:Food  50 USD

      
        19
        
        -    Assets:Cash  50 USD

      
        20
        
        -

      
        21
        
        -2025-01-05 * reconcile or some s..stuff

      
        22
        
        -  Assets:Bank  = 1.69 UAH

      
        23
        
        -  Assets:Cash  = 2.00 UAH

      
A internal/linter/testdata/multiple-omitted-amounts.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +2025-01-01 "two omitted amounts"

      
        
        3
        +    Expenses:Food

      
        
        4
        +    Expenses:Fun

      
        
        5
        +    Assets:Cash  100 USD

      
        
        6
        +

      
        
        7
        +2025-01-02 "three omitted amounts"

      
        
        8
        +    Expenses:Food

      
        
        9
        +    Expenses:Fun

      
        
        10
        +    Expenses:Play

      
        
        11
        +    Assets:Cash  100 USD

      
        
        12
        +    Equity:Opening-Balances

      
        
        13
        +

      
        
        14
        +2025-01-03 "one omitted amount (ok)"

      
        
        15
        +    Expenses:Food  50 USD

      
        
        16
        +    Assets:Cash

      
        
        17
        +

      
        
        18
        +2025-01-04 "all have amounts (ok)"

      
        
        19
        +    Expenses:Food  50 USD

      
        
        20
        +    Assets:Cash  50 USD

      
        
        21
        +

      
        
        22
        +2025-01-05 * reconcile or some s..stuff

      
        
        23
        +  Assets:Bank  = 1.69 UAH

      
        
        24
        +  Assets:Cash  = 2.00 UAH

      
        
        25
        +

      
        
        26
        +-- expect --

      
        
        27
        +in.journal:2:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        
        28
        +in.journal:3:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        
        29
        +in.journal:7:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        
        30
        +in.journal:8:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        
        31
        +in.journal:9:1: multiple-omitted-amounts: more than one posting has omitted amount

      
        
        32
        +in.journal:11:1: multiple-omitted-amounts: more than one posting has omitted amount

      
D internal/linter/testdata/omitted-precision.golden
···
        1
        
        -omitted-precision.journal:2:9: omitted-precision: amount has insufficient precision

      
        2
        
        -omitted-precision.journal:4:9: omitted-precision: amount has insufficient precision

      
D internal/linter/testdata/omitted-precision.input
···
        1
        
        -2026-06-10 *  asdf

      
        2
        
        -  qwer  1 UIO

      
        3
        
        -  asdf  1.00 ASD

      
        4
        
        -  zxcv  2 UIO

      
A internal/linter/testdata/omitted-precision.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +2026-06-10 *  asdf

      
        
        3
        +  qwer  1 UIO

      
        
        4
        +  asdf  1.00 ASD

      
        
        5
        +  zxcv  2 UIO

      
        
        6
        +

      
        
        7
        +-- expect --

      
        
        8
        +in.journal:2:9: omitted-precision: amount has insufficient precision

      
        
        9
        +in.journal:4:9: omitted-precision: amount has insufficient precision

      
D internal/linter/testdata/orderdate.golden
···
        1
        
        -orderdate.journal:5:1: orderdate: transaction is out of chronological order (date 2026-01-10 before 2026-01-15)

      
        2
        
        -orderdate.journal:9:1: orderdate: transaction is out of chronological order (date 2026-01-12 before 2026-01-15)

      
        3
        
        -orderdate.journal:17:1: orderdate: transaction is out of chronological order (date 2026-01-20 before 2026-02-01)

      
        4
        
        -orderdate.journal:25:1: orderdate: transaction is out of chronological order (date 2025-12-01 before 2026-03-01)

      
D internal/linter/testdata/orderdate.input
···
        1
        
        -2026-01-15 * "Transaction 1"

      
        2
        
        -  Expenses:Food      $10

      
        3
        
        -  Assets:Cash

      
        4
        
        -

      
        5
        
        -2026-01-10 * "Transaction 2 - out of order (before Txn 1)"

      
        6
        
        -  Expenses:Food      $20

      
        7
        
        -  Assets:Cash

      
        8
        
        -

      
        9
        
        -2026-01-12 * "Transaction 3 - would slip through old prev logic (12 < 15 but 12 > 10)"

      
        10
        
        -  Expenses:Food      $8

      
        11
        
        -  Assets:Cash

      
        12
        
        -

      
        13
        
        -2026-02-01 * "Transaction 4"

      
        14
        
        -  Expenses:Food      $15

      
        15
        
        -  Assets:Cash

      
        16
        
        -

      
        17
        
        -2026-01-20 * "Transaction 5 - out of order (before Txn 4)"

      
        18
        
        -  Expenses:Food      $5

      
        19
        
        -  Assets:Cash

      
        20
        
        -

      
        21
        
        -2026-03-01 * "Transaction 6"

      
        22
        
        -  Expenses:Food      $25

      
        23
        
        -  Assets:Cash

      
        24
        
        -

      
        25
        
        -2025-12-01 * "Transaction 7 - entirely before everything"

      
        26
        
        -  Expenses:Food      $30

      
        27
        
        -  Assets:Cash

      
A internal/linter/testdata/orderdate.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +2026-01-15 * "Transaction 1"

      
        
        3
        +  Expenses:Food      $10

      
        
        4
        +  Assets:Cash

      
        
        5
        +

      
        
        6
        +2026-01-10 * "Transaction 2 - out of order (before Txn 1)"

      
        
        7
        +  Expenses:Food      $20

      
        
        8
        +  Assets:Cash

      
        
        9
        +

      
        
        10
        +2026-01-12 * "Transaction 3 - would slip through old prev logic (12 < 15 but 12 > 10)"

      
        
        11
        +  Expenses:Food      $8

      
        
        12
        +  Assets:Cash

      
        
        13
        +

      
        
        14
        +2026-02-01 * "Transaction 4"

      
        
        15
        +  Expenses:Food      $15

      
        
        16
        +  Assets:Cash

      
        
        17
        +

      
        
        18
        +2026-01-20 * "Transaction 5 - out of order (before Txn 4)"

      
        
        19
        +  Expenses:Food      $5

      
        
        20
        +  Assets:Cash

      
        
        21
        +

      
        
        22
        +2026-03-01 * "Transaction 6"

      
        
        23
        +  Expenses:Food      $25

      
        
        24
        +  Assets:Cash

      
        
        25
        +

      
        
        26
        +2025-12-01 * "Transaction 7 - entirely before everything"

      
        
        27
        +  Expenses:Food      $30

      
        
        28
        +  Assets:Cash

      
        
        29
        +

      
        
        30
        +-- expect --

      
        
        31
        +in.journal:5:1: orderdate: transaction is out of chronological order (date 2026-01-10 before 2026-01-15)

      
        
        32
        +in.journal:9:1: orderdate: transaction is out of chronological order (date 2026-01-12 before 2026-01-15)

      
        
        33
        +in.journal:17:1: orderdate: transaction is out of chronological order (date 2026-01-20 before 2026-02-01)

      
        
        34
        +in.journal:25:1: orderdate: transaction is out of chronological order (date 2025-12-01 before 2026-03-01)

      
D internal/linter/testdata/parse-error.golden
···
        1
        
        -parse-error.journal:1:1: parse-error: unexpected token AT

      
A internal/linter/testdata/parse-error.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +@@@

      
        
        3
        +

      
        
        4
        +-- expect --

      
        
        5
        +in.journal:1:1: parse-error: unexpected token AT

      
D internal/linter/testdata/unbalanced-transaction.golden
···
        1
        
        -unbalanced-transaction.journal:14:1: unbalanced-transaction: transaction is unbalanced; USD balance is 5

      
        2
        
        -unbalanced-transaction.journal:18:1: unbalanced-transaction: transaction is unbalanced; EUR balance is 5

      
        3
        
        -unbalanced-transaction.journal:18:1: unbalanced-transaction: transaction is unbalanced; USD balance is 5

      
        4
        
        -unbalanced-transaction.journal:32:1: unbalanced-transaction: transaction is unbalanced; USD balance is 10

      
M internal/linter/testdata/unbalanced-transaction.inputinternal/linter/testdata/unbalanced-transaction.txtar
···
        
        1
        +-- in.journal --

      
        1
        2
         2024-01-01 "auto-balance"

      
        2
        3
             expenses:food    10.00 USD

      
        3
        4
             assets:cash

      ···
        36
        37
         2024-01-01 "all-virtual"

      
        37
        38
             (Budget:fun)    10.00 USD

      
        38
        39
             (Budget:fun)   -10.00 USD

      
        
        40
        +

      
        
        41
        +-- expect --

      
        
        42
        +in.journal:14:1: unbalanced-transaction: transaction is unbalanced; USD balance is 5

      
        
        43
        +in.journal:18:1: unbalanced-transaction: transaction is unbalanced; EUR balance is 5

      
        
        44
        +in.journal:18:1: unbalanced-transaction: transaction is unbalanced; USD balance is 5

      
        
        45
        +in.journal:32:1: unbalanced-transaction: transaction is unbalanced; USD balance is 10

      
D internal/linter/testdata/undeclared-account.golden
···
        1
        
        -undeclared-account.journal:5:3: undeclared-account: undeclared account: Assets:Cash

      
        2
        
        -undeclared-account.journal:9:3: undeclared-account: undeclared account: Assets:Cash

      
D internal/linter/testdata/undeclared-account.input
···
        1
        
        -account Expenses:Food

      
        2
        
        -

      
        3
        
        -2026-01-15 * "Lunch"

      
        4
        
        -  Expenses:Food      $10

      
        5
        
        -  Assets:Cash

      
        6
        
        -

      
        7
        
        -2026-01-16 * "Dinner"

      
        8
        
        -  Expenses:Food      $15

      
        9
        
        -  Assets:Cash

      
A internal/linter/testdata/undeclared-account.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +account Expenses:Food

      
        
        3
        +

      
        
        4
        +2026-01-15 * "Lunch"

      
        
        5
        +  Expenses:Food      $10

      
        
        6
        +  Assets:Cash

      
        
        7
        +

      
        
        8
        +2026-01-16 * "Dinner"

      
        
        9
        +  Expenses:Food      $15

      
        
        10
        +  Assets:Cash

      
        
        11
        +

      
        
        12
        +-- expect --

      
        
        13
        +in.journal:5:3: undeclared-account: undeclared account: Assets:Cash

      
        
        14
        +in.journal:9:3: undeclared-account: undeclared account: Assets:Cash

      
D internal/linter/testdata/undeclared-commodity.golden
···
        1
        
        -undeclared-commodity.journal:13:22: undeclared-commodity: undeclared commodity: GBP

      
M internal/linter/testdata/undeclared-commodity.inputinternal/linter/testdata/undeclared-commodity.txtar
···
        
        1
        +-- in.journal --

      
        1
        2
         commodity USD

      
        2
        3
         commodity EUR

      
        3
        4
         

      ···
        12
        13
         2026-01-17 * "Transfer"

      
        13
        14
           Assets:Cash        100 GBP

      
        14
        15
           Assets:Bank

      
        
        16
        +

      
        
        17
        +-- expect --

      
        
        18
        +in.journal:13:22: undeclared-commodity: undeclared commodity: GBP

      
D internal/linter/testdata/unused-account.golden
···
        1
        
        -unused-account.journal:2:9: unused-account: unused account: equity:opening

      
D internal/linter/testdata/unused-account.input
···
        1
        
        -account assets:cash

      
        2
        
        -account equity:opening

      
        3
        
        -account assets:cash

      
        4
        
        -

      
        5
        
        -2024-05-02 * "groceries"

      
        6
        
        -    expenses:food   40.00 USD

      
        7
        
        -    assets:cash

      
A internal/linter/testdata/unused-account.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +account assets:cash

      
        
        3
        +account equity:opening

      
        
        4
        +account assets:cash

      
        
        5
        +

      
        
        6
        +2024-05-02 * "groceries"

      
        
        7
        +    expenses:food   40.00 USD

      
        
        8
        +    assets:cash

      
        
        9
        +

      
        
        10
        +-- expect --

      
        
        11
        +in.journal:2:9: unused-account: unused account: equity:opening

      
M internal/testutil/golden/golden.go
···
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"flag"

      
        
        5
        +	"io/fs"

      
        5
        6
         	"os"

      
        6
        7
         	"path/filepath"

      
        7
        8
         	"strings"

      
        8
        9
         	"testing"

      
        
        10
        +

      
        
        11
        +	"olexsmir.xyz/clerk/internal/testutil/txtar"

      
        9
        12
         )

      
        10
        13
         

      
        11
        14
         var update = flag.Bool("golden.update", false, "update golden files")

      
        12
        15
         

      
        13
        
        -// Load reads testdata/<path>.input. Fails the test if file is not found.

      
        14
        
        -func Load(t testing.TB, path string) []byte {

      
        
        16
        +var normalizer = strings.NewReplacer("/", "__", " ", "_")

      
        
        17
        +

      
        
        18
        +func txtarPath(name string) string {

      
        
        19
        +	return filepath.Join("testdata", normalizer.Replace(name)+".txtar")

      
        
        20
        +}

      
        
        21
        +

      
        
        22
        +// Archive wraps a txtar.Archive with its on-disk path for efficient updates.

      
        
        23
        +type Archive struct {

      
        
        24
        +	*txtar.Archive

      
        
        25
        +	path string

      
        
        26
        +}

      
        
        27
        +

      
        
        28
        +// Read parses testdata/<name>.txtar and returns the archive.

      
        
        29
        +// Fails the test if the file doesn't exist or the archive is empty.

      
        
        30
        +func Read(t testing.TB, name string) *Archive {

      
        15
        31
         	t.Helper()

      
        16
        
        -	data, err := os.ReadFile(filepath.Join("testdata", path+".input"))

      
        
        32
        +	path := txtarPath(name)

      
        
        33
        +	data, err := os.ReadFile(path)

      
        17
        34
         	if err != nil {

      
        18
        
        -		t.Fatalf("loading %s: %v", path, err)

      
        
        35
        +		t.Fatalf("loading %s: %v", name+".txtar", err)

      
        19
        36
         	}

      
        20
        
        -	return data

      
        
        37
        +	a := txtar.Parse(data)

      
        
        38
        +	if len(a.Files) == 0 {

      
        
        39
        +		t.Fatalf("txtar %s: no files", name)

      
        
        40
        +	}

      
        
        41
        +	return &Archive{Archive: a, path: path}

      
        21
        42
         }

      
        22
        43
         

      
        23
        
        -// Assert compares got against testdata/<name>.golden.

      
        24
        
        -func Assert(t testing.TB, name, got string) {

      
        25
        
        -	t.Helper()

      
        
        44
        +func (a *Archive) FS() (fs.FS, error) { return txtar.FS(a.Archive) }

      
        26
        45
         

      
        27
        
        -	normalized := strings.NewReplacer("/", "__", " ", "_").Replace(name)

      
        28
        
        -	path := filepath.Join("testdata", normalized+".golden")

      
        
        46
        +// Assert compares got against the "expect" file in a txtar archive.

      
        
        47
        +// With -golden.update, rewrites the expect section in-place, preserving other files.

      
        
        48
        +func Assert(t testing.TB, a *Archive, got string) {

      
        
        49
        +	t.Helper()

      
        29
        50
         

      
        30
        51
         	if *update {

      
        31
        
        -		if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {

      
        32
        
        -			t.Fatalf("creating testdata dir: %v", err)

      
        
        52
        +		for i, f := range a.Files {

      
        
        53
        +			if f.Name == "expect" {

      
        
        54
        +				a.Files[i].Data = []byte(got)

      
        
        55
        +				goto write

      
        
        56
        +			}

      
        33
        57
         		}

      
        34
        
        -		if err := os.WriteFile(path, []byte(got), 0o644); err != nil {

      
        35
        
        -			t.Fatalf("writing golden file: %v", err)

      
        
        58
        +		a.Files = append(a.Files, txtar.File{Name: "expect", Data: []byte(got)})

      
        
        59
        +	write:

      
        
        60
        +		if err := os.WriteFile(a.path, txtar.Format(a.Archive), 0o644); err != nil {

      
        
        61
        +			t.Fatalf("writing updated txtar: %v", err)

      
        36
        62
         		}

      
        37
        63
         		return

      
        38
        64
         	}

      
        39
        65
         

      
        40
        
        -	golden, err := os.ReadFile(path)

      
        41
        
        -	if os.IsNotExist(err) {

      
        42
        
        -		t.Fatalf("no golden file %s, run with -golden.update:\n%s", path, got)

      
        43
        
        -	}

      
        44
        
        -	if err != nil {

      
        45
        
        -		t.Fatalf("reading golden file: %v", err)

      
        46
        
        -	}

      
        47
        
        -

      
        48
        
        -	if string(golden) != got {

      
        49
        
        -		t.Fatalf("golden mismatch for %s\nwant:\n%s\ngot:\n%s", path, string(golden), got)

      
        
        66
        +	for _, f := range a.Files {

      
        
        67
        +		if f.Name == "expect" {

      
        
        68
        +			if string(f.Data) != got {

      
        
        69
        +				t.Fatalf("golden mismatch for %s\nwant:\n%s\ngot:\n%s", a.path, string(f.Data), got)

      
        
        70
        +			}

      
        
        71
        +			return

      
        
        72
        +		}

      
        50
        73
         	}

      
        
        74
        +	t.Fatalf("txtar %s: no 'expect' file", a.path)

      
        51
        75
         }

      
A internal/testutil/txtar/archive.go
···
        
        1
        +// Copyright 2018 The Go Authors. All rights reserved.

      
        
        2
        +// Use of this source code is governed by a BSD-style

      
        
        3
        +// license that can be found in the LICENSE file.

      
        
        4
        +

      
        
        5
        +// Package txtar implements a trivial text-based file archive format.

      
        
        6
        +//

      
        
        7
        +// The goals for the format are:

      
        
        8
        +//

      
        
        9
        +//   - be trivial enough to create and edit by hand.

      
        
        10
        +//   - be able to store trees of text files describing go command test cases.

      
        
        11
        +//   - diff nicely in git history and code reviews.

      
        
        12
        +//

      
        
        13
        +// Non-goals include being a completely general archive format,

      
        
        14
        +// storing binary data, storing file modes, storing special files like

      
        
        15
        +// symbolic links, and so on.

      
        
        16
        +//

      
        
        17
        +// # Txtar format

      
        
        18
        +//

      
        
        19
        +// A txtar archive is zero or more comment lines and then a sequence of file entries.

      
        
        20
        +// Each file entry begins with a file marker line of the form "-- FILENAME --"

      
        
        21
        +// and is followed by zero or more file content lines making up the file data.

      
        
        22
        +// The comment or file content ends at the next file marker line.

      
        
        23
        +// The file marker line must begin with the three-byte sequence "-- "

      
        
        24
        +// and end with the three-byte sequence " --", but the enclosed

      
        
        25
        +// file name can be surrounding by additional white space,

      
        
        26
        +// all of which is stripped.

      
        
        27
        +//

      
        
        28
        +// If the txtar file is missing a trailing newline on the final line,

      
        
        29
        +// parsers should consider a final newline to be present anyway.

      
        
        30
        +//

      
        
        31
        +// There are no possible syntax errors in a txtar archive.

      
        
        32
        +package txtar

      
        
        33
        +

      
        
        34
        +import (

      
        
        35
        +	"bytes"

      
        
        36
        +	"fmt"

      
        
        37
        +	"os"

      
        
        38
        +	"strings"

      
        
        39
        +)

      
        
        40
        +

      
        
        41
        +// An Archive is a collection of files.

      
        
        42
        +type Archive struct {

      
        
        43
        +	Comment []byte

      
        
        44
        +	Files   []File

      
        
        45
        +}

      
        
        46
        +

      
        
        47
        +// A File is a single file in an archive.

      
        
        48
        +type File struct {

      
        
        49
        +	Name string // name of file ("foo/bar.txt")

      
        
        50
        +	Data []byte // text content of file

      
        
        51
        +}

      
        
        52
        +

      
        
        53
        +// Get returns the data of the first file with the given name, or nil if not found.

      
        
        54
        +func (a *Archive) Get(name string) []byte {

      
        
        55
        +	for _, f := range a.Files {

      
        
        56
        +		if f.Name == name {

      
        
        57
        +			return f.Data

      
        
        58
        +		}

      
        
        59
        +	}

      
        
        60
        +	return nil

      
        
        61
        +}

      
        
        62
        +

      
        
        63
        +// Format returns the serialized form of an Archive.

      
        
        64
        +// It is assumed that the Archive data structure is well-formed:

      
        
        65
        +// a.Comment and all a.File[i].Data contain no file marker lines,

      
        
        66
        +// and all a.File[i].Name is non-empty.

      
        
        67
        +func Format(a *Archive) []byte {

      
        
        68
        +	var buf bytes.Buffer

      
        
        69
        +	buf.Write(fixNL(a.Comment))

      
        
        70
        +	for _, f := range a.Files {

      
        
        71
        +		fmt.Fprintf(&buf, "-- %s --\n", f.Name)

      
        
        72
        +		buf.Write(fixNL(f.Data))

      
        
        73
        +	}

      
        
        74
        +	return buf.Bytes()

      
        
        75
        +}

      
        
        76
        +

      
        
        77
        +// ParseFile parses the named file as an archive.

      
        
        78
        +func ParseFile(file string) (*Archive, error) {

      
        
        79
        +	data, err := os.ReadFile(file)

      
        
        80
        +	if err != nil {

      
        
        81
        +		return nil, err

      
        
        82
        +	}

      
        
        83
        +	return Parse(data), nil

      
        
        84
        +}

      
        
        85
        +

      
        
        86
        +// Parse parses the serialized form of an Archive.

      
        
        87
        +// The returned Archive holds slices of data.

      
        
        88
        +func Parse(data []byte) *Archive {

      
        
        89
        +	a := new(Archive)

      
        
        90
        +	var name string

      
        
        91
        +	a.Comment, name, data = findFileMarker(data)

      
        
        92
        +	for name != "" {

      
        
        93
        +		f := File{name, nil}

      
        
        94
        +		f.Data, name, data = findFileMarker(data)

      
        
        95
        +		a.Files = append(a.Files, f)

      
        
        96
        +	}

      
        
        97
        +	return a

      
        
        98
        +}

      
        
        99
        +

      
        
        100
        +var (

      
        
        101
        +	newlineMarker = []byte("\n-- ")

      
        
        102
        +	marker        = []byte("-- ")

      
        
        103
        +	markerEnd     = []byte(" --")

      
        
        104
        +)

      
        
        105
        +

      
        
        106
        +// findFileMarker finds the next file marker in data,

      
        
        107
        +// extracts the file name, and returns the data before the marker,

      
        
        108
        +// the file name, and the data after the marker.

      
        
        109
        +// If there is no next marker, findFileMarker returns before = fixNL(data), name = "", after = nil.

      
        
        110
        +func findFileMarker(data []byte) (before []byte, name string, after []byte) {

      
        
        111
        +	var i int

      
        
        112
        +	for {

      
        
        113
        +		if name, after = isMarker(data[i:]); name != "" {

      
        
        114
        +			return data[:i], name, after

      
        
        115
        +		}

      
        
        116
        +		j := bytes.Index(data[i:], newlineMarker)

      
        
        117
        +		if j < 0 {

      
        
        118
        +			return fixNL(data), "", nil

      
        
        119
        +		}

      
        
        120
        +		i += j + 1 // positioned at start of new possible marker

      
        
        121
        +	}

      
        
        122
        +}

      
        
        123
        +

      
        
        124
        +// isMarker checks whether data begins with a file marker line.

      
        
        125
        +// If so, it returns the name from the line and the data after the line.

      
        
        126
        +// Otherwise it returns name == "" with an unspecified after.

      
        
        127
        +func isMarker(data []byte) (name string, after []byte) {

      
        
        128
        +	if !bytes.HasPrefix(data, marker) {

      
        
        129
        +		return "", nil

      
        
        130
        +	}

      
        
        131
        +	if i := bytes.IndexByte(data, '\n'); i >= 0 {

      
        
        132
        +		data, after = data[:i], data[i+1:]

      
        
        133
        +		if data[i-1] == '\r' { // handle \r\n line ending

      
        
        134
        +			data = data[:i-1]

      
        
        135
        +		}

      
        
        136
        +	}

      
        
        137
        +	if !bytes.HasSuffix(data, markerEnd) || len(data) < len(marker)+len(markerEnd) {

      
        
        138
        +		return "", nil

      
        
        139
        +	}

      
        
        140
        +	return strings.TrimSpace(string(data[len(marker) : len(data)-len(markerEnd)])), after

      
        
        141
        +}

      
        
        142
        +

      
        
        143
        +// If data is empty or ends in \n, fixNL returns data.

      
        
        144
        +// Otherwise fixNL returns a new slice consisting of data with a final \n added.

      
        
        145
        +func fixNL(data []byte) []byte {

      
        
        146
        +	if len(data) == 0 || data[len(data)-1] == '\n' {

      
        
        147
        +		return data

      
        
        148
        +	}

      
        
        149
        +	d := make([]byte, len(data)+1)

      
        
        150
        +	copy(d, data)

      
        
        151
        +	d[len(data)] = '\n'

      
        
        152
        +	return d

      
        
        153
        +}

      
A internal/testutil/txtar/archive_test.go
···
        
        1
        +// Copyright 2018 The Go Authors. All rights reserved.

      
        
        2
        +// Use of this source code is governed by a BSD-style

      
        
        3
        +// license that can be found in the LICENSE file.

      
        
        4
        +

      
        
        5
        +package txtar

      
        
        6
        +

      
        
        7
        +import (

      
        
        8
        +	"bytes"

      
        
        9
        +	"fmt"

      
        
        10
        +	"reflect"

      
        
        11
        +	"testing"

      
        
        12
        +)

      
        
        13
        +

      
        
        14
        +func TestParse(t *testing.T) {

      
        
        15
        +	tests := []struct {

      
        
        16
        +		name   string

      
        
        17
        +		text   string

      
        
        18
        +		parsed *Archive

      
        
        19
        +	}{

      
        
        20
        +		{

      
        
        21
        +			name: "basic",

      
        
        22
        +			text: `comment1

      
        
        23
        +comment2

      
        
        24
        +-- file1 --

      
        
        25
        +File 1 text.

      
        
        26
        +-- foo ---

      
        
        27
        +More file 1 text.

      
        
        28
        +-- file 2 --

      
        
        29
        +File 2 text.

      
        
        30
        +-- empty --

      
        
        31
        +-- noNL --

      
        
        32
        +hello world

      
        
        33
        +-- empty filename line --

      
        
        34
        +some content

      
        
        35
        +-- --`,

      
        
        36
        +			parsed: &Archive{

      
        
        37
        +				Comment: []byte("comment1\ncomment2\n"),

      
        
        38
        +				Files: []File{

      
        
        39
        +					{"file1", []byte("File 1 text.\n-- foo ---\nMore file 1 text.\n")},

      
        
        40
        +					{"file 2", []byte("File 2 text.\n")},

      
        
        41
        +					{"empty", []byte{}},

      
        
        42
        +					{"noNL", []byte("hello world\n")},

      
        
        43
        +					{"empty filename line", []byte("some content\n-- --\n")},

      
        
        44
        +				},

      
        
        45
        +			},

      
        
        46
        +		},

      
        
        47
        +		{

      
        
        48
        +			name: "crlf",

      
        
        49
        +			text: "comment\r\n-- file --\r\ndata\r\n",

      
        
        50
        +			parsed: &Archive{

      
        
        51
        +				Comment: []byte("comment\r\n"),

      
        
        52
        +				Files:   []File{{"file", []byte("data\r\n")}},

      
        
        53
        +			},

      
        
        54
        +		},

      
        
        55
        +		{

      
        
        56
        +			name: "utf8 and comment",

      
        
        57
        +			text: `# This is a test comment

      
        
        58
        +-- hello.txt --

      
        
        59
        +Hello

      
        
        60
        +-- unicode.txt --

      
        
        61
        +Go语言

      
        
        62
        +`,

      
        
        63
        +			parsed: &Archive{

      
        
        64
        +				Comment: []byte("# This is a test comment\n"),

      
        
        65
        +				Files: []File{

      
        
        66
        +					{"hello.txt", []byte("Hello\n")},

      
        
        67
        +					{"unicode.txt", []byte("Go语言\n")},

      
        
        68
        +				},

      
        
        69
        +			},

      
        
        70
        +		},

      
        
        71
        +	}

      
        
        72
        +	for _, tt := range tests {

      
        
        73
        +		t.Run(tt.name, func(t *testing.T) {

      
        
        74
        +			a := Parse([]byte(tt.text))

      
        
        75
        +			if !reflect.DeepEqual(a, tt.parsed) {

      
        
        76
        +				t.Fatalf("Parse: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))

      
        
        77
        +			}

      
        
        78
        +			text := Format(a)

      
        
        79
        +			a = Parse(text)

      
        
        80
        +			if !reflect.DeepEqual(a, tt.parsed) {

      
        
        81
        +				t.Fatalf("Parse after Format: wrong output:\nhave:\n%s\nwant:\n%s", shortArchive(a), shortArchive(tt.parsed))

      
        
        82
        +			}

      
        
        83
        +		})

      
        
        84
        +	}

      
        
        85
        +}

      
        
        86
        +

      
        
        87
        +func TestFormat(t *testing.T) {

      
        
        88
        +	tests := []struct {

      
        
        89
        +		name   string

      
        
        90
        +		input  *Archive

      
        
        91
        +		wanted string

      
        
        92
        +	}{

      
        
        93
        +		{

      
        
        94
        +			name: "basic",

      
        
        95
        +			input: &Archive{

      
        
        96
        +				Comment: []byte("comment1\ncomment2\n"),

      
        
        97
        +				Files: []File{

      
        
        98
        +					{"file1", []byte("File 1 text.\n-- foo ---\nMore file 1 text.\n")},

      
        
        99
        +					{"file 2", []byte("File 2 text.\n")},

      
        
        100
        +					{"empty", []byte{}},

      
        
        101
        +					{"noNL", []byte("hello world")},

      
        
        102
        +				},

      
        
        103
        +			},

      
        
        104
        +			wanted: `comment1

      
        
        105
        +comment2

      
        
        106
        +-- file1 --

      
        
        107
        +File 1 text.

      
        
        108
        +-- foo ---

      
        
        109
        +More file 1 text.

      
        
        110
        +-- file 2 --

      
        
        111
        +File 2 text.

      
        
        112
        +-- empty --

      
        
        113
        +-- noNL --

      
        
        114
        +hello world

      
        
        115
        +`,

      
        
        116
        +		},

      
        
        117
        +	}

      
        
        118
        +	for _, tt := range tests {

      
        
        119
        +		t.Run(tt.name, func(t *testing.T) {

      
        
        120
        +			result := Format(tt.input)

      
        
        121
        +			if string(result) != tt.wanted {

      
        
        122
        +				t.Errorf("Wrong output. \nGot:\n%s\nWant:\n%s\n", string(result), tt.wanted)

      
        
        123
        +			}

      
        
        124
        +		})

      
        
        125
        +	}

      
        
        126
        +}

      
        
        127
        +

      
        
        128
        +func shortArchive(a *Archive) string {

      
        
        129
        +	var buf bytes.Buffer

      
        
        130
        +	fmt.Fprintf(&buf, "comment: %q\n", a.Comment)

      
        
        131
        +	for _, f := range a.Files {

      
        
        132
        +		fmt.Fprintf(&buf, "file %q: %q\n", f.Name, f.Data)

      
        
        133
        +	}

      
        
        134
        +	return buf.String()

      
        
        135
        +}

      
A internal/testutil/txtar/fs.go
···
        
        1
        +// Copyright 2024 The Go Authors. All rights reserved.

      
        
        2
        +// Use of this source code is governed by a BSD-style

      
        
        3
        +// license that can be found in the LICENSE file.

      
        
        4
        +

      
        
        5
        +package txtar

      
        
        6
        +

      
        
        7
        +import (

      
        
        8
        +	"errors"

      
        
        9
        +	"fmt"

      
        
        10
        +	"io"

      
        
        11
        +	"io/fs"

      
        
        12
        +	"path"

      
        
        13
        +	"slices"

      
        
        14
        +	"time"

      
        
        15
        +)

      
        
        16
        +

      
        
        17
        +// FS returns the file system form of an Archive.

      
        
        18
        +// It returns an error if any of the file names in the archive

      
        
        19
        +// are not valid file system names.

      
        
        20
        +// The archive must not be modified while the FS is in use.

      
        
        21
        +//

      
        
        22
        +// If the file system detects that it has been modified, calls to the

      
        
        23
        +// file system return an ErrModified error.

      
        
        24
        +func FS(a *Archive) (fs.FS, error) {

      
        
        25
        +	// Create a filesystem with a root directory.

      
        
        26
        +	root := &node{fileinfo: fileinfo{path: ".", mode: readOnlyDir}}

      
        
        27
        +	fsys := &filesystem{a, map[string]*node{root.path: root}}

      
        
        28
        +

      
        
        29
        +	if err := initFiles(fsys); err != nil {

      
        
        30
        +		return nil, fmt.Errorf("cannot create fs.FS from txtar.Archive: %s", err)

      
        
        31
        +	}

      
        
        32
        +	return fsys, nil

      
        
        33
        +}

      
        
        34
        +

      
        
        35
        +const (

      
        
        36
        +	readOnly    fs.FileMode = 0o444 // read only mode

      
        
        37
        +	readOnlyDir             = readOnly | fs.ModeDir

      
        
        38
        +)

      
        
        39
        +

      
        
        40
        +// ErrModified indicates that file system returned by FS

      
        
        41
        +// noticed that the underlying archive has been modified

      
        
        42
        +// since the call to FS. Detection of modification is best effort,

      
        
        43
        +// to help diagnose misuse of the API, and is not guaranteed.

      
        
        44
        +var ErrModified error = errors.New("txtar.Archive has been modified during txtar.FS")

      
        
        45
        +

      
        
        46
        +// A filesystem is a simple in-memory file system for txtar archives,

      
        
        47
        +// represented as a map from valid path names to information about the

      
        
        48
        +// files or directories they represent.

      
        
        49
        +//

      
        
        50
        +// File system operations are read only. Modifications to the underlying

      
        
        51
        +// *Archive may race. To help prevent this, the filesystem tries

      
        
        52
        +// to detect modification during Open and return ErrModified if it

      
        
        53
        +// is able to detect a modification.

      
        
        54
        +type filesystem struct {

      
        
        55
        +	ar    *Archive

      
        
        56
        +	nodes map[string]*node

      
        
        57
        +}

      
        
        58
        +

      
        
        59
        +// node is a file or directory in the tree of a filesystem.

      
        
        60
        +type node struct {

      
        
        61
        +	fileinfo               // fs.FileInfo and fs.DirEntry implementation

      
        
        62
        +	idx      int           // index into ar.Files (for files)

      
        
        63
        +	entries  []fs.DirEntry // subdirectories and files (for directories)

      
        
        64
        +}

      
        
        65
        +

      
        
        66
        +var (

      
        
        67
        +	_ fs.FS       = (*filesystem)(nil)

      
        
        68
        +	_ fs.DirEntry = (*node)(nil)

      
        
        69
        +)

      
        
        70
        +

      
        
        71
        +// initFiles initializes fsys from fsys.ar.Files. Returns an error if there are any

      
        
        72
        +// invalid file names or collisions between file or directories.

      
        
        73
        +func initFiles(fsys *filesystem) error {

      
        
        74
        +	for idx, file := range fsys.ar.Files {

      
        
        75
        +		name := file.Name

      
        
        76
        +		if !fs.ValidPath(name) {

      
        
        77
        +			return fmt.Errorf("file %q is an invalid path", name)

      
        
        78
        +		}

      
        
        79
        +

      
        
        80
        +		n := &node{idx: idx, fileinfo: fileinfo{path: name, size: len(file.Data), mode: readOnly}}

      
        
        81
        +		if err := insert(fsys, n); err != nil {

      
        
        82
        +			return err

      
        
        83
        +		}

      
        
        84
        +	}

      
        
        85
        +	return nil

      
        
        86
        +}

      
        
        87
        +

      
        
        88
        +// insert adds node n as an entry to its parent directory within the filesystem.

      
        
        89
        +func insert(fsys *filesystem, n *node) error {

      
        
        90
        +	if m := fsys.nodes[n.path]; m != nil {

      
        
        91
        +		return fmt.Errorf("duplicate path %q", n.path)

      
        
        92
        +	}

      
        
        93
        +	fsys.nodes[n.path] = n

      
        
        94
        +

      
        
        95
        +	// fsys.nodes contains "." to prevent infinite loops.

      
        
        96
        +	parent, err := directory(fsys, path.Dir(n.path))

      
        
        97
        +	if err != nil {

      
        
        98
        +		return err

      
        
        99
        +	}

      
        
        100
        +	parent.entries = append(parent.entries, n)

      
        
        101
        +	return nil

      
        
        102
        +}

      
        
        103
        +

      
        
        104
        +// directory returns the directory node with the path dir and lazily-creates it

      
        
        105
        +// if it does not exist.

      
        
        106
        +func directory(fsys *filesystem, dir string) (*node, error) {

      
        
        107
        +	if m := fsys.nodes[dir]; m != nil && m.IsDir() {

      
        
        108
        +		return m, nil // pre-existing directory

      
        
        109
        +	}

      
        
        110
        +

      
        
        111
        +	n := &node{fileinfo: fileinfo{path: dir, mode: readOnlyDir}}

      
        
        112
        +	if err := insert(fsys, n); err != nil {

      
        
        113
        +		return nil, err

      
        
        114
        +	}

      
        
        115
        +	return n, nil

      
        
        116
        +}

      
        
        117
        +

      
        
        118
        +// dataOf returns the data associated with the file t.

      
        
        119
        +// May return ErrModified if fsys.ar has been modified.

      
        
        120
        +func dataOf(fsys *filesystem, n *node) ([]byte, error) {

      
        
        121
        +	if n.idx >= len(fsys.ar.Files) {

      
        
        122
        +		return nil, ErrModified

      
        
        123
        +	}

      
        
        124
        +

      
        
        125
        +	f := fsys.ar.Files[n.idx]

      
        
        126
        +	if f.Name != n.path || len(f.Data) != n.size {

      
        
        127
        +		return nil, ErrModified

      
        
        128
        +	}

      
        
        129
        +	return f.Data, nil

      
        
        130
        +}

      
        
        131
        +

      
        
        132
        +func (fsys *filesystem) Open(name string) (fs.File, error) {

      
        
        133
        +	if !fs.ValidPath(name) {

      
        
        134
        +		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}

      
        
        135
        +	}

      
        
        136
        +

      
        
        137
        +	n := fsys.nodes[name]

      
        
        138
        +	switch {

      
        
        139
        +	case n == nil:

      
        
        140
        +		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}

      
        
        141
        +	case n.IsDir():

      
        
        142
        +		return &openDir{fileinfo: n.fileinfo, entries: n.entries}, nil

      
        
        143
        +	default:

      
        
        144
        +		data, err := dataOf(fsys, n)

      
        
        145
        +		if err != nil {

      
        
        146
        +			return nil, err

      
        
        147
        +		}

      
        
        148
        +		return &openFile{fileinfo: n.fileinfo, data: data}, nil

      
        
        149
        +	}

      
        
        150
        +}

      
        
        151
        +

      
        
        152
        +func (fsys *filesystem) ReadFile(name string) ([]byte, error) {

      
        
        153
        +	file, err := fsys.Open(name)

      
        
        154
        +	if err != nil {

      
        
        155
        +		return nil, err

      
        
        156
        +	}

      
        
        157
        +	if file, ok := file.(*openFile); ok {

      
        
        158
        +		return slices.Clone(file.data), nil

      
        
        159
        +	}

      
        
        160
        +	return nil, &fs.PathError{Op: "read", Path: name, Err: fs.ErrInvalid}

      
        
        161
        +}

      
        
        162
        +

      
        
        163
        +// A fileinfo implements fs.FileInfo and fs.DirEntry for a given archive file.

      
        
        164
        +type fileinfo struct {

      
        
        165
        +	path string // unique path to the file or directory within a filesystem

      
        
        166
        +	size int

      
        
        167
        +	mode fs.FileMode

      
        
        168
        +}

      
        
        169
        +

      
        
        170
        +var (

      
        
        171
        +	_ fs.FileInfo = (*fileinfo)(nil)

      
        
        172
        +	_ fs.DirEntry = (*fileinfo)(nil)

      
        
        173
        +)

      
        
        174
        +

      
        
        175
        +func (i *fileinfo) Name() string               { return path.Base(i.path) }

      
        
        176
        +func (i *fileinfo) Size() int64                { return int64(i.size) }

      
        
        177
        +func (i *fileinfo) Mode() fs.FileMode          { return i.mode }

      
        
        178
        +func (i *fileinfo) Type() fs.FileMode          { return i.mode.Type() }

      
        
        179
        +func (i *fileinfo) ModTime() time.Time         { return time.Time{} }

      
        
        180
        +func (i *fileinfo) IsDir() bool                { return i.mode&fs.ModeDir != 0 }

      
        
        181
        +func (i *fileinfo) Sys() any                   { return nil }

      
        
        182
        +func (i *fileinfo) Info() (fs.FileInfo, error) { return i, nil }

      
        
        183
        +

      
        
        184
        +// An openFile is a regular (non-directory) fs.File open for reading.

      
        
        185
        +type openFile struct {

      
        
        186
        +	fileinfo

      
        
        187
        +	data   []byte

      
        
        188
        +	offset int64

      
        
        189
        +}

      
        
        190
        +

      
        
        191
        +var _ fs.File = (*openFile)(nil)

      
        
        192
        +

      
        
        193
        +func (f *openFile) Stat() (fs.FileInfo, error) { return &f.fileinfo, nil }

      
        
        194
        +func (f *openFile) Close() error               { return nil }

      
        
        195
        +func (f *openFile) Read(b []byte) (int, error) {

      
        
        196
        +	if f.offset >= int64(len(f.data)) {

      
        
        197
        +		return 0, io.EOF

      
        
        198
        +	}

      
        
        199
        +	if f.offset < 0 {

      
        
        200
        +		return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}

      
        
        201
        +	}

      
        
        202
        +	n := copy(b, f.data[f.offset:])

      
        
        203
        +	f.offset += int64(n)

      
        
        204
        +	return n, nil

      
        
        205
        +}

      
        
        206
        +

      
        
        207
        +func (f *openFile) Seek(offset int64, whence int) (int64, error) {

      
        
        208
        +	switch whence {

      
        
        209
        +	case 0:

      
        
        210
        +		// offset += 0

      
        
        211
        +	case 1:

      
        
        212
        +		offset += f.offset

      
        
        213
        +	case 2:

      
        
        214
        +		offset += int64(len(f.data))

      
        
        215
        +	}

      
        
        216
        +	if offset < 0 || offset > int64(len(f.data)) {

      
        
        217
        +		return 0, &fs.PathError{Op: "seek", Path: f.path, Err: fs.ErrInvalid}

      
        
        218
        +	}

      
        
        219
        +	f.offset = offset

      
        
        220
        +	return offset, nil

      
        
        221
        +}

      
        
        222
        +

      
        
        223
        +func (f *openFile) ReadAt(b []byte, offset int64) (int, error) {

      
        
        224
        +	if offset < 0 || offset > int64(len(f.data)) {

      
        
        225
        +		return 0, &fs.PathError{Op: "read", Path: f.path, Err: fs.ErrInvalid}

      
        
        226
        +	}

      
        
        227
        +	n := copy(b, f.data[offset:])

      
        
        228
        +	if n < len(b) {

      
        
        229
        +		return n, io.EOF

      
        
        230
        +	}

      
        
        231
        +	return n, nil

      
        
        232
        +}

      
        
        233
        +

      
        
        234
        +// A openDir is a directory fs.File (so also an fs.ReadDirFile) open for reading.

      
        
        235
        +type openDir struct {

      
        
        236
        +	fileinfo

      
        
        237
        +	entries []fs.DirEntry

      
        
        238
        +	offset  int

      
        
        239
        +}

      
        
        240
        +

      
        
        241
        +var _ fs.ReadDirFile = (*openDir)(nil)

      
        
        242
        +

      
        
        243
        +func (d *openDir) Stat() (fs.FileInfo, error) { return &d.fileinfo, nil }

      
        
        244
        +func (d *openDir) Close() error               { return nil }

      
        
        245
        +func (d *openDir) Read(b []byte) (int, error) {

      
        
        246
        +	return 0, &fs.PathError{Op: "read", Path: d.path, Err: fs.ErrInvalid}

      
        
        247
        +}

      
        
        248
        +

      
        
        249
        +func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {

      
        
        250
        +	n := len(d.entries) - d.offset

      
        
        251
        +	if n == 0 && count > 0 {

      
        
        252
        +		return nil, io.EOF

      
        
        253
        +	}

      
        
        254
        +	if count > 0 && n > count {

      
        
        255
        +		n = count

      
        
        256
        +	}

      
        
        257
        +	list := make([]fs.DirEntry, n)

      
        
        258
        +	copy(list, d.entries[d.offset:d.offset+n])

      
        
        259
        +	d.offset += n

      
        
        260
        +	return list, nil

      
        
        261
        +}

      
A internal/testutil/txtar/fs_test.go
···
        
        1
        +// Copyright 2024 The Go Authors. All rights reserved.

      
        
        2
        +// Use of this source code is governed by a BSD-style

      
        
        3
        +// license that can be found in the LICENSE file.

      
        
        4
        +

      
        
        5
        +package txtar_test

      
        
        6
        +

      
        
        7
        +import (

      
        
        8
        +	"io/fs"

      
        
        9
        +	"strings"

      
        
        10
        +	"testing"

      
        
        11
        +	"testing/fstest"

      
        
        12
        +

      
        
        13
        +	"olexsmir.xyz/clerk/internal/testutil/txtar"

      
        
        14
        +)

      
        
        15
        +

      
        
        16
        +func TestFS(t *testing.T) {

      
        
        17
        +	fstestcases := []struct{ name, input, files string }{

      
        
        18
        +		{

      
        
        19
        +			name:  "empty",

      
        
        20
        +			input: ``,

      
        
        21
        +			files: "",

      
        
        22
        +		},

      
        
        23
        +		{

      
        
        24
        +			name: "one",

      
        
        25
        +			input: `

      
        
        26
        +-- one.txt --

      
        
        27
        +one

      
        
        28
        +`,

      
        
        29
        +			files: "one.txt",

      
        
        30
        +		},

      
        
        31
        +		{

      
        
        32
        +			name: "two",

      
        
        33
        +			input: `

      
        
        34
        +-- one.txt --

      
        
        35
        +one

      
        
        36
        +-- two.txt --

      
        
        37
        +two

      
        
        38
        +`,

      
        
        39
        +			files: "one.txt two.txt",

      
        
        40
        +		},

      
        
        41
        +		{

      
        
        42
        +			name: "subdirectories",

      
        
        43
        +			input: `

      
        
        44
        +-- one.txt --

      
        
        45
        +one

      
        
        46
        +-- 2/two.txt --

      
        
        47
        +two

      
        
        48
        +-- 2/3/three.txt --

      
        
        49
        +three

      
        
        50
        +-- 4/four.txt --

      
        
        51
        +four

      
        
        52
        +`,

      
        
        53
        +			files: "one.txt 2/two.txt 2/3/three.txt 4/four.txt",

      
        
        54
        +		},

      
        
        55
        +	}

      
        
        56
        +

      
        
        57
        +	for _, tc := range fstestcases {

      
        
        58
        +		t.Run(tc.name, func(t *testing.T) {

      
        
        59
        +			a := txtar.Parse([]byte(tc.input))

      
        
        60
        +			fsys, err := txtar.FS(a)

      
        
        61
        +			if err != nil {

      
        
        62
        +				t.Fatal(err)

      
        
        63
        +			}

      
        
        64
        +

      
        
        65
        +			files := strings.Fields(tc.files)

      
        
        66
        +			if err := fstest.TestFS(fsys, files...); err != nil {

      
        
        67
        +				t.Fatal(err)

      
        
        68
        +			}

      
        
        69
        +

      
        
        70
        +			for _, f := range a.Files {

      
        
        71
        +				b, err := fs.ReadFile(fsys, f.Name)

      
        
        72
        +				if err != nil {

      
        
        73
        +					t.Errorf("ReadFile(%q) failed with error: %v", f.Name, err)

      
        
        74
        +				}

      
        
        75
        +				if got, want := string(b), string(f.Data); got != want {

      
        
        76
        +					t.Errorf("ReadFile(%q) = %q; want %q", f.Name, got, want)

      
        
        77
        +				}

      
        
        78
        +			}

      
        
        79
        +		})

      
        
        80
        +	}

      
        
        81
        +}

      
        
        82
        +

      
        
        83
        +func TestInvalid(t *testing.T) {

      
        
        84
        +	invalidtestcases := []struct{ name, want, input string }{

      
        
        85
        +		{"unclean file names", "invalid path", `

      
        
        86
        +-- 1/../one.txt --

      
        
        87
        +one

      
        
        88
        +-- 2/sub/../two.txt --

      
        
        89
        +two

      
        
        90
        +`},

      
        
        91
        +		{"duplicate name", `cannot create fs.FS from txtar.Archive: duplicate path "1/2/one.txt"`, `

      
        
        92
        +-- 1/2/one.txt --

      
        
        93
        +one

      
        
        94
        +-- 1/2/one.txt --

      
        
        95
        +two

      
        
        96
        +`},

      
        
        97
        +		{"file conflicts with directory", `duplicate path "1/2"`, `

      
        
        98
        +-- 1/2 --

      
        
        99
        +one

      
        
        100
        +-- 1/2/one.txt --

      
        
        101
        +two

      
        
        102
        +`},

      
        
        103
        +	}

      
        
        104
        +

      
        
        105
        +	for _, tc := range invalidtestcases {

      
        
        106
        +		t.Run(tc.name, func(t *testing.T) {

      
        
        107
        +			a := txtar.Parse([]byte(tc.input))

      
        
        108
        +			_, err := txtar.FS(a)

      
        
        109
        +			if err == nil {

      
        
        110
        +				t.Fatal("txtar.FS(...) succeeded; expected an error")

      
        
        111
        +			}

      
        
        112
        +			if got := err.Error(); !strings.Contains(got, tc.want) || tc.want == "" {

      
        
        113
        +				t.Errorf("txtar.FS(...) got error %q; want %q", got, tc.want)

      
        
        114
        +			}

      
        
        115
        +		})

      
        
        116
        +	}

      
        
        117
        +}

      
        
        118
        +

      
        
        119
        +func TestModified(t *testing.T) {

      
        
        120
        +	const input = `

      
        
        121
        +-- one.txt --

      
        
        122
        +one

      
        
        123
        +`

      
        
        124
        +	for _, mod := range []func(a *txtar.Archive){

      
        
        125
        +		func(a *txtar.Archive) { a.Files[0].Data = []byte("other") },

      
        
        126
        +		func(a *txtar.Archive) { a.Files[0].Name = "other" },

      
        
        127
        +		func(a *txtar.Archive) { a.Files = nil },

      
        
        128
        +	} {

      
        
        129
        +		a := txtar.Parse([]byte(input))

      
        
        130
        +		if n := len(a.Files); n != 1 {

      
        
        131
        +			t.Fatalf("txtar.Parse(%q) got %d files; expected 1", input, n)

      
        
        132
        +		}

      
        
        133
        +

      
        
        134
        +		fsys, err := txtar.FS(a)

      
        
        135
        +		if err != nil {

      
        
        136
        +			t.Fatal(err)

      
        
        137
        +		}

      
        
        138
        +

      
        
        139
        +		// Confirm we can open "one.txt".

      
        
        140
        +		_, err = fsys.Open("one.txt")

      
        
        141
        +		if err != nil {

      
        
        142
        +			t.Fatal(err)

      
        
        143
        +		}

      
        
        144
        +		// Modify a to get ErrModified when opening "one.txt".

      
        
        145
        +		mod(a)

      
        
        146
        +

      
        
        147
        +		_, err = fsys.Open("one.txt")

      
        
        148
        +		if err != txtar.ErrModified {

      
        
        149
        +			t.Errorf("Open(%q) got error %s; want ErrModified", "one.txt", err)

      
        
        150
        +		}

      
        
        151
        +	}

      
        
        152
        +}

      
        
        153
        +

      
        
        154
        +func TestReadFile(t *testing.T) {

      
        
        155
        +	const input = `

      
        
        156
        +-- 1/one.txt --

      
        
        157
        +one

      
        
        158
        +`

      
        
        159
        +	a := txtar.Parse([]byte(input))

      
        
        160
        +	fsys, err := txtar.FS(a)

      
        
        161
        +	if err != nil {

      
        
        162
        +		t.Fatal(err)

      
        
        163
        +	}

      
        
        164
        +	readfs := fsys.(fs.ReadFileFS)

      
        
        165
        +	_, err = readfs.ReadFile("1")

      
        
        166
        +	if err == nil {

      
        
        167
        +		t.Errorf("ReadFile(%q) succeeded; expected an error when reading a directory", "1")

      
        
        168
        +	}

      
        
        169
        +

      
        
        170
        +	content, err := readfs.ReadFile("1/one.txt")

      
        
        171
        +	if err != nil {

      
        
        172
        +		t.Fatal(err)

      
        
        173
        +	}

      
        
        174
        +	want := "one\n"

      
        
        175
        +	if got := string(content); want != got {

      
        
        176
        +		t.Errorf("ReadFile(%q) = %q; want %q", "1/one.txt", got, want)

      
        
        177
        +	}

      
        
        178
        +}

      
M journal/lexer/lexer_test.go
···
        8
        8
         )

      
        9
        9
         

      
        10
        10
         func TestLexer(t *testing.T) {

      
        11
        
        -	tests := []struct {

      
        12
        
        -		name  string

      
        13
        
        -		input string

      
        14
        
        -	}{

      
        15
        
        -		{"simple transaction", `2024/01/01 groceries

      
        16
        
        -    expenses:food  $10.00

      
        17
        
        -    assets:checking

      
        18
        
        -`},

      
        19
        
        -		{"transaction, accounts with uppercase latters", `

      
        20
        
        -2011/01/27 Book Store

      
        21
        
        -    Expenses:Books                       $20.00

      
        22
        
        -    Liabilities:MasterCard

      
        23
        
        -`},

      
        24
        
        -		{"cleared transaction", `2024/01/01 * groceries

      
        25
        
        -    expenses:food  $10.00

      
        26
        
        -    assets:checking

      
        27
        
        -`},

      
        28
        
        -		{"automated transaction", `= ^income

      
        29
        
        -    (liabilities:tax)  *.33

      
        30
        
        -

      
        31
        
        -= expenses:gifts

      
        32
        
        -    budget:gifts  (amount * -1)

      
        33
        
        -`},

      
        34
        
        -		{"transaction with code", `2024/01/01 (123) groceries

      
        35
        
        -    expenses:food  $10.00

      
        36
        
        -    assets:checking

      
        37
        
        -

      
        38
        
        -2024/01/02 (ABS) groceries

      
        39
        
        -`},

      
        40
        
        -		{"transaction with virtual accounts", `2024/01/01 * groceries

      
        41
        
        -	(virtual:account)  1 PESO

      
        42
        
        -	[some thing:else]   5 PESO

      
        43
        
        -`},

      
        44
        
        -		{"transaction with unicode commodity symbols", `2024/01/01 groceries

      
        45
        
        -    expenses:food  €10.00

      
        46
        
        -    expenses:food  £5.00

      
        47
        
        -    expenses:food  ₹700.00

      
        48
        
        -    expenses:food  40.00 гривні

      
        49
        
        -    assets:cash

      
        50
        
        -`},

      
        51
        
        -		{"special chars in description", `

      
        52
        
        -2024/01/01 * groceries + water| 1 + 2

      
        53
        
        -2024/01/01 groceries * water | 1 * 2

      
        54
        
        -2024/01/01 groceries ! water | 1 ! 2

      
        55
        
        -2024/01/01 groceries # water | 1 # 2

      
        56
        
        -2024/01/01 groceries % water | 1 % 2

      
        57
        
        -2024/01/01 groceries ^ water | 1 ^ 2

      
        58
        
        -2024/01/01 groceries & water | 1 & 2

      
        59
        
        -2024/01/01 groceries ( water | 1 ( 2

      
        60
        
        -2024/01/01 groceries ) water | 1 ) 2

      
        61
        
        -2024/01/01 groceries [ water | 1 [ 2

      
        62
        
        -2024/01/01 groceries ] water | 1 ] 2

      
        63
        
        -2024/01/01 groceries { water | 1 { 2

      
        64
        
        -2024/01/01 groceries } water | 1 } 2

      
        65
        
        -2026-06-07 * payee !one | something *important*

      
        66
        
        -    expenses:food  40.00

      
        67
        
        -    assets:cash

      
        68
        
        -`},

      
        69
        
        -		{"date with secondary", `2024/01/01=2024/01/02 groceries`},

      
        70
        
        -		{"better date", `2024-01-02`},

      
        71
        
        -		{"comment line", `; this is a comment`},

      
        72
        
        -		{"star comment", `* this is a comment`},

      
        73
        
        -		{"hash comment", `# this is a comment`},

      
        74
        
        -		{"account directive", `account expenses:food`},

      
        75
        
        -		{"commodity directive", `commodity 1,000.00 UAH`},

      
        76
        
        -		{"market price directive", "P 2024-01-01 USD 40.50 UAH\n"},

      
        77
        
        -		{"market price directive with time", "P 2024-01-01 12:00:00 USD 40.50 UAH\n"},

      
        78
        
        -		{"inline comment", `2024/01/01 groceries ; a note`},

      
        79
        
        -		{"empty", ``},

      
        80
        
        -		{"blank lines", "\n\n\n"},

      
        81
        
        -		{"comment block directive", "comment\ncontent\nend\n"},

      
        82
        
        -		{"comment block directive without end", "comment\ncontent\n"},

      
        
        11
        +	tests := []string{

      
        
        12
        +		"account directive",

      
        
        13
        +		"automated transaction",

      
        
        14
        +		"better date",

      
        
        15
        +		"blank lines",

      
        
        16
        +		"cleared transaction",

      
        
        17
        +		"comment block directive without end",

      
        
        18
        +		"comment block directive",

      
        
        19
        +		"comment line",

      
        
        20
        +		"commodity directive",

      
        
        21
        +		"date with secondary",

      
        
        22
        +		"empty",

      
        
        23
        +		"hash comment",

      
        
        24
        +		"inline comment",

      
        
        25
        +		"market price directive with time",

      
        
        26
        +		"market price directive",

      
        
        27
        +		"simple transaction",

      
        
        28
        +		"special chars in description",

      
        
        29
        +		"star comment",

      
        
        30
        +		"transaction with code",

      
        
        31
        +		"transaction with unicode commodity symbols",

      
        
        32
        +		"transaction with virtual accounts",

      
        
        33
        +		"transaction, accounts with uppercase latters",

      
        83
        34
         	}

      
        84
        35
         	for _, tt := range tests {

      
        85
        
        -		t.Run(tt.name, func(t *testing.T) {

      
        86
        
        -			l := New("j", []byte(tt.input))

      
        87
        
        -			golden.Assert(t, tt.name, l.Dump())

      
        
        36
        +		t.Run(tt, func(t *testing.T) {

      
        
        37
        +			a := golden.Read(t, tt)

      
        
        38
        +			golden.Assert(t, a, New("j", a.Get("input")).Dump())

      
        88
        39
         		})

      
        89
        40
         	}

      
        90
        41
         }

      
        91
        42
         

      
        92
        
        -// token category bounds, ensures fuzzer never sees out-of-range token types.

      
        93
        
        -const maxKnownTokenType = token.N

      
        
        43
        +func FuzzLexer(f *testing.F) {

      
        
        44
        +	const maxKnownTokenType = token.C // ensures fuzzer never sees out-of-range token types

      
        94
        45
         

      
        95
        
        -func FuzzLexer(f *testing.F) {

      
        96
        46
         	f.Add([]byte("2024/01/01 groceries\n  expenses:food  $10.00\n  assets:checking\n"))

      
        97
        47
         	f.Add([]byte("2024/01/01 * groceries\n  expenses:food  $10.00\n  assets:checking\n"))

      
        98
        48
         	f.Add([]byte("2024/01/01 ! groceries\n  expenses:food  $10.00\n  assets:checking\n"))

      
D journal/lexer/testdata/account_directive.golden
···
        1
        
        -ACCOUNT      "account"            1:1-1:8

      
        2
        
        -WHITESPACE   " "                  1:8-1:9

      
        3
        
        -TEXT         "expenses"           1:9-1:17

      
        4
        
        -COLON        ":"                  1:17-1:18

      
        5
        
        -TEXT         "food"               1:18-1:22

      
        6
        
        -NEWLINE      "\n"                 1:22-1:23

      
        7
        
        -EOF          ""                   1:23-1:23

      
A journal/lexer/testdata/account_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +account expenses:food

      
        
        3
        +-- expect --

      
        
        4
        +ACCOUNT      "account"            1:1-1:8

      
        
        5
        +WHITESPACE   " "                  1:8-1:9

      
        
        6
        +TEXT         "expenses"           1:9-1:17

      
        
        7
        +COLON        ":"                  1:17-1:18

      
        
        8
        +TEXT         "food"               1:18-2:0

      
        
        9
        +NEWLINE      "\n"                 2:0-2:1

      
        
        10
        +EOF          ""                   2:1-2:1

      
D journal/lexer/testdata/account_with_colon.golden
···
        1
        
        -ACCOUNT      "account"            1:1-1:8

      
        2
        
        -WHITESPACE   " "                  1:8-1:9

      
        3
        
        -TEXT         "income"              1:9-1:15

      
        4
        
        -COLON        ":"                  1:15-1:16

      
        5
        
        -TEXT         "salary"              1:16-1:22

      
        6
        
        -NEWLINE      "\n"                 1:22-1:23

      
        7
        
        -EOF          ""                   1:23-1:23

      
M journal/lexer/testdata/automated_transaction.goldenjournal/lexer/testdata/automated_transaction.txtar
···
        
        1
        +-- input --

      
        
        2
        += ^income

      
        
        3
        +    (liabilities:tax)  *.33

      
        
        4
        +

      
        
        5
        += expenses:gifts

      
        
        6
        +    budget:gifts  (amount * -1)

      
        
        7
        +-- expect --

      
        1
        8
         EQ           "="                  1:1-1:2

      
        2
        9
         WHITESPACE   " "                  1:2-1:3

      
        3
        10
         TEXT         "^income"            1:3-2:0

      
D journal/lexer/testdata/better_date.golden
···
        1
        
        -DATE         "2024-01-02"         1:1-1:11

      
        2
        
        -EOF          ""                   1:11-1:11

      
A journal/lexer/testdata/better_date.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-02

      
        
        3
        +-- expect --

      
        
        4
        +DATE         "2024-01-02"         1:1-2:0

      
        
        5
        +NEWLINE      "\n"                 2:0-2:1

      
        
        6
        +EOF          ""                   2:1-2:1

      
D journal/lexer/testdata/blank_lines.golden
···
        1
        
        -NEWLINE      "\n"                 2:0-3:0

      
        2
        
        -NEWLINE      "\n"                 3:0-4:0

      
        3
        
        -NEWLINE      "\n"                 4:0-4:1

      
        4
        
        -EOF          ""                   4:1-4:1

      
A journal/lexer/testdata/blank_lines.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +

      
        
        4
        +-- expect --

      
        
        5
        +NEWLINE      "\n"                 2:0-3:0

      
        
        6
        +NEWLINE      "\n"                 3:0-3:1

      
        
        7
        +EOF          ""                   3:1-3:1

      
M journal/lexer/testdata/cleared_transaction.goldenjournal/lexer/testdata/cleared_transaction.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 * groceries

      
        
        3
        +    expenses:food  $10.00

      
        
        4
        +    assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         DATE         "2024/01/01"         1:1-1:11

      
        2
        7
         WHITESPACE   " "                  1:11-1:12

      
        3
        8
         STAR         "*"                  1:12-1:13

      
M journal/lexer/testdata/comment_block_directive.goldenjournal/lexer/testdata/comment_block_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment

      
        
        3
        +content

      
        
        4
        +end

      
        
        5
        +-- expect --

      
        1
        6
         COMMENTKW    "comment"            1:1-2:0

      
        2
        7
         NEWLINE      "\n"                 2:0-2:1

      
        3
        8
         TEXT         "content"            2:1-3:0

      
M journal/lexer/testdata/comment_block_directive_without_end.goldenjournal/lexer/testdata/comment_block_directive_without_end.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment

      
        
        3
        +content

      
        
        4
        +-- expect --

      
        1
        5
         COMMENTKW    "comment"            1:1-2:0

      
        2
        6
         NEWLINE      "\n"                 2:0-2:1

      
        3
        7
         TEXT         "content"            2:1-3:0

      
D journal/lexer/testdata/comment_line.golden
···
        1
        
        -SEMICOLON    ";"                  1:1-1:2

      
        2
        
        -TEXT         "this is a comment"  1:3-1:20

      
        3
        
        -NEWLINE      "\n"                 1:20-1:21

      
        4
        
        -EOF          ""                   1:21-1:21

      
A journal/lexer/testdata/comment_line.txtar
···
        
        1
        +-- input --

      
        
        2
        +; this is a comment

      
        
        3
        +-- expect --

      
        
        4
        +SEMICOLON    ";"                  1:1-1:2

      
        
        5
        +TEXT         "this is a comment"  1:3-2:0

      
        
        6
        +NEWLINE      "\n"                 2:0-2:1

      
        
        7
        +EOF          ""                   2:1-2:1

      
D journal/lexer/testdata/commodity_directive.golden
···
        1
        
        -COMMODITY    "commodity"          1:1-1:10

      
        2
        
        -WHITESPACE   " "                  1:10-1:11

      
        3
        
        -DECIMAL      "1,000.00"           1:11-1:19

      
        4
        
        -WHITESPACE   " "                  1:19-1:20

      
        5
        
        -COMMODITYMARK "UAH"                1:20-1:23

      
        6
        
        -NEWLINE      "\n"                 1:23-1:24

      
        7
        
        -EOF          ""                   1:24-1:24

      
A journal/lexer/testdata/commodity_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity 1,000.00 UAH

      
        
        3
        +-- expect --

      
        
        4
        +COMMODITY    "commodity"          1:1-1:10

      
        
        5
        +WHITESPACE   " "                  1:10-1:11

      
        
        6
        +DECIMAL      "1,000.00"           1:11-1:19

      
        
        7
        +WHITESPACE   " "                  1:19-1:20

      
        
        8
        +COMMODITYMARK "UAH"                1:20-2:0

      
        
        9
        +NEWLINE      "\n"                 2:0-2:1

      
        
        10
        +EOF          ""                   2:1-2:1

      
D journal/lexer/testdata/date_with_secondary.golden
···
        1
        
        -DATE         "2024/01/01"         1:1-1:11

      
        2
        
        -EQ           "="                  1:11-1:12

      
        3
        
        -DATE         "2024/01/02"         1:12-1:22

      
        4
        
        -WHITESPACE   " "                  1:22-1:23

      
        5
        
        -TEXT         "groceries"          1:23-1:32

      
        6
        
        -EOF          ""                   1:32-1:32

      
A journal/lexer/testdata/date_with_secondary.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01=2024/01/02 groceries

      
        
        3
        +-- expect --

      
        
        4
        +DATE         "2024/01/01"         1:1-1:11

      
        
        5
        +EQ           "="                  1:11-1:12

      
        
        6
        +DATE         "2024/01/02"         1:12-1:22

      
        
        7
        +WHITESPACE   " "                  1:22-1:23

      
        
        8
        +TEXT         "groceries"          1:23-2:0

      
        
        9
        +NEWLINE      "\n"                 2:0-2:1

      
        
        10
        +EOF          ""                   2:1-2:1

      
M journal/lexer/testdata/empty.goldenjournal/lexer/testdata/empty.txtar
···
        
        1
        +-- input --

      
        
        2
        +-- expect --

      
        1
        3
         EOF          ""                   1:1-1:1

      
D journal/lexer/testdata/hash_comment.golden
···
        1
        
        -SEMICOLON    "#"                  1:1-1:2

      
        2
        
        -TEXT         "this is a comment"  1:3-1:20

      
        3
        
        -NEWLINE      "\n"                 1:20-1:21

      
        4
        
        -EOF          ""                   1:21-1:21

      
A journal/lexer/testdata/hash_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +# this is a comment

      
        
        3
        +-- expect --

      
        
        4
        +SEMICOLON    "#"                  1:1-1:2

      
        
        5
        +TEXT         "this is a comment"  1:3-2:0

      
        
        6
        +NEWLINE      "\n"                 2:0-2:1

      
        
        7
        +EOF          ""                   2:1-2:1

      
D journal/lexer/testdata/inline_comment.golden
···
        1
        
        -DATE         "2024/01/01"         1:1-1:11

      
        2
        
        -WHITESPACE   " "                  1:11-1:12

      
        3
        
        -TEXT         "groceries"          1:12-1:21

      
        4
        
        -WHITESPACE   " "                  1:21-1:22

      
        5
        
        -SEMICOLON    ";"                  1:22-1:23

      
        6
        
        -TEXT         "a note"             1:24-1:30

      
        7
        
        -NEWLINE      "\n"                 1:30-1:31

      
        8
        
        -EOF          ""                   1:31-1:31

      
A journal/lexer/testdata/inline_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries ; a note

      
        
        3
        +-- expect --

      
        
        4
        +DATE         "2024/01/01"         1:1-1:11

      
        
        5
        +WHITESPACE   " "                  1:11-1:12

      
        
        6
        +TEXT         "groceries"          1:12-1:21

      
        
        7
        +WHITESPACE   " "                  1:21-1:22

      
        
        8
        +SEMICOLON    ";"                  1:22-1:23

      
        
        9
        +TEXT         "a note"             1:24-2:0

      
        
        10
        +NEWLINE      "\n"                 2:0-2:1

      
        
        11
        +EOF          ""                   2:1-2:1

      
M journal/lexer/testdata/market_price_directive.goldenjournal/lexer/testdata/market_price_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +P 2024-01-01 USD 40.50 UAH

      
        
        3
        +-- expect --

      
        1
        4
         P            "P"                  1:1-1:2

      
        2
        5
         WHITESPACE   " "                  1:2-1:3

      
        3
        6
         DATE         "2024-01-01"         1:3-1:13

      
M journal/lexer/testdata/market_price_directive_with_time.goldenjournal/lexer/testdata/market_price_directive_with_time.txtar
···
        
        1
        +-- input --

      
        
        2
        +P 2024-01-01 12:00:00 USD 40.50 UAH

      
        
        3
        +-- expect --

      
        1
        4
         P            "P"                  1:1-1:2

      
        2
        5
         WHITESPACE   " "                  1:2-1:3

      
        3
        6
         DATE         "2024-01-01"         1:3-1:13

      
M journal/lexer/testdata/simple_transaction.goldenjournal/lexer/testdata/simple_transaction.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10.00

      
        
        4
        +    assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         DATE         "2024/01/01"         1:1-1:11

      
        2
        7
         WHITESPACE   " "                  1:11-1:12

      
        3
        8
         TEXT         "groceries"          1:12-2:0

      
M journal/lexer/testdata/special_chars_in_description.goldenjournal/lexer/testdata/special_chars_in_description.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +2024/01/01 * groceries + water| 1 + 2

      
        
        4
        +2024/01/01 groceries * water | 1 * 2

      
        
        5
        +2024/01/01 groceries ! water | 1 ! 2

      
        
        6
        +2024/01/01 groceries # water | 1 # 2

      
        
        7
        +2024/01/01 groceries % water | 1 % 2

      
        
        8
        +2024/01/01 groceries ^ water | 1 ^ 2

      
        
        9
        +2024/01/01 groceries & water | 1 & 2

      
        
        10
        +2024/01/01 groceries ( water | 1 ( 2

      
        
        11
        +2024/01/01 groceries ) water | 1 ) 2

      
        
        12
        +2024/01/01 groceries [ water | 1 [ 2

      
        
        13
        +2024/01/01 groceries ] water | 1 ] 2

      
        
        14
        +2024/01/01 groceries { water | 1 { 2

      
        
        15
        +2024/01/01 groceries } water | 1 } 2

      
        
        16
        +2026-06-07 * payee !one | something *important*

      
        
        17
        +    expenses:food  40.00

      
        
        18
        +    assets:cash

      
        
        19
        +-- expect --

      
        1
        20
         NEWLINE      "\n"                 2:0-2:1

      
        2
        21
         DATE         "2024/01/01"         2:1-2:11

      
        3
        22
         WHITESPACE   " "                  2:11-2:12

      
D journal/lexer/testdata/star_comment.golden
···
        1
        
        -STAR         "*"                  1:1-1:2

      
        2
        
        -TEXT         "this is a comment"  1:3-1:20

      
        3
        
        -NEWLINE      "\n"                 1:20-1:21

      
        4
        
        -EOF          ""                   1:21-1:21

      
A journal/lexer/testdata/star_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +* this is a comment

      
        
        3
        +-- expect --

      
        
        4
        +STAR         "*"                  1:1-1:2

      
        
        5
        +TEXT         "this is a comment"  1:3-2:0

      
        
        6
        +NEWLINE      "\n"                 2:0-2:1

      
        
        7
        +EOF          ""                   2:1-2:1

      
M journal/lexer/testdata/transaction,_accounts_with_uppercase_latters.goldenjournal/lexer/testdata/transaction,_accounts_with_uppercase_latters.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +2011/01/27 Book Store

      
        
        4
        +    Expenses:Books                       $20.00

      
        
        5
        +    Liabilities:MasterCard

      
        
        6
        +-- expect --

      
        1
        7
         NEWLINE      "\n"                 2:0-2:1

      
        2
        8
         DATE         "2011/01/27"         2:1-2:11

      
        3
        9
         WHITESPACE   " "                  2:11-2:12

      
M journal/lexer/testdata/transaction_with_code.goldenjournal/lexer/testdata/transaction_with_code.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 (123) groceries

      
        
        3
        +    expenses:food  $10.00

      
        
        4
        +    assets:checking

      
        
        5
        +

      
        
        6
        +2024/01/02 (ABS) groceries

      
        
        7
        +-- expect --

      
        1
        8
         DATE         "2024/01/01"         1:1-1:11

      
        2
        9
         WHITESPACE   " "                  1:11-1:12

      
        3
        10
         TEXT         "(123)"              1:12-1:17

      
M journal/lexer/testdata/transaction_with_unicode_commodity_symbols.goldenjournal/lexer/testdata/transaction_with_unicode_commodity_symbols.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  €10.00

      
        
        4
        +    expenses:food  £5.00

      
        
        5
        +    expenses:food  ₹700.00

      
        
        6
        +    expenses:food  40.00 гривні

      
        
        7
        +    assets:cash

      
        
        8
        +-- expect --

      
        1
        9
         DATE         "2024/01/01"         1:1-1:11

      
        2
        10
         WHITESPACE   " "                  1:11-1:12

      
        3
        11
         TEXT         "groceries"          1:12-2:0

      
M journal/lexer/testdata/transaction_with_virtual_accounts.goldenjournal/lexer/testdata/transaction_with_virtual_accounts.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 * groceries

      
        
        3
        +	(virtual:account)  1 PESO

      
        
        4
        +	[some thing:else]   5 PESO

      
        
        5
        +-- expect --

      
        1
        6
         DATE         "2024/01/01"         1:1-1:11

      
        2
        7
         WHITESPACE   " "                  1:11-1:12

      
        3
        8
         STAR         "*"                  1:12-1:13

      
M journal/loader.go
···
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"fmt"

      
        
        5
        +	"io/fs"

      
        5
        6
         	"os"

      
        
        7
        +	"path"

      
        6
        8
         	"path/filepath"

      
        7
        9
         	"slices"

      
        8
        10
         	"strings"

      ···
        21
        23
         	FileErrors []*ast.FileError

      
        22
        24
         }

      
        23
        25
         

      
        
        26
        +// CollectFiles returns root and all its transitive includes.

      
        
        27
        +func CollectFiles(root *ParsedFile) []*ParsedFile {

      
        
        28
        +	var files []*ParsedFile

      
        
        29
        +	visited := make(map[*ParsedFile]bool)

      
        
        30
        +	var walk func(*ParsedFile)

      
        
        31
        +	walk = func(pf *ParsedFile) {

      
        
        32
        +		if visited[pf] {

      
        
        33
        +			return

      
        
        34
        +		}

      
        
        35
        +		visited[pf] = true

      
        
        36
        +		files = append(files, pf)

      
        
        37
        +		for _, inc := range pf.Includes {

      
        
        38
        +			walk(inc)

      
        
        39
        +		}

      
        
        40
        +	}

      
        
        41
        +	walk(root)

      
        
        42
        +	return files

      
        
        43
        +}

      
        
        44
        +

      
        24
        45
         type Loader struct {

      
        25
        
        -	files map[string]*ParsedFile // key is absolute path

      
        
        46
        +	files map[string]*ParsedFile // key is absolute path (OS) or FS-relative path

      
        
        47
        +	fsys  fs.FS                  // set during LoadFS

      
        26
        48
         }

      
        27
        49
         

      
        28
        50
         func NewLoader() *Loader {

      
        29
        
        -	return &Loader{make(map[string]*ParsedFile)}

      
        
        51
        +	return &Loader{files: make(map[string]*ParsedFile)}

      
        30
        52
         }

      
        31
        53
         

      
        32
        54
         func (l *Loader) Load(fpath string) (*ParsedFile, error) {

      
        33
        55
         	return l.loadFile(fpath, nil)

      
        34
        56
         }

      
        35
        57
         

      
        
        58
        +// LoadFS loads a journal from an [fs.FS], resolving includes through the same filesystem.

      
        
        59
        +func (l *Loader) LoadFS(fsys fs.FS, fpath string) (*ParsedFile, error) {

      
        
        60
        +	l.fsys = fsys

      
        
        61
        +	defer func() { l.fsys = nil }()

      
        
        62
        +	return l.loadFile(fpath, nil)

      
        
        63
        +}

      
        
        64
        +

      
        36
        65
         func (l *Loader) LoadBytes(path string, src []byte) (*ParsedFile, error) {

      
        37
        66
         	return l.loadBytes(path, src, nil)

      
        38
        67
         }

      ···
        63
        92
         	return roots

      
        64
        93
         }

      
        65
        94
         

      
        66
        
        -// CollectFiles returns root and all its transitive includes.

      
        67
        
        -func CollectFiles(root *ParsedFile) []*ParsedFile {

      
        68
        
        -	var files []*ParsedFile

      
        69
        
        -	visited := make(map[*ParsedFile]bool)

      
        70
        
        -	var walk func(*ParsedFile)

      
        71
        
        -	walk = func(pf *ParsedFile) {

      
        72
        
        -		if visited[pf] {

      
        73
        
        -			return

      
        74
        
        -		}

      
        75
        
        -		visited[pf] = true

      
        76
        
        -		files = append(files, pf)

      
        77
        
        -		for _, inc := range pf.Includes {

      
        78
        
        -			walk(inc)

      
        79
        
        -		}

      
        80
        
        -	}

      
        81
        
        -	walk(root)

      
        82
        
        -	return files

      
        83
        
        -}

      
        84
        
        -

      
        85
        95
         // Ordered returns all files in dependency order (included before includer)

      
        86
        96
         func (l *Loader) Ordered() []*ParsedFile {

      
        87
        97
         	visited := make(map[string]bool)

      ···
        104
        114
         }

      
        105
        115
         

      
        106
        116
         func (l *Loader) loadFile(fpath string, stack []string) (*ParsedFile, error) {

      
        
        117
        +	if l.fsys != nil {

      
        
        118
        +		fpath = path.Clean(fpath)

      
        
        119
        +		if pf, ok := l.files[fpath]; ok {

      
        
        120
        +			return pf, nil

      
        
        121
        +		}

      
        
        122
        +		src, err := fs.ReadFile(l.fsys, fpath)

      
        
        123
        +		if err != nil {

      
        
        124
        +			return nil, err

      
        
        125
        +		}

      
        
        126
        +		return l.loadBytes(fpath, src, stack)

      
        
        127
        +	}

      
        
        128
        +

      
        107
        129
         	abs, err := filepath.Abs(fpath)

      
        108
        130
         	if err != nil {

      
        109
        131
         		return nil, err

      
        110
        132
         	}

      
        111
        
        -

      
        112
        
        -	// reuse already loaded

      
        113
        133
         	if pf, ok := l.files[abs]; ok {

      
        114
        134
         		return pf, nil

      
        115
        135
         	}

      
        116
        
        -

      
        117
        136
         	src, err := os.ReadFile(abs)

      
        118
        137
         	if err != nil {

      
        119
        138
         		return nil, err

      
        120
        139
         	}

      
        121
        
        -

      
        122
        140
         	return l.loadBytes(abs, src, stack)

      
        123
        141
         }

      
        124
        142
         

      
        125
        
        -func (l *Loader) loadBytes(path string, src []byte, stack []string) (*ParsedFile, error) {

      
        126
        
        -	abs, err := filepath.Abs(path)

      
        127
        
        -	if err != nil {

      
        128
        
        -		return nil, err

      
        
        143
        +func (l *Loader) loadBytes(pathStr string, src []byte, stack []string) (*ParsedFile, error) {

      
        
        144
        +	var fpath string

      
        
        145
        +	if l.fsys != nil {

      
        
        146
        +		fpath = path.Clean(pathStr)

      
        
        147
        +	} else {

      
        
        148
        +		abs, err := filepath.Abs(pathStr)

      
        
        149
        +		if err != nil {

      
        
        150
        +			return nil, err

      
        
        151
        +		}

      
        
        152
        +		fpath = abs

      
        129
        153
         	}

      
        130
        154
         

      
        131
        
        -	// cycle includes

      
        132
        
        -	if slices.Contains(stack, abs) {

      
        133
        
        -		return nil, fmt.Errorf("include cycle: %s", strings.Join(append(stack, abs), " → "))

      
        
        155
        +	if slices.Contains(stack, fpath) {

      
        
        156
        +		return nil, fmt.Errorf("include cycle: %s", strings.Join(append(stack, fpath), " → "))

      
        134
        157
         	}

      
        135
        
        -

      
        136
        
        -	// reuse already loaded

      
        137
        
        -	if pf, ok := l.files[abs]; ok {

      
        
        158
        +	if pf, ok := l.files[fpath]; ok {

      
        138
        159
         		return pf, nil

      
        139
        160
         	}

      
        140
        161
         

      
        141
        
        -	lex := lexer.New(abs, src)

      
        
        162
        +	lex := lexer.New(fpath, src)

      
        142
        163
         	par := parser.New(lex)

      
        143
        164
         	j := par.ParseJournal()

      
        144
        165
         

      
        145
        166
         	pf := &ParsedFile{

      
        146
        
        -		Path:     abs,

      
        
        167
        +		Path:     fpath,

      
        147
        168
         		Src:      src,

      
        148
        169
         		Ast:      j,

      
        149
        170
         		Includes: []*ParsedFile{},

      
        150
        171
         		Errors:   j.Errors,

      
        151
        172
         	}

      
        152
        
        -	l.files[abs] = pf

      
        
        173
        +	l.files[fpath] = pf

      
        153
        174
         

      
        154
        175
         	for _, entry := range j.Entries {

      
        155
        176
         		inc, ok := entry.(*ast.IncludeDirective)

      ···
        157
        178
         			continue

      
        158
        179
         		}

      
        159
        180
         

      
        160
        
        -		incPath := filepath.Join(filepath.Dir(abs), inc.Path)

      
        
        181
        +		var incPath string

      
        
        182
        +		var matches []string

      
        
        183
        +		var err error

      
        
        184
        +		if l.fsys != nil {

      
        
        185
        +			incPath = path.Join(path.Dir(fpath), inc.Path)

      
        
        186
        +			matches, err = fs.Glob(l.fsys, incPath)

      
        
        187
        +		} else {

      
        
        188
        +			incPath = filepath.Join(filepath.Dir(fpath), inc.Path)

      
        
        189
        +			matches, err = filepath.Glob(incPath)

      
        
        190
        +		}

      
        161
        191
         

      
        162
        
        -		matches, err := filepath.Glob(incPath)

      
        163
        192
         		if err != nil || len(matches) == 0 {

      
        164
        193
         			pf.FileErrors = append(pf.FileErrors, &ast.FileError{

      
        165
        194
         				Path:    incPath,

      ···
        170
        199
         		}

      
        171
        200
         

      
        172
        201
         		for _, match := range matches {

      
        173
        
        -			child, err := l.loadFile(match, append(stack, abs))

      
        
        202
        +			child, err := l.loadFile(match, append(stack, fpath))

      
        174
        203
         			if err != nil {

      
        175
        204
         				pf.FileErrors = append(pf.FileErrors, &ast.FileError{

      
        176
        205
         					Path:    match,

      
M journal/parser/parser_test.go
···
        9
        9
         )

      
        10
        10
         

      
        11
        11
         func TestParser_ParseFile(t *testing.T) {

      
        12
        
        -	tests := []struct{ name, inp string }{

      
        13
        
        -		{"blank line", "\n"},

      
        14
        
        -		{"comment semicolon", "; a comment\n"},

      
        15
        
        -		{"comment hash", "# a comment\n"},

      
        16
        
        -		{"comment percent", "% a comment\n"},

      
        17
        
        -		{"comment star", "* a comment\n"},

      
        18
        
        -		{"alias directive", "alias checking = assets:bank:checking\n"},

      
        19
        
        -		{"tag directive", "tag project-xyz\n"},

      
        20
        
        -		{"year directive", "year 1488\n"},

      
        21
        
        -		{"decimal-mark directive", "decimal-mark ,\n"},

      
        22
        
        -		{"C directive", "C 1.00s = 100c\n"},

      
        23
        
        -		{"D directive", `D $1.00

      
        24
        
        -D 10 UAH

      
        25
        
        -`},

      
        26
        
        -		{"P directive", "P 2024/01/01 USD 40.50 UAH\n"},

      
        27
        
        -		{"P directive with time", "P 2024-01-01 12:00:00 USD 40.50 UAH\n"},

      
        28
        
        -		{"N directive", "N $\n"},

      
        29
        
        -		{"inclue directive", "include ./path-to.journal\n"},

      
        30
        
        -		{"inclue directive with comment", "include ./path.journal ; something importnat\n"},

      
        31
        
        -		{"apply tag directive", "apply tag hashtag\n"},

      
        32
        
        -		{"apply fixed directive", "apply fixed CAD $0.90\n"},

      
        33
        
        -		{"end apply directive", "end apply tag\n"},

      
        34
        
        -		{"comment directive", "comment\nsome text\nend comment\n"},

      
        35
        
        -		{"comment directive end alone", "comment\nsome text\nend\n"},

      
        36
        
        -		{"comment directive with header", "comment tag:hidden\nstuff\nend\n"},

      
        37
        
        -		{"empty comment block", "comment\nend\n"},

      
        38
        
        -		{"never ending comment directive", "comment\nsome text\n"},

      
        39
        
        -		{"nested apply tag directives", `apply tag hashtag

      
        40
        
        -apply tag nestedtag: true

      
        41
        
        -2011/01/27 Book Store

      
        42
        
        -  expenses:books  $20.00

      
        43
        
        -  liabilities:mastercard

      
        44
        
        -end apply tag

      
        45
        
        -end apply tag

      
        46
        
        -`},

      
        47
        
        -		{"account directive", "account expenses:food\n"},

      
        48
        
        -		{"account directive with comment", "account expenses:food ; my account\n"},

      
        49
        
        -		{"account with subdirectives", `account expenses:food

      
        50
        
        -  note some note

      
        51
        
        -  alias food  ; this gets ignored

      
        52
        
        -`},

      
        53
        
        -		{"comodity directive", "commodity $\n"},

      
        54
        
        -		{"comodity directive word", "commodity UAH\n"},

      
        55
        
        -		{"comodity directive no space", "commodity $1000.00\n"},

      
        56
        
        -		{"commodity quantity first", "commodity 1,000.00 UAH\n"},

      
        57
        
        -		{"commodity quantity after", "commodity UAH 1,000.00\n"},

      
        58
        
        -		{"commodity with subdirectives", `commodity UAH

      
        59
        
        -  format 1 000.00 UAH

      
        60
        
        -  note Божествена Гривня  ; this gets ignored

      
        61
        
        -`},

      
        62
        
        -		{"payee directive", `payee grocery store

      
        63
        
        -payee 'grocery store 3'

      
        64
        
        -payee "grocery store 2"

      
        65
        
        -payee grocery store 1

      
        66
        
        -`},

      
        67
        
        -		{"transaction", "2024/01/01\n"},

      
        68
        
        -		{"automated transaction", `= ^income

      
        69
        
        -    (liabilities:tax)  *.33

      
        70
        
        -

      
        71
        
        -= expenses:gifts

      
        72
        
        -    budget:gifts  *-1

      
        73
        
        -    assets:budget  *1

      
        74
        
        -

      
        75
        
        -= income:salary

      
        76
        
        -    budget:savings  (amount * 0.5)

      
        77
        
        -`},

      
        78
        
        -		{"transaction with payee", "2024-01-01 groceries\n"},

      
        79
        
        -		{"transaction with digit payees", `2002/01/01 * 1a1a6305d06ce4b284dba0d267c23f69d70c20be

      
        80
        
        -    af0628973ff35bd62ddb048fa41dd8d83c1c46fe       $474.31

      
        81
        
        -    fc6f6f10f627ad1a5af9d488c98405a1498d019d

      
        82
        
        -

      
        83
        
        -2002/03/01 * 9861ce541c17b11f627e71c26bf350b33141f62b

      
        84
        
        -    0ecbb1b15e2cf3e515cc0f8533e5bb0fb2326728        $14.91

      
        85
        
        -    fc6f6f10f627ad1a5af9d488c98405a1498d019d

      
        86
        
        -`},

      
        87
        
        -		{"account with spaces", `2026-05-11 testies

      
        88
        
        -	expenses:account name  20.00

      
        89
        
        -	assets:bank

      
        90
        
        -`},

      
        91
        
        -		{"transaction with multiword payee", "2024-01-01 opening balances\n"},

      
        92
        
        -		{"transaction pending", "2024-01-01 ! groceries\n"},

      
        93
        
        -		{"transaction clearerd", "2024-01-01 * groceries\n"},

      
        94
        
        -		{"transaction with note", "2024-01-01 groceries | eating out\n"},

      
        95
        
        -		{"transaction with comment", "2024-01-01 groceries ; note\n"},

      
        96
        
        -		{"transaction with secondary date", "2024/01/01=2024/01/02 groceries\n"},

      
        97
        
        -		{"transaction with costs", `2026-05-11 testies

      
        98
        
        -	expenses:atm  20.00 UAH @ 1 USD

      
        99
        
        -	assets:bank

      
        100
        
        -

      
        101
        
        -2026-05-11 testies2

      
        102
        
        -	expenses:atm  20.00 UAH @@ 1 USD

      
        103
        
        -	assets:bank

      
        104
        
        -

      
        105
        
        -2026-05-12 testies3

      
        106
        
        -	expenses:atm  20.00 UAH @ 1 USD = 20.00 UAH @ 1 USD

      
        107
        
        -	assets:bank

      
        108
        
        -

      
        109
        
        -2015-01-03 money exchange office

      
        110
        
        -    assets:cash  -20 EUR @ 7.53 HRK

      
        111
        
        -    assets:cash  150.60 HRK

      
        112
        
        -`},

      
        113
        
        -		{"transaction with cost and assertion", `2026-05-11 testies

      
        114
        
        -	expenses:atm  UAH 20.00 @ 1 USD = 0 UAH

      
        115
        
        -	assets:bank

      
        116
        
        -`},

      
        117
        
        -		{"transaction with posting", `2024-01-01 groceries

      
        118
        
        -    expenses:food  $10.00

      
        119
        
        -    assets:checking

      
        120
        
        -`},

      
        121
        
        -		{"transaction with unicode commodity symbols", `2024-01-01 groceries

      
        122
        
        -    expenses:food  €10.00

      
        123
        
        -    expenses:food  £5.00

      
        124
        
        -    expenses:food  ₹700.00

      
        125
        
        -    assets:checking

      
        126
        
        -`},

      
        127
        
        -		{"transaction with quoted commodity", `2025-06-16 groceries

      
        128
        
        -    expenses:food  10.00 "EUR"

      
        129
        
        -    assets:cash

      
        130
        
        -`},

      
        131
        
        -		{"transaction with strange commodity symbols", `2024-01-01 groceries

      
        132
        
        -2026-05-20

      
        133
        
        -  asdf  123 $€£

      
        134
        
        -  asdf2

      
        135
        
        -

      
        136
        
        -2026-05-20

      
        137
        
        -  asdf  123 bytes

      
        138
        
        -  asdf2

      
        139
        
        -`},

      
        140
        
        -		{"special chars in description", `

      
        141
        
        -2024/01/01 groceries | 1 + 2

      
        142
        
        -2024/01/01 groceries | 1 * 2

      
        143
        
        -2024/01/01 groceries | 1 ! 2

      
        144
        
        -2024/01/01 groceries | 1 # 2

      
        145
        
        -2024/01/01 groceries | 1 % 2

      
        146
        
        -2024/01/01 groceries | 1 ^ 2

      
        147
        
        -2024/01/01 groceries | 1 & 2

      
        148
        
        -2024/01/01 groceries | 1 ( 2

      
        149
        
        -2024/01/01 groceries | 1 ) 2

      
        150
        
        -2024/01/01 groceries | 1 [ 2

      
        151
        
        -2024/01/01 groceries | 1 ] 2

      
        152
        
        -2024/01/01 groceries | 1 { 2

      
        153
        
        -2024/01/01 groceries | 1 } 2

      
        154
        
        -2026-06-07 * payee !one | something *important*

      
        155
        
        -    expenses:food  40.00

      
        156
        
        -    assets:cash

      
        157
        
        -`},

      
        158
        
        -		{"transaction with tabs", `2024-01-01 groceries

      
        159
        
        -	expenses:food  $10.00

      
        160
        
        -	assets:checking

      
        161
        
        -`},

      
        162
        
        -		{"transaction in ukrainian", `2024/03/02 Обід

      
        163
        
        -  витрати:їжа  350 UAH

      
        164
        
        -  активи:готівка

      
        165
        
        -`},

      
        166
        
        -		{"transaction with code", `2024-01-01 (123) groceries

      
        167
        
        -    expenses:food  $10.00

      
        168
        
        -    assets:checking

      
        169
        
        -`},

      
        170
        
        -		{"transaction with posting amounts", `2024.01.01 groceries

      
        171
        
        -    expenses:food  10.00 UAH

      
        172
        
        -    assets:checking  -10.00 UAH

      
        173
        
        -`},

      
        174
        
        -		{"transaction with spaced account name", `2022-01-01 opening balances

      
        175
        
        -    assets                    21 = 21

      
        176
        
        -    equity:opening/closing balances

      
        177
        
        -`},

      
        178
        
        -		{"transaction with inline comment", `2024/01/01 groceries

      
        179
        
        -	Expenses:Good  $10.00 ; food

      
        180
        
        -	Assets:Checking

      
        181
        
        -`},

      
        182
        
        -		{"transaction with header comment", `2024/01/01 groceries

      
        183
        
        -	; header comment

      
        184
        
        -	expenses:food  $10.00

      
        185
        
        -	assets:checking

      
        186
        
        -`},

      
        187
        
        -		{"transaction with trailing indent", `

      
        188
        
        -2013/1/1 * pay taxes

      
        189
        
        -    expenses:personal:tax              $1250

      
        190
        
        -    assets:bank:checking               $-1250

      
        191
        
        -

      
        192
        
        -`},

      
        193
        
        -		{"transaction with balance assertion", `2024/01/01 groceries

      
        194
        
        -	expenses:food  $10.00 = $100.00

      
        195
        
        -	assets:checking

      
        196
        
        -

      
        197
        
        -2025/03/07 groceries2

      
        198
        
        -	expenses:food  $10.00 == $100.00

      
        199
        
        -	assets:checking  == $0.00

      
        200
        
        -

      
        201
        
        -2025/04/08 groceries3

      
        202
        
        -	expenses:food  $10.00

      
        203
        
        -	assets:checking  = $0.00

      
        204
        
        -`},

      
        205
        
        -		{"transaction with virtual accounts", `2024/01/01 groceries

      
        206
        
        -	(virtual:account)  1 PESO

      
        207
        
        -	[something:else]   5 PESO

      
        208
        
        -	something:else

      
        209
        
        -`},

      
        210
        
        -		{"virtual postings with statuses", `2024/01/01 test

      
        211
        
        -  ! (assets:cash)  $10

      
        212
        
        -  (income:gift)  $-10

      
        213
        
        -

      
        214
        
        -2024/01/01 test

      
        215
        
        -    (! assets:cash)  $10

      
        216
        
        -    (income:gift)  $-10

      
        217
        
        -

      
        218
        
        -2024/01/01 test

      
        219
        
        -    ! (! assets:cash)  $10

      
        220
        
        -    (* income:gift)  $-10

      
        221
        
        -`},

      
        222
        
        -		{"period transaction expressions", `

      
        223
        
        -~ monthly

      
        224
        
        -    expenses:rent          $2000

      
        225
        
        -    assets:bank:checking

      
        226
        
        -

      
        227
        
        -~ monthly from 2023-04-15 to 2023-06-16

      
        228
        
        -    expenses:utilities          $400

      
        229
        
        -    assets:bank:checking

      
        230
        
        -

      
        231
        
        -~ every 2 months  in 2023, we will review

      
        232
        
        -    expenses:utilities          $400

      
        233
        
        -    assets:bank:checking

      
        234
        
        -

      
        235
        
        -~ monthly  Next year blah blah

      
        236
        
        -    expenses:food  $100

      
        237
        
        -    assets:checking

      
        238
        
        -

      
        239
        
        -~ monthly from 2018/6 ;In 2019 we will change this

      
        240
        
        -    expenses:food  $100

      
        241
        
        -    assets:checking

      
        242
        
        -`},

      
        243
        
        -		{"unexpected token", `@@@ garbage\n`},

      
        244
        
        -		{"recovery after bad posting", `2024-01-01 groceries

      
        245
        
        -    @@@invalid

      
        246
        
        -    assets:checking

      
        247
        
        -

      
        248
        
        -2024/01/02 salary

      
        249
        
        -    income:salary  $1000

      
        250
        
        -    assets:checking

      
        251
        
        -`},

      
        252
        
        -		{"illegal only", "@@@\n"},

      
        253
        
        -		{"illegal at start", "@@@ garbage\n"},

      
        254
        
        -		{"illegal in posting", `2024/01/01 groceries

      
        255
        
        -    @@@invalid

      
        256
        
        -    assets:checking

      
        257
        
        -`},

      
        258
        
        -		{"illegal between transactions", `2024/01/01 groceries

      
        259
        
        -    expenses:food  $10

      
        260
        
        -    assets:checking

      
        261
        
        -@@@

      
        262
        
        -2024/01/02 salary

      
        263
        
        -    income:salary  $1000

      
        264
        
        -    assets:checking

      
        265
        
        -`},

      
        266
        
        -		{"multiple bad postings", `2024/01/01 groceries

      
        267
        
        -    expenses:food  $10

      
        268
        
        -    @@@bad1

      
        269
        
        -    @bad2

      
        270
        
        -    assets:checking

      
        271
        
        -`},

      
        272
        
        -		{"three bad postings", `2024/01/01 groceries

      
        273
        
        -    expenses:food  $10

      
        274
        
        -    @@@bad1

      
        275
        
        -    @bad2

      
        276
        
        -    @bad3

      
        277
        
        -    assets:checking

      
        278
        
        -`},

      
        279
        
        -		{"quoted payee names", `2024-01-01 "groceries store"

      
        280
        
        -  expenses:food  $10

      
        281
        
        -  assets:checking

      
        282
        
        -`},

      
        283
        
        -		{"digit group marks", `2024-01-01 groceries

      
        284
        
        -  expenses:food  1 000.00 UAH

      
        285
        
        -  expenses:supplies  1'000.00 USD

      
        286
        
        -  assets:checking  -2_000.00 USD

      
        287
        
        -`},

      
        288
        
        -		{"directive prefixes", `!account expenses:food

      
        289
        
        -@commodity USD

      
        290
        
        -`},

      
        291
        
        -		{"bad between good", `2024/01/01 groceries

      
        292
        
        -    expenses:food  $10

      
        293
        
        -    @@@bad

      
        294
        
        -    assets:cash  $5

      
        295
        
        -`},

      
        296
        
        -		{"all postings bad", `2024/01/01 groceries

      
        297
        
        -    @@@bad1

      
        298
        
        -    @bad2

      
        299
        
        -    @bad3

      
        300
        
        -`},

      
        301
        
        -		{"bad then next transaction", `2024/01/01 groceries

      
        302
        
        -    expenses:food  $10

      
        303
        
        -    @@@bad

      
        304
        
        -

      
        305
        
        -2024/01/02 salary

      
        306
        
        -    income:salary  $1000

      
        307
        
        -    assets:checking

      
        308
        
        -`},

      
        309
        
        -		{"comment between bad postings", `2024/01/01 groceries

      
        310
        
        -    expenses:food  $10

      
        311
        
        -    @@@bad1

      
        312
        
        -    ; a comment

      
        313
        
        -    @bad2

      
        314
        
        -    assets:checking

      
        315
        
        -`},

      
        316
        
        -		{"bad posting at end", `2024/01/01 groceries

      
        317
        
        -    expenses:food  $10

      
        318
        
        -    @@@bad

      
        319
        
        -

      
        320
        
        -2024/01/02 salary

      
        321
        
        -    income:salary  $1000

      
        322
        
        -    assets:checking

      
        323
        
        -`},

      
        
        12
        +	tests := []string{

      
        
        13
        +		"blank line",

      
        
        14
        +		"comment semicolon",

      
        
        15
        +		"comment hash",

      
        
        16
        +		"comment percent",

      
        
        17
        +		"comment star",

      
        
        18
        +		"alias directive",

      
        
        19
        +		"tag directive",

      
        
        20
        +		"year directive",

      
        
        21
        +		"decimal-mark directive",

      
        
        22
        +		"C directive",

      
        
        23
        +		"D directive",

      
        
        24
        +		"P directive",

      
        
        25
        +		"P directive with time",

      
        
        26
        +		"N directive",

      
        
        27
        +		"inclue directive",

      
        
        28
        +		"inclue directive with comment",

      
        
        29
        +		"apply tag directive",

      
        
        30
        +		"apply fixed directive",

      
        
        31
        +		"end apply directive",

      
        
        32
        +		"comment directive",

      
        
        33
        +		"comment directive end alone",

      
        
        34
        +		"comment directive with header",

      
        
        35
        +		"empty comment block",

      
        
        36
        +		"never ending comment directive",

      
        
        37
        +		"nested apply tag directives",

      
        
        38
        +		"account directive",

      
        
        39
        +		"account directive with comment",

      
        
        40
        +		"account with subdirectives",

      
        
        41
        +		"comodity directive",

      
        
        42
        +		"comodity directive word",

      
        
        43
        +		"comodity directive no space",

      
        
        44
        +		"commodity quantity first",

      
        
        45
        +		"commodity quantity after",

      
        
        46
        +		"commodity with subdirectives",

      
        
        47
        +		"payee directive",

      
        
        48
        +		"transaction",

      
        
        49
        +		"automated transaction",

      
        
        50
        +		"transaction with payee",

      
        
        51
        +		"transaction with digit payees",

      
        
        52
        +		"account with spaces",

      
        
        53
        +		"transaction with multiword payee",

      
        
        54
        +		"transaction pending",

      
        
        55
        +		"transaction clearerd",

      
        
        56
        +		"transaction with note",

      
        
        57
        +		"transaction with comment",

      
        
        58
        +		"transaction with secondary date",

      
        
        59
        +		"transaction with costs",

      
        
        60
        +		"transaction with cost and assertion",

      
        
        61
        +		"transaction with posting",

      
        
        62
        +		"transaction with unicode commodity symbols",

      
        
        63
        +		"transaction with quoted commodity",

      
        
        64
        +		"transaction with strange commodity symbols",

      
        
        65
        +		"special chars in description",

      
        
        66
        +		"transaction with tabs",

      
        
        67
        +		"transaction in ukrainian",

      
        
        68
        +		"transaction with code",

      
        
        69
        +		"transaction with posting amounts",

      
        
        70
        +		"transaction with spaced account name",

      
        
        71
        +		"transaction with inline comment",

      
        
        72
        +		"transaction with header comment",

      
        
        73
        +		"transaction with trailing indent",

      
        
        74
        +		"transaction with balance assertion",

      
        
        75
        +		"transaction with virtual accounts",

      
        
        76
        +		"virtual postings with statuses",

      
        
        77
        +		"period transaction expressions",

      
        
        78
        +		"unexpected token",

      
        
        79
        +		"recovery after bad posting",

      
        
        80
        +		"illegal only",

      
        
        81
        +		"illegal at start",

      
        
        82
        +		"illegal in posting",

      
        
        83
        +		"illegal between transactions",

      
        
        84
        +		"multiple bad postings",

      
        
        85
        +		"three bad postings",

      
        
        86
        +		"quoted payee names",

      
        
        87
        +		"digit group marks",

      
        
        88
        +		"directive prefixes",

      
        
        89
        +		"bad between good",

      
        
        90
        +		"all postings bad",

      
        
        91
        +		"bad then next transaction",

      
        
        92
        +		"comment between bad postings",

      
        
        93
        +		"bad posting at end",

      
        324
        94
         	}

      
        325
        95
         	for _, tt := range tests {

      
        326
        
        -		t.Run(tt.name, func(t *testing.T) {

      
        327
        
        -			l := lexer.New("j", []byte(tt.inp))

      
        
        96
        +		t.Run(tt, func(t *testing.T) {

      
        
        97
        +			a := golden.Read(t, tt)

      
        
        98
        +			l := lexer.New("j", a.Get("input"))

      
        328
        99
         			f := New(l).ParseJournal()

      
        329
        
        -			golden.Assert(t, tt.name, ast.Dump(f))

      
        
        100
        +			golden.Assert(t, a, ast.Dump(f))

      
        330
        101
         		})

      
        331
        102
         	}

      
        332
        103
         }

      
M journal/parser/testdata/C_directive.goldenjournal/parser/testdata/C_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +C 1.00s = 100c

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           ConversionDirective j:1:1-2:1

      
        3
        6
             From:

      
M journal/parser/testdata/D_directive.goldenjournal/parser/testdata/D_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +D $1.00

      
        
        3
        +D 10 UAH

      
        
        4
        +-- expect --

      
        1
        5
         Journal

      
        2
        6
           DefaultCommodityDirective j:1:1-2:1

      
        3
        7
             Amount j:1:3-1:3

      
M journal/parser/testdata/N_directive.goldenjournal/parser/testdata/N_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +N $

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           IgnoredDirective j:1:1-2:1

      
        3
        6
             Text: $

      
M journal/parser/testdata/P_directive.goldenjournal/parser/testdata/P_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +P 2024/01/01 USD 40.50 UAH

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           MarketPriceDirective j:1:1-2:1

      
        3
        6
             Date: 2024/01/01

      
M journal/parser/testdata/P_directive_with_time.goldenjournal/parser/testdata/P_directive_with_time.txtar
···
        
        1
        +-- input --

      
        
        2
        +P 2024-01-01 12:00:00 USD 40.50 UAH

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           MarketPriceDirective j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/account_directive.goldenjournal/parser/testdata/account_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +account expenses:food

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           AccountDirective j:1:1-2:1

      
        3
        6
             Account j:1:9-2:0

      
M journal/parser/testdata/account_directive_with_comment.goldenjournal/parser/testdata/account_directive_with_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +account expenses:food ; my account

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           AccountDirective j:1:1-2:1

      
        3
        6
             Account j:1:9-1:22

      
M journal/parser/testdata/account_with_spaces.goldenjournal/parser/testdata/account_with_spaces.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-05-11 testies

      
        
        3
        +	expenses:account name  20.00

      
        
        4
        +	assets:bank

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2026-05-11

      
D journal/parser/testdata/account_with_subdirectives.golden
···
        1
        
        -Journal

      
        2
        
        -  AccountDirective j:1:1-4:1

      
        3
        
        -    Account j:1:9-2:0

      
        4
        
        -      SubAccount: "expenses" j:1:9-1:17

      
        5
        
        -      SubAccount: "food" j:1:18-2:0

      
A journal/parser/testdata/account_with_subdirectives.txtar
···
        
        1
        +-- input --

      
        
        2
        +account expenses:food

      
        
        3
        +  note some note

      
        
        4
        +  alias food  ; this gets ignored

      
        
        5
        +-- expect --

      
        
        6
        +Journal

      
        
        7
        +  AccountDirective j:1:1-4:1

      
        
        8
        +    Account j:1:9-2:0

      
        
        9
        +      SubAccount: "expenses" j:1:9-1:17

      
        
        10
        +      SubAccount: "food" j:1:18-2:0

      
D journal/parser/testdata/alias_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  AliasDirective j:1:1-2:1

      
        3
        
        -    From: "checking"

      
        4
        
        -    To: "assets:bank:checking"

      
A journal/parser/testdata/alias_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +alias checking = assets:bank:checking

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  AliasDirective j:1:1-2:1

      
        
        6
        +    From: "checking"

      
        
        7
        +    To: "assets:bank:checking"

      
M journal/parser/testdata/all_postings_bad.goldenjournal/parser/testdata/all_postings_bad.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    @@@bad1

      
        
        4
        +    @bad2

      
        
        5
        +    @bad3

      
        
        6
        +-- expect --

      
        1
        7
         Journal

      
        2
        8
           Transaction j:1:1-5:1

      
        3
        9
             Date: 2024/01/01

      
D journal/parser/testdata/apply_fixed_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  ApplyDirective j:1:1-2:1

      
        3
        
        -    Expr: "fixed CAD $0.90"

      
A journal/parser/testdata/apply_fixed_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +apply fixed CAD $0.90

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  ApplyDirective j:1:1-2:1

      
        
        6
        +    Expr: "fixed CAD $0.90"

      
D journal/parser/testdata/apply_tag_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  ApplyDirective j:1:1-2:1

      
        3
        
        -    Expr: "tag hashtag"

      
A journal/parser/testdata/apply_tag_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +apply tag hashtag

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  ApplyDirective j:1:1-2:1

      
        
        6
        +    Expr: "tag hashtag"

      
M journal/parser/testdata/automated_transaction.goldenjournal/parser/testdata/automated_transaction.txtar
···
        
        1
        +-- input --

      
        
        2
        += ^income

      
        
        3
        +    (liabilities:tax)  *.33

      
        
        4
        +

      
        
        5
        += expenses:gifts

      
        
        6
        +    budget:gifts  *-1

      
        
        7
        +    assets:budget  *1

      
        
        8
        +

      
        
        9
        += income:salary

      
        
        10
        +    budget:savings  (amount * 0.5)

      
        
        11
        +-- expect --

      
        1
        12
         Journal

      
        2
        13
           AutomatedTransaction j:1:1-4:0

      
        3
        14
             Expr: "^income"

      
M journal/parser/testdata/bad_between_good.goldenjournal/parser/testdata/bad_between_good.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    @@@bad

      
        
        5
        +    assets:cash  $5

      
        
        6
        +-- expect --

      
        1
        7
         Journal

      
        2
        8
           Transaction j:1:1-5:1

      
        3
        9
             Date: 2024/01/01

      
M journal/parser/testdata/bad_posting_at_end.goldenjournal/parser/testdata/bad_posting_at_end.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    @@@bad

      
        
        5
        +

      
        
        6
        +2024/01/02 salary

      
        
        7
        +    income:salary  $1000

      
        
        8
        +    assets:checking

      
        
        9
        +-- expect --

      
        1
        10
         Journal

      
        2
        11
           Transaction j:1:1-5:0

      
        3
        12
             Date: 2024/01/01

      
M journal/parser/testdata/bad_then_next_transaction.goldenjournal/parser/testdata/bad_then_next_transaction.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    @@@bad

      
        
        5
        +

      
        
        6
        +2024/01/02 salary

      
        
        7
        +    income:salary  $1000

      
        
        8
        +    assets:checking

      
        
        9
        +-- expect --

      
        1
        10
         Journal

      
        2
        11
           Transaction j:1:1-5:0

      
        3
        12
             Date: 2024/01/01

      
D journal/parser/testdata/blank_line.golden
···
        1
        
        -Journal

      
        2
        
        -  BlankLine j:2:0-2:1

      
A journal/parser/testdata/blank_line.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  BlankLine j:2:0-2:1

      
M journal/parser/testdata/comment_between_bad_postings.goldenjournal/parser/testdata/comment_between_bad_postings.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    @@@bad1

      
        
        5
        +    ; a comment

      
        
        6
        +    @bad2

      
        
        7
        +    assets:checking

      
        
        8
        +-- expect --

      
        1
        9
         Journal

      
        2
        10
           Transaction j:1:1-7:1

      
        3
        11
             Date: 2024/01/01

      
M journal/parser/testdata/comment_directive.goldenjournal/parser/testdata/comment_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment

      
        
        3
        +some text

      
        
        4
        +end comment

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           CommentBlockDirective j:1:1-4:1

      
        3
        8
             Header: ""

      
M journal/parser/testdata/comment_directive_end_alone.goldenjournal/parser/testdata/comment_directive_end_alone.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment

      
        
        3
        +some text

      
        
        4
        +end

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           CommentBlockDirective j:1:1-4:1

      
        3
        8
             Header: ""

      
M journal/parser/testdata/comment_directive_with_header.goldenjournal/parser/testdata/comment_directive_with_header.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment tag:hidden

      
        
        3
        +stuff

      
        
        4
        +end

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           CommentBlockDirective j:1:1-4:1

      
        3
        8
             Header: "tag:hidden"

      
M journal/parser/testdata/comment_hash.goldenjournal/parser/testdata/comment_hash.txtar
···
        
        1
        +-- input --

      
        
        2
        +# a comment

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Comment j:1:1-2:1

      
        3
        6
             Marker: "#"

      
M journal/parser/testdata/comment_percent.goldenjournal/parser/testdata/comment_percent.txtar
···
        
        1
        +-- input --

      
        
        2
        +% a comment

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Comment j:1:1-2:1

      
        3
        6
             Marker: "%"

      
M journal/parser/testdata/comment_semicolon.goldenjournal/parser/testdata/comment_semicolon.txtar
···
        
        1
        +-- input --

      
        
        2
        +; a comment

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Comment j:1:1-2:1

      
        3
        6
             Marker: ";"

      
M journal/parser/testdata/comment_star.goldenjournal/parser/testdata/comment_star.txtar
···
        
        1
        +-- input --

      
        
        2
        +* a comment

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Comment j:1:1-2:1

      
        3
        6
             Marker: "*"

      
M journal/parser/testdata/commodity_quantity_after.goldenjournal/parser/testdata/commodity_quantity_after.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity UAH 1,000.00

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           CommodityDirective j:1:1-2:1

      
        3
        6
             Commodity: "UAH"

      
M journal/parser/testdata/commodity_quantity_first.goldenjournal/parser/testdata/commodity_quantity_first.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity 1,000.00 UAH

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           CommodityDirective j:1:1-2:1

      
        3
        6
             Commodity: "UAH"

      
D journal/parser/testdata/commodity_with_subdirectives.golden
···
        1
        
        -Journal

      
        2
        
        -  CommodityDirective j:1:1-4:1

      
        3
        
        -    Commodity: "UAH"

      
A journal/parser/testdata/commodity_with_subdirectives.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity UAH

      
        
        3
        +  format 1 000.00 UAH

      
        
        4
        +  note Божествена Гривня  ; this gets ignored

      
        
        5
        +-- expect --

      
        
        6
        +Journal

      
        
        7
        +  CommodityDirective j:1:1-4:1

      
        
        8
        +    Commodity: "UAH"

      
M journal/parser/testdata/comodity_directive.goldenjournal/parser/testdata/comodity_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity $

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           CommodityDirective j:1:1-2:1

      
        3
        6
             Commodity: "$"

      
M journal/parser/testdata/comodity_directive_no_space.goldenjournal/parser/testdata/comodity_directive_no_space.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity $1000.00

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           CommodityDirective j:1:1-2:1

      
        3
        6
             Commodity: "$"

      
M journal/parser/testdata/comodity_directive_word.goldenjournal/parser/testdata/comodity_directive_word.txtar
···
        
        1
        +-- input --

      
        
        2
        +commodity UAH

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           CommodityDirective j:1:1-2:1

      
        3
        6
             Commodity: "UAH"

      
D journal/parser/testdata/decimal-mark_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  DecimalMarkDirective j:1:1-2:1

      
        3
        
        -    Mark: ","

      
A journal/parser/testdata/decimal-mark_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +decimal-mark ,

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  DecimalMarkDirective j:1:1-2:1

      
        
        6
        +    Mark: ","

      
M journal/parser/testdata/digit_group_marks.goldenjournal/parser/testdata/digit_group_marks.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +  expenses:food  1 000.00 UAH

      
        
        4
        +  expenses:supplies  1'000.00 USD

      
        
        5
        +  assets:checking  -2_000.00 USD

      
        
        6
        +-- expect --

      
        1
        7
         Journal

      
        2
        8
           Transaction j:1:1-5:1

      
        3
        9
             Date: 2024-01-01

      
M journal/parser/testdata/directive_prefixes.goldenjournal/parser/testdata/directive_prefixes.txtar
···
        
        1
        +-- input --

      
        
        2
        +!account expenses:food

      
        
        3
        +@commodity USD

      
        
        4
        +-- expect --

      
        1
        5
         Journal

      
        2
        6
           AccountDirective j:1:2-2:1

      
        3
        7
             Account j:1:10-2:0

      
M journal/parser/testdata/empty_comment_block.goldenjournal/parser/testdata/empty_comment_block.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment

      
        
        3
        +end

      
        
        4
        +-- expect --

      
        1
        5
         Journal

      
        2
        6
           CommentBlockDirective j:1:1-3:1

      
        3
        7
             Header: ""

      
D journal/parser/testdata/end_apply_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  EndDirective j:1:1-2:1

      
        3
        
        -    Expr: "apply tag"

      
A journal/parser/testdata/end_apply_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +end apply tag

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  EndDirective j:1:1-2:1

      
        
        6
        +    Expr: "apply tag"

      
D journal/parser/testdata/illegal_at_start.golden
···
        1
        
        -Journal

      
        2
        
        -  Errors

      
        3
        
        -    j:1:1-1:2: unexpected token AT

      
A journal/parser/testdata/illegal_at_start.txtar
···
        
        1
        +-- input --

      
        
        2
        +@@@ garbage

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  Errors

      
        
        6
        +    j:1:1-1:2: unexpected token AT

      
M journal/parser/testdata/illegal_between_transactions.goldenjournal/parser/testdata/illegal_between_transactions.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    assets:checking

      
        
        5
        +@@@

      
        
        6
        +2024/01/02 salary

      
        
        7
        +    income:salary  $1000

      
        
        8
        +    assets:checking

      
        
        9
        +-- expect --

      
        1
        10
         Journal

      
        2
        11
           Transaction j:1:1-4:1

      
        3
        12
             Date: 2024/01/01

      
M journal/parser/testdata/illegal_in_posting.goldenjournal/parser/testdata/illegal_in_posting.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    @@@invalid

      
        
        4
        +    assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024/01/01

      
D journal/parser/testdata/illegal_only.golden
···
        1
        
        -Journal

      
        2
        
        -  Errors

      
        3
        
        -    j:1:1-1:2: unexpected token AT

      
D journal/parser/testdata/inclue_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  IncludeDirective j:1:1-2:1

      
        3
        
        -    Path: "./path-to.journal"

      
A journal/parser/testdata/inclue_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +include ./path-to.journal

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  IncludeDirective j:1:1-2:1

      
        
        6
        +    Path: "./path-to.journal"

      
M journal/parser/testdata/inclue_directive_with_comment.goldenjournal/parser/testdata/inclue_directive_with_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +include ./path.journal ; something importnat

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           IncludeDirective j:1:1-2:1

      
        3
        6
             Path: "./path.journal"

      
M journal/parser/testdata/multiple_bad_postings.goldenjournal/parser/testdata/multiple_bad_postings.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    @@@bad1

      
        
        5
        +    @bad2

      
        
        6
        +    assets:checking

      
        
        7
        +-- expect --

      
        1
        8
         Journal

      
        2
        9
           Transaction j:1:1-6:1

      
        3
        10
             Date: 2024/01/01

      
M journal/parser/testdata/nested_apply_tag_directives.goldenjournal/parser/testdata/nested_apply_tag_directives.txtar
···
        
        1
        +-- input --

      
        
        2
        +apply tag hashtag

      
        
        3
        +apply tag nestedtag: true

      
        
        4
        +2011/01/27 Book Store

      
        
        5
        +  expenses:books  $20.00

      
        
        6
        +  liabilities:mastercard

      
        
        7
        +end apply tag

      
        
        8
        +end apply tag

      
        
        9
        +-- expect --

      
        1
        10
         Journal

      
        2
        11
           ApplyDirective j:1:1-2:1

      
        3
        12
             Expr: "tag hashtag"

      
M journal/parser/testdata/never_ending_comment_directive.goldenjournal/parser/testdata/never_ending_comment_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +comment

      
        
        3
        +some text

      
        
        4
        +-- expect --

      
        1
        5
         Journal

      
        2
        6
           CommentBlockDirective j:1:1-3:1

      
        3
        7
             Header: ""

      
M journal/parser/testdata/payee_directive.goldenjournal/parser/testdata/payee_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +payee grocery store

      
        
        3
        +payee 'grocery store 3'

      
        
        4
        +payee "grocery store 2"

      
        
        5
        +payee grocery store 1

      
        
        6
        +-- expect --

      
        1
        7
         Journal

      
        2
        8
           PayeeDirective j:1:1-2:1

      
        3
        9
             Name: "grocery store"

      
M journal/parser/testdata/period_transaction_expressions.goldenjournal/parser/testdata/period_transaction_expressions.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +~ monthly

      
        
        4
        +    expenses:rent          $2000

      
        
        5
        +    assets:bank:checking

      
        
        6
        +

      
        
        7
        +~ monthly from 2023-04-15 to 2023-06-16

      
        
        8
        +    expenses:utilities          $400

      
        
        9
        +    assets:bank:checking

      
        
        10
        +

      
        
        11
        +~ every 2 months  in 2023, we will review

      
        
        12
        +    expenses:utilities          $400

      
        
        13
        +    assets:bank:checking

      
        
        14
        +

      
        
        15
        +~ monthly  Next year blah blah

      
        
        16
        +    expenses:food  $100

      
        
        17
        +    assets:checking

      
        
        18
        +

      
        
        19
        +~ monthly from 2018/6 ;In 2019 we will change this

      
        
        20
        +    expenses:food  $100

      
        
        21
        +    assets:checking

      
        
        22
        +-- expect --

      
        1
        23
         Journal

      
        2
        24
           BlankLine j:2:0-2:1

      
        3
        25
           PeriodicTransaction j:2:1-6:0

      
M journal/parser/testdata/quoted_payee_names.goldenjournal/parser/testdata/quoted_payee_names.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 "groceries store"

      
        
        3
        +  expenses:food  $10

      
        
        4
        +  assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024-01-01

      
M journal/parser/testdata/recovery_after_bad_posting.goldenjournal/parser/testdata/recovery_after_bad_posting.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +    @@@invalid

      
        
        4
        +    assets:checking

      
        
        5
        +

      
        
        6
        +2024/01/02 salary

      
        
        7
        +    income:salary  $1000

      
        
        8
        +    assets:checking

      
        
        9
        +-- expect --

      
        1
        10
         Journal

      
        2
        11
           Transaction j:1:1-5:0

      
        3
        12
             Date: 2024-01-01

      
M journal/parser/testdata/special_chars_in_description.goldenjournal/parser/testdata/special_chars_in_description.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +2024/01/01 groceries | 1 + 2

      
        
        4
        +2024/01/01 groceries | 1 * 2

      
        
        5
        +2024/01/01 groceries | 1 ! 2

      
        
        6
        +2024/01/01 groceries | 1 # 2

      
        
        7
        +2024/01/01 groceries | 1 % 2

      
        
        8
        +2024/01/01 groceries | 1 ^ 2

      
        
        9
        +2024/01/01 groceries | 1 & 2

      
        
        10
        +2024/01/01 groceries | 1 ( 2

      
        
        11
        +2024/01/01 groceries | 1 ) 2

      
        
        12
        +2024/01/01 groceries | 1 [ 2

      
        
        13
        +2024/01/01 groceries | 1 ] 2

      
        
        14
        +2024/01/01 groceries | 1 { 2

      
        
        15
        +2024/01/01 groceries | 1 } 2

      
        
        16
        +2026-06-07 * payee !one | something *important*

      
        
        17
        +    expenses:food  40.00

      
        
        18
        +    assets:cash

      
        
        19
        +-- expect --

      
        1
        20
         Journal

      
        2
        21
           BlankLine j:2:0-2:1

      
        3
        22
           Transaction j:2:1-3:1

      
D journal/parser/testdata/tag_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  TagDirective j:1:1-2:1

      
        3
        
        -    Name: "project-xyz"

      
A journal/parser/testdata/tag_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +tag project-xyz

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  TagDirective j:1:1-2:1

      
        
        6
        +    Name: "project-xyz"

      
D journal/parser/testdata/tags.golden

Not showing binary file.

M journal/parser/testdata/three_bad_postings.goldenjournal/parser/testdata/three_bad_postings.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +    expenses:food  $10

      
        
        4
        +    @@@bad1

      
        
        5
        +    @bad2

      
        
        6
        +    @bad3

      
        
        7
        +    assets:checking

      
        
        8
        +-- expect --

      
        1
        9
         Journal

      
        2
        10
           Transaction j:1:1-7:1

      
        3
        11
             Date: 2024/01/01

      
D journal/parser/testdata/transaction.golden
···
        1
        
        -Journal

      
        2
        
        -  Transaction j:1:1-2:1

      
        3
        
        -    Date: 2024/01/01

      
A journal/parser/testdata/transaction.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  Transaction j:1:1-2:1

      
        
        6
        +    Date: 2024/01/01

      
M journal/parser/testdata/transaction_clearerd.goldenjournal/parser/testdata/transaction_clearerd.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 * groceries

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_in_ukrainian.goldenjournal/parser/testdata/transaction_in_ukrainian.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/03/02 Обід

      
        
        3
        +  витрати:їжа  350 UAH

      
        
        4
        +  активи:готівка

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024/03/02

      
M journal/parser/testdata/transaction_pending.goldenjournal/parser/testdata/transaction_pending.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 ! groceries

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_balance_assertion.goldenjournal/parser/testdata/transaction_with_balance_assertion.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +	expenses:food  $10.00 = $100.00

      
        
        4
        +	assets:checking

      
        
        5
        +

      
        
        6
        +2025/03/07 groceries2

      
        
        7
        +	expenses:food  $10.00 == $100.00

      
        
        8
        +	assets:checking  == $0.00

      
        
        9
        +

      
        
        10
        +2025/04/08 groceries3

      
        
        11
        +	expenses:food  $10.00

      
        
        12
        +	assets:checking  = $0.00

      
        
        13
        +-- expect --

      
        1
        14
         Journal

      
        2
        15
           Transaction j:1:1-5:0

      
        3
        16
             Date: 2024/01/01

      
M journal/parser/testdata/transaction_with_code.goldenjournal/parser/testdata/transaction_with_code.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 (123) groceries

      
        
        3
        +    expenses:food  $10.00

      
        
        4
        +    assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_comment.goldenjournal/parser/testdata/transaction_with_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries ; note

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_cost_and_assertion.goldenjournal/parser/testdata/transaction_with_cost_and_assertion.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-05-11 testies

      
        
        3
        +	expenses:atm  UAH 20.00 @ 1 USD = 0 UAH

      
        
        4
        +	assets:bank

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2026-05-11

      
M journal/parser/testdata/transaction_with_costs.goldenjournal/parser/testdata/transaction_with_costs.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-05-11 testies

      
        
        3
        +	expenses:atm  20.00 UAH @ 1 USD

      
        
        4
        +	assets:bank

      
        
        5
        +

      
        
        6
        +2026-05-11 testies2

      
        
        7
        +	expenses:atm  20.00 UAH @@ 1 USD

      
        
        8
        +	assets:bank

      
        
        9
        +

      
        
        10
        +2026-05-12 testies3

      
        
        11
        +	expenses:atm  20.00 UAH @ 1 USD = 20.00 UAH @ 1 USD

      
        
        12
        +	assets:bank

      
        
        13
        +

      
        
        14
        +2015-01-03 money exchange office

      
        
        15
        +    assets:cash  -20 EUR @ 7.53 HRK

      
        
        16
        +    assets:cash  150.60 HRK

      
        
        17
        +-- expect --

      
        1
        18
         Journal

      
        2
        19
           Transaction j:1:1-5:0

      
        3
        20
             Date: 2026-05-11

      
M journal/parser/testdata/transaction_with_digit_payees.goldenjournal/parser/testdata/transaction_with_digit_payees.txtar
···
        
        1
        +-- input --

      
        
        2
        +2002/01/01 * 1a1a6305d06ce4b284dba0d267c23f69d70c20be

      
        
        3
        +    af0628973ff35bd62ddb048fa41dd8d83c1c46fe       $474.31

      
        
        4
        +    fc6f6f10f627ad1a5af9d488c98405a1498d019d

      
        
        5
        +

      
        
        6
        +2002/03/01 * 9861ce541c17b11f627e71c26bf350b33141f62b

      
        
        7
        +    0ecbb1b15e2cf3e515cc0f8533e5bb0fb2326728        $14.91

      
        
        8
        +    fc6f6f10f627ad1a5af9d488c98405a1498d019d

      
        
        9
        +-- expect --

      
        1
        10
         Journal

      
        2
        11
           Transaction j:1:1-5:0

      
        3
        12
             Date: 2002/01/01

      
M journal/parser/testdata/transaction_with_header_comment.goldenjournal/parser/testdata/transaction_with_header_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +	; header comment

      
        
        4
        +	expenses:food  $10.00

      
        
        5
        +	assets:checking

      
        
        6
        +-- expect --

      
        1
        7
         Journal

      
        2
        8
           Transaction j:1:1-5:1

      
        3
        9
             Date: 2024/01/01

      
M journal/parser/testdata/transaction_with_inline_comment.goldenjournal/parser/testdata/transaction_with_inline_comment.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +	Expenses:Good  $10.00 ; food

      
        
        4
        +	Assets:Checking

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024/01/01

      
M journal/parser/testdata/transaction_with_multiword_payee.goldenjournal/parser/testdata/transaction_with_multiword_payee.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 opening balances

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_note.goldenjournal/parser/testdata/transaction_with_note.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries | eating out

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_payee.goldenjournal/parser/testdata/transaction_with_payee.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_posting.goldenjournal/parser/testdata/transaction_with_posting.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +    expenses:food  $10.00

      
        
        4
        +    assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_posting_amounts.goldenjournal/parser/testdata/transaction_with_posting_amounts.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024.01.01 groceries

      
        
        3
        +    expenses:food  10.00 UAH

      
        
        4
        +    assets:checking  -10.00 UAH

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024.01.01

      
M journal/parser/testdata/transaction_with_quoted_commodity.goldenjournal/parser/testdata/transaction_with_quoted_commodity.txtar
···
        
        1
        +-- input --

      
        
        2
        +2025-06-16 groceries

      
        
        3
        +    expenses:food  10.00 "EUR"

      
        
        4
        +    assets:cash

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2025-06-16

      
M journal/parser/testdata/transaction_with_secondary_date.goldenjournal/parser/testdata/transaction_with_secondary_date.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01=2024/01/02 groceries

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Transaction j:1:1-2:1

      
        3
        6
             Date: 2024/01/01

      
M journal/parser/testdata/transaction_with_spaced_account_name.goldenjournal/parser/testdata/transaction_with_spaced_account_name.txtar
···
        
        1
        +-- input --

      
        
        2
        +2022-01-01 opening balances

      
        
        3
        +    assets                    21 = 21

      
        
        4
        +    equity:opening/closing balances

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2022-01-01

      
M journal/parser/testdata/transaction_with_strange_commodity_symbols.goldenjournal/parser/testdata/transaction_with_strange_commodity_symbols.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +2026-05-20

      
        
        4
        +  asdf  123 $€£

      
        
        5
        +  asdf2

      
        
        6
        +

      
        
        7
        +2026-05-20

      
        
        8
        +  asdf  123 bytes

      
        
        9
        +  asdf2

      
        
        10
        +-- expect --

      
        1
        11
         Journal

      
        2
        12
           Transaction j:1:1-2:1

      
        3
        13
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_tabs.goldenjournal/parser/testdata/transaction_with_tabs.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +	expenses:food  $10.00

      
        
        4
        +	assets:checking

      
        
        5
        +-- expect --

      
        1
        6
         Journal

      
        2
        7
           Transaction j:1:1-4:1

      
        3
        8
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_trailing_indent.goldenjournal/parser/testdata/transaction_with_trailing_indent.txtar
···
        
        1
        +-- input --

      
        
        2
        +

      
        
        3
        +2013/1/1 * pay taxes

      
        
        4
        +    expenses:personal:tax              $1250

      
        
        5
        +    assets:bank:checking               $-1250

      
        
        6
        +

      
        
        7
        +-- expect --

      
        1
        8
         Journal

      
        2
        9
           BlankLine j:2:0-2:1

      
        3
        10
           Transaction j:2:1-6:0

      
M journal/parser/testdata/transaction_with_unicode_commodity_symbols.goldenjournal/parser/testdata/transaction_with_unicode_commodity_symbols.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024-01-01 groceries

      
        
        3
        +    expenses:food  €10.00

      
        
        4
        +    expenses:food  £5.00

      
        
        5
        +    expenses:food  ₹700.00

      
        
        6
        +    assets:checking

      
        
        7
        +-- expect --

      
        1
        8
         Journal

      
        2
        9
           Transaction j:1:1-6:1

      
        3
        10
             Date: 2024-01-01

      
M journal/parser/testdata/transaction_with_virtual_accounts.goldenjournal/parser/testdata/transaction_with_virtual_accounts.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 groceries

      
        
        3
        +	(virtual:account)  1 PESO

      
        
        4
        +	[something:else]   5 PESO

      
        
        5
        +	something:else

      
        
        6
        +-- expect --

      
        1
        7
         Journal

      
        2
        8
           Transaction j:1:1-5:1

      
        3
        9
             Date: 2024/01/01

      
M journal/parser/testdata/unexpected_token.goldenjournal/parser/testdata/illegal_only.txtar
···
        
        1
        +-- input --

      
        
        2
        +@@@

      
        
        3
        +-- expect --

      
        1
        4
         Journal

      
        2
        5
           Errors

      
        3
        6
             j:1:1-1:2: unexpected token AT

      
A journal/parser/testdata/unexpected_token.txtar
···
        
        1
        +-- input --

      
        
        2
        +@@@ garbage\n

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  Errors

      
        
        6
        +    j:1:1-1:2: unexpected token AT

      
M journal/parser/testdata/virtual_postings_with_statuses.goldenjournal/parser/testdata/virtual_postings_with_statuses.txtar
···
        
        1
        +-- input --

      
        
        2
        +2024/01/01 test

      
        
        3
        +  ! (assets:cash)  $10

      
        
        4
        +  (income:gift)  $-10

      
        
        5
        +

      
        
        6
        +2024/01/01 test

      
        
        7
        +    (! assets:cash)  $10

      
        
        8
        +    (income:gift)  $-10

      
        
        9
        +

      
        
        10
        +2024/01/01 test

      
        
        11
        +    ! (! assets:cash)  $10

      
        
        12
        +    (* income:gift)  $-10

      
        
        13
        +-- expect --

      
        1
        14
         Journal

      
        2
        15
           Transaction j:1:1-5:0

      
        3
        16
             Date: 2024/01/01

      
D journal/parser/testdata/year_directive.golden
···
        1
        
        -Journal

      
        2
        
        -  YearDirective j:1:1-2:1

      
        3
        
        -    Year: 1488

      
A journal/parser/testdata/year_directive.txtar
···
        
        1
        +-- input --

      
        
        2
        +year 1488

      
        
        3
        +-- expect --

      
        
        4
        +Journal

      
        
        5
        +  YearDirective j:1:1-2:1

      
        
        6
        +    Year: 1488

      
M journal/printer/printer_test.go
···
        13
        13
         func TestRoundTrip(t *testing.T) {

      
        14
        14
         	for _, tname := range tests {

      
        15
        15
         		t.Run(tname, func(t *testing.T) {

      
        16
        
        -			inp := golden.Load(t, tname)

      
        17
        
        -

      
        18
        
        -			pf, err := journal.NewLoader().LoadBytes(tname+".journal", inp)

      
        
        16
        +			a := golden.Read(t, tname)

      
        
        17
        +			pf, err := journal.NewLoader().LoadBytes(tname+".journal", a.Get("input"))

      
        19
        18
         			if err != nil {

      
        20
        19
         				t.Fatal(err)

      
        21
        20
         			}

      ···
        24
        23
         				t.Fatal(err)

      
        25
        24
         			}

      
        26
        25
         

      
        27
        
        -			golden.Assert(t, tname, b.String())

      
        
        26
        +			golden.Assert(t, a, b.String())

      
        28
        27
         		})

      
        29
        28
         	}

      
        30
        29
         }

      ···
        33
        32
         	for _, tname := range tests {

      
        34
        33
         		b.Run(tname, func(b *testing.B) {

      
        35
        34
         			b.ReportAllocs()

      
        36
        
        -			inp := golden.Load(b, tname)

      
        
        35
        +			inp := golden.Read(b, tname).Get("input")

      
        37
        36
         			pf, err := journal.NewLoader().LoadBytes(tname+".journal", inp)

      
        38
        37
         			if err != nil {

      
        39
        38
         				b.Fatal(err)

      ···
        61
        60
         func TestRoundTrip_WithConfig(t *testing.T) {

      
        62
        61
         	for tname, tt := range testsWithConfig {

      
        63
        62
         		t.Run(tname, func(t *testing.T) {

      
        64
        
        -			inp := golden.Load(t, tname)

      
        65
        
        -			pf, err := journal.NewLoader().LoadBytes(tname+".journal", inp)

      
        
        63
        +			a := golden.Read(t, tname)

      
        
        64
        +			pf, err := journal.NewLoader().LoadBytes(tname+".journal", a.Get("input"))

      
        66
        65
         			if err != nil {

      
        67
        66
         				t.Fatal(err)

      
        68
        67
         			}

      ···
        71
        70
         				t.Fatal(err)

      
        72
        71
         			}

      
        73
        72
         

      
        74
        
        -			golden.Assert(t, tname, b.String())

      
        
        73
        +			golden.Assert(t, a, b.String())

      
        75
        74
         		})

      
        76
        75
         	}

      
        77
        76
         }

      
        78
        77
         

      
        79
        78
         func BenchmarkPrinter_Config(b *testing.B) {

      
        80
        
        -	inp := golden.Load(b, "entries")

      
        
        79
        +	inp := golden.Read(b, "entries").Get("input")

      
        81
        80
         	pf, err := journal.NewLoader().LoadBytes("entries.journal", inp)

      
        82
        81
         	if err != nil {

      
        83
        82
         		b.Fatal(err)

      
M journal/printer/testdata/align_right.goldenjournal/printer/testdata/align_right.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-01-15 * Test restaurant

      
        
        3
        +  Expenses:FoodAndDrinks  50.00$

      
        
        4
        +  Assets:Cash  -50.00$

      
        
        5
        +

      
        
        6
        +2026-01-16 another entry

      
        
        7
        +  Expenses:Food  30.00$

      
        
        8
        +  Assets:Cash  -30.00$

      
        
        9
        +

      
        
        10
        +-- expect --

      
        1
        11
         2026-01-15 * Test restaurant

      
        2
        12
           Expenses:FoodAndDrinks                            50.00$

      
        3
        13
           Assets:Cash                                       -50.00$

      
D journal/printer/testdata/align_right.input
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -  Expenses:FoodAndDrinks  50.00$

      
        3
        
        -  Assets:Cash  -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 another entry

      
        6
        
        -  Expenses:Food  30.00$

      
        7
        
        -  Assets:Cash  -30.00$

      
D journal/printer/testdata/align_tab.golden
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -  Expenses:FoodAndDrinks  50.00$

      
        3
        
        -  Assets:Cash             -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 Another entry with longer account

      
        6
        
        -  Expenses:SuperMarket  120.50$

      
        7
        
        -  Assets:Cash           -120.50$

      
        8
        
        -

      
        9
        
        -2026-01-17 Short accounts

      
        10
        
        -  a  10.00$

      
        11
        
        -  b  -10.00$

      
D journal/printer/testdata/align_tab.input
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -  Expenses:FoodAndDrinks  50.00$

      
        3
        
        -  Assets:Cash  -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 Another entry with longer account

      
        6
        
        -  Expenses:SuperMarket  120.50$

      
        7
        
        -  Assets:Cash  -120.50$

      
        8
        
        -

      
        9
        
        -2026-01-17 Short accounts

      
        10
        
        -  a  10.00$

      
        11
        
        -  b  -10.00$

      
A journal/printer/testdata/align_tab.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-01-15 * Test restaurant

      
        
        3
        +  Expenses:FoodAndDrinks  50.00$

      
        
        4
        +  Assets:Cash  -50.00$

      
        
        5
        +

      
        
        6
        +2026-01-16 Another entry with longer account

      
        
        7
        +  Expenses:SuperMarket  120.50$

      
        
        8
        +  Assets:Cash  -120.50$

      
        
        9
        +

      
        
        10
        +2026-01-17 Short accounts

      
        
        11
        +  a  10.00$

      
        
        12
        +  b  -10.00$

      
        
        13
        +

      
        
        14
        +-- expect --

      
        
        15
        +2026-01-15 * Test restaurant

      
        
        16
        +  Expenses:FoodAndDrinks  50.00$

      
        
        17
        +  Assets:Cash             -50.00$

      
        
        18
        +

      
        
        19
        +2026-01-16 Another entry with longer account

      
        
        20
        +  Expenses:SuperMarket  120.50$

      
        
        21
        +  Assets:Cash           -120.50$

      
        
        22
        +

      
        
        23
        +2026-01-17 Short accounts

      
        
        24
        +  a  10.00$

      
        
        25
        +  b  -10.00$

      
D journal/printer/testdata/basic_twospaces.golden
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -  ; header comment on transaction (before payee)

      
        3
        
        -  Expenses:Food  50.00$

      
        4
        
        -  Assets:Cash  -50.00$

      
        5
        
        -  ; inline comment on posting

      
        6
        
        -

      
        7
        
        -; A simple comment line

      
        8
        
        -

      
        9
        
        -2026-01-16 another entry

      
        10
        
        -  Expenses:Food  30.00$

      
        11
        
        -  Assets:Cash  -30.00$

      
D journal/printer/testdata/commodity_before.golden
···
        1
        
        -2026-01-15 * Groceries

      
        2
        
        -  Expenses:Food  $50.00

      
        3
        
        -  Assets:Cash

      
        4
        
        -

      
        5
        
        -2026-01-16 Salary

      
        6
        
        -  Assets:Cash  $2000.00

      
        7
        
        -  Income:Salary  $-2000.00

      
D journal/printer/testdata/commodity_before.input
···
        1
        
        -2026-01-15 * Groceries

      
        2
        
        -  Expenses:Food  50.00$

      
        3
        
        -  Assets:Cash

      
        4
        
        -

      
        5
        
        -2026-01-16 Salary

      
        6
        
        -  Assets:Cash  $2000.00

      
        7
        
        -  Income:Salary  -2000.00$

      
A journal/printer/testdata/commodity_before.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-01-15 * Groceries

      
        
        3
        +  Expenses:Food  50.00$

      
        
        4
        +  Assets:Cash

      
        
        5
        +

      
        
        6
        +2026-01-16 Salary

      
        
        7
        +  Assets:Cash  $2000.00

      
        
        8
        +  Income:Salary  -2000.00$

      
        
        9
        +

      
        
        10
        +-- expect --

      
        
        11
        +2026-01-15 * Groceries

      
        
        12
        +  Expenses:Food  $50.00

      
        
        13
        +  Assets:Cash

      
        
        14
        +

      
        
        15
        +2026-01-16 Salary

      
        
        16
        +  Assets:Cash  $2000.00

      
        
        17
        +  Income:Salary  $-2000.00

      
D journal/printer/testdata/directives.golden
···
        1
        
        -; Directives

      
        2
        
        -account Assets:Cash  ; cash account

      
        3
        
        -commodity $

      
        4
        
        -commodity UAH

      
        5
        
        -commodity 1000.00 USD

      
        6
        
        -commodity $1000.00

      
        7
        
        -commodity 1,000.00 UAH

      
        8
        
        -include ./other.journal

      
        9
        
        -include ../one../another.journal

      
        10
        
        -alias Assets:Checking = Assets:Cash

      
        11
        
        -payee "Test Payee"

      
        12
        
        -tag important

      
        13
        
        -tag something-else

      
        14
        
        -year 2026

      
        15
        
        -decimal-mark ,

      
        16
        
        -

      
        17
        
        -; Market price

      
        18
        
        -P 2026-01-15 12:00:00 GOOG 150.00$

      
        19
        
        -

      
        20
        
        -; Conversion

      
        21
        
        -C 100.00$ = 85.00€  ; test comment

      
        22
        
        -

      
        23
        
        -; Default commodity

      
        24
        
        -D 1,000.00$

      
        25
        
        -

      
        26
        
        -; N directive

      
        27
        
        -N $

      
        28
        
        -; Apply/end

      
        29
        
        -apply account Expenses

      
        30
        
        -end

      
        31
        
        -

      
        32
        
        -; Comment block

      
        33
        
        -comment

      
        34
        
        -Some notes about the file

      
        35
        
        -end

      
D journal/printer/testdata/directives.input
···
        1
        
        -; Directives

      
        2
        
        -account Assets:Cash ; cash account

      
        3
        
        -commodity $

      
        4
        
        -commodity UAH

      
        5
        
        -commodity 1000.00 USD

      
        6
        
        -commodity $1000.00

      
        7
        
        -commodity 1,000.00 UAH

      
        8
        
        -include ./other.journal

      
        9
        
        -include ../one../another.journal

      
        10
        
        -alias Assets:Checking = Assets:Cash

      
        11
        
        -payee "Test Payee"

      
        12
        
        -tag "important"

      
        13
        
        -tag something-else

      
        14
        
        -year 2026

      
        15
        
        -decimal-mark ,

      
        16
        
        -

      
        17
        
        -; Market price

      
        18
        
        -P 2026/01/15 12:00:00 GOOG $150.00

      
        19
        
        -

      
        20
        
        -; Conversion

      
        21
        
        -C 100.00$ = 85.00€  ; test comment

      
        22
        
        -

      
        23
        
        -; Default commodity

      
        24
        
        -D $1,000.00

      
        25
        
        -

      
        26
        
        -; N directive

      
        27
        
        -N $

      
        28
        
        -

      
        29
        
        -; Apply/end

      
        30
        
        -apply account Expenses

      
        31
        
        -end

      
        32
        
        -

      
        33
        
        -; Comment block

      
        34
        
        -comment

      
        35
        
        -Some notes about the file

      
        36
        
        -end comment

      
A journal/printer/testdata/directives.txtar
···
        
        1
        +-- input --

      
        
        2
        +; Directives

      
        
        3
        +account Assets:Cash ; cash account

      
        
        4
        +commodity $

      
        
        5
        +commodity UAH

      
        
        6
        +commodity 1000.00 USD

      
        
        7
        +commodity $1000.00

      
        
        8
        +commodity 1,000.00 UAH

      
        
        9
        +include ./other.journal

      
        
        10
        +include ../one../another.journal

      
        
        11
        +alias Assets:Checking = Assets:Cash

      
        
        12
        +payee "Test Payee"

      
        
        13
        +tag "important"

      
        
        14
        +tag something-else

      
        
        15
        +year 2026

      
        
        16
        +decimal-mark ,

      
        
        17
        +

      
        
        18
        +; Market price

      
        
        19
        +P 2026/01/15 12:00:00 GOOG $150.00

      
        
        20
        +

      
        
        21
        +; Conversion

      
        
        22
        +C 100.00$ = 85.00€  ; test comment

      
        
        23
        +

      
        
        24
        +; Default commodity

      
        
        25
        +D $1,000.00

      
        
        26
        +

      
        
        27
        +; N directive

      
        
        28
        +N $

      
        
        29
        +

      
        
        30
        +; Apply/end

      
        
        31
        +apply account Expenses

      
        
        32
        +end

      
        
        33
        +

      
        
        34
        +; Comment block

      
        
        35
        +comment

      
        
        36
        +Some notes about the file

      
        
        37
        +end comment

      
        
        38
        +

      
        
        39
        +-- expect --

      
        
        40
        +; Directives

      
        
        41
        +account Assets:Cash  ; cash account

      
        
        42
        +commodity $

      
        
        43
        +commodity UAH

      
        
        44
        +commodity 1000.00 USD

      
        
        45
        +commodity $1000.00

      
        
        46
        +commodity 1,000.00 UAH

      
        
        47
        +include ./other.journal

      
        
        48
        +include ../one../another.journal

      
        
        49
        +alias Assets:Checking = Assets:Cash

      
        
        50
        +payee "Test Payee"

      
        
        51
        +tag important

      
        
        52
        +tag something-else

      
        
        53
        +year 2026

      
        
        54
        +decimal-mark ,

      
        
        55
        +

      
        
        56
        +; Market price

      
        
        57
        +P 2026-01-15 12:00:00 GOOG 150.00$

      
        
        58
        +

      
        
        59
        +; Conversion

      
        
        60
        +C 100.00$ = 85.00€  ; test comment

      
        
        61
        +

      
        
        62
        +; Default commodity

      
        
        63
        +D 1,000.00$

      
        
        64
        +

      
        
        65
        +; N directive

      
        
        66
        +N $

      
        
        67
        +; Apply/end

      
        
        68
        +apply account Expenses

      
        
        69
        +end

      
        
        70
        +

      
        
        71
        +; Comment block

      
        
        72
        +comment

      
        
        73
        +Some notes about the file

      
        
        74
        +end

      
D journal/printer/testdata/entries.golden
···
        1
        
        -; i am a comment

      
        2
        
        -;; i am a comment

      
        3
        
        -# i am a comment

      
        4
        
        -* i am a comment

      
        5
        
        -**** i'm a fat comment

      
        6
        
        -% and i am a comment

      
        7
        
        -

      
        8
        
        -2026-01-15 * Test restaurant

      
        9
        
        -  ; header comment on transaction (before payee)

      
        10
        
        -  Expenses:Food  50.00$

      
        11
        
        -  Assets:Cash  -50.00$

      
        12
        
        -  ; inline comment on posting

      
        13
        
        -

      
        14
        
        -; A simple comment line

      
        15
        
        -

      
        16
        
        -2026-01-16 another entry

      
        17
        
        -  Expenses:Food  30.00$

      
        18
        
        -  Assets:Cash  -30.00$

      
        19
        
        -

      
        20
        
        -~ monthly *

      
        21
        
        -  Expenses:Food  100.00$

      
        22
        
        -  Assets:Cash  -100.00$

      
        23
        
        -

      
        24
        
        -= account:Expenses

      
        25
        
        -  Expenses:Food  100.00$

      
        26
        
        -  Assets:Cash  -100.00$

      
        27
        
        -

      
        28
        
        -2026-01-15 * (CODE) Test with code and note | Some note

      
        29
        
        -  Expenses:Food  100.00$

      
        30
        
        -  Assets:Cash  -100.00$

      
        31
        
        -

      
        32
        
        -2026-01-15=2026-01-20 * Virtual posting

      
        33
        
        -  (Expenses:Food)  100.00$

      
        34
        
        -  [Assets:Cash]  -100.00$

      
        35
        
        -

      
        36
        
        -2026-01-15 * Cost test

      
        37
        
        -  Expenses:Food  100.00$ @ 1.50$

      
        38
        
        -  Assets:Cash  -100.00$ @@ 1.50$

      
        39
        
        -  ; continuation comment

      
        40
        
        -

      
        41
        
        -2026-02-01 * Balance assertion test

      
        42
        
        -  Expenses:Food  50.00$ = 500.00$

      
        43
        
        -  Assets:Cash  -50.00$ == 500.00$

      
        44
        
        -  Assets:Total  === 0.00$

      
        45
        
        -

      
        46
        
        -2026-03-01 Multi-posting

      
        47
        
        -  Expenses:Food  30.00$

      
        48
        
        -  Expenses:Dining  20.00$

      
        49
        
        -  Assets:Cash  -50.00$

      
        50
        
        -

      
        51
        
        -0000-01-01 test

      
        52
        
        -  a  123.00$

      
        53
        
        -  b

      
        54
        
        -

      
        55
        
        -2026-01-15 * Comments test

      
        56
        
        -  ; header comment

      
        57
        
        -  Expenses:Food  50.00$

      
        58
        
        -  Assets:Cash  -50.00$

      
        59
        
        -  ; inline comment

      
D journal/printer/testdata/entries.input
···
        1
        
        -; i am a comment

      
        2
        
        -;; i am a comment

      
        3
        
        -# i am a comment

      
        4
        
        -* i am a comment

      
        5
        
        -**** i'm a fat comment

      
        6
        
        -% and i am a comment

      
        7
        
        -

      
        8
        
        -2026/01/15 * Test restaurant

      
        9
        
        -    ; header comment on transaction (before payee)

      
        10
        
        -    Expenses:Food    $50.00

      
        11
        
        -    Assets:Cash     $-50.00

      
        12
        
        -    ; inline comment on posting

      
        13
        
        -

      
        14
        
        -; A simple comment line

      
        15
        
        -

      
        16
        
        -2026/01/16 another entry

      
        17
        
        -    Expenses:Food    $30.00

      
        18
        
        -    Assets:Cash     $-30.00

      
        19
        
        -

      
        20
        
        -~ monthly

      
        21
        
        -    Expenses:Food    $100.00

      
        22
        
        -    Assets:Cash     $-100.00

      
        23
        
        -

      
        24
        
        -= account:Expenses

      
        25
        
        -    Expenses:Food    $100.00

      
        26
        
        -    Assets:Cash     $-100.00

      
        27
        
        -

      
        28
        
        -2026/01/15 * (CODE) Test with code and note | Some note

      
        29
        
        -    Expenses:Food    $100

      
        30
        
        -    Assets:Cash     $-100

      
        31
        
        -

      
        32
        
        -2026/01/15=2026/01/20 * Virtual posting

      
        33
        
        -    (Expenses:Food)    $100.00

      
        34
        
        -    [Assets:Cash]     $-100.00

      
        35
        
        -

      
        36
        
        -2026/01/15 * Cost test

      
        37
        
        -    Expenses:Food  $100.00 @ $1.50

      
        38
        
        -    Assets:Cash    $-100.00 @@ $1.50

      
        39
        
        -    ; continuation comment

      
        40
        
        -

      
        41
        
        -2026/02/01 * Balance assertion test

      
        42
        
        -    Expenses:Food  $50.00 = $500.00

      
        43
        
        -    Assets:Cash   $-50.00 == $500.00

      
        44
        
        -    Assets:Total        === $0.00

      
        45
        
        -

      
        46
        
        -2026/03/01 Multi-posting

      
        47
        
        -    Expenses:Food    $30.00

      
        48
        
        -    Expenses:Dining  $20.00

      
        49
        
        -    Assets:Cash     $-50.00

      
        50
        
        -

      
        51
        
        -01-01 test

      
        52
        
        -    a  $123.00

      
        53
        
        -    b

      
        54
        
        -

      
        55
        
        -2026/01/15 * Comments test

      
        56
        
        -    ; header comment

      
        57
        
        -    Expenses:Food    $50.00

      
        58
        
        -    Assets:Cash     $-50.00

      
        59
        
        -    ; inline comment

      
A journal/printer/testdata/entries.txtar
···
        
        1
        +-- input --

      
        
        2
        +; i am a comment

      
        
        3
        +;; i am a comment

      
        
        4
        +# i am a comment

      
        
        5
        +* i am a comment

      
        
        6
        +**** i'm a fat comment

      
        
        7
        +% and i am a comment

      
        
        8
        +

      
        
        9
        +2026/01/15 * Test restaurant

      
        
        10
        +    ; header comment on transaction (before payee)

      
        
        11
        +    Expenses:Food    $50.00

      
        
        12
        +    Assets:Cash     $-50.00

      
        
        13
        +    ; inline comment on posting

      
        
        14
        +

      
        
        15
        +; A simple comment line

      
        
        16
        +

      
        
        17
        +2026/01/16 another entry

      
        
        18
        +    Expenses:Food    $30.00

      
        
        19
        +    Assets:Cash     $-30.00

      
        
        20
        +

      
        
        21
        +~ monthly

      
        
        22
        +    Expenses:Food    $100.00

      
        
        23
        +    Assets:Cash     $-100.00

      
        
        24
        +

      
        
        25
        += account:Expenses

      
        
        26
        +    Expenses:Food    $100.00

      
        
        27
        +    Assets:Cash     $-100.00

      
        
        28
        +

      
        
        29
        +2026/01/15 * (CODE) Test with code and note | Some note

      
        
        30
        +    Expenses:Food    $100

      
        
        31
        +    Assets:Cash     $-100

      
        
        32
        +

      
        
        33
        +2026/01/15=2026/01/20 * Virtual posting

      
        
        34
        +    (Expenses:Food)    $100.00

      
        
        35
        +    [Assets:Cash]     $-100.00

      
        
        36
        +

      
        
        37
        +2026/01/15 * Cost test

      
        
        38
        +    Expenses:Food  $100.00 @ $1.50

      
        
        39
        +    Assets:Cash    $-100.00 @@ $1.50

      
        
        40
        +    ; continuation comment

      
        
        41
        +

      
        
        42
        +2026/02/01 * Balance assertion test

      
        
        43
        +    Expenses:Food  $50.00 = $500.00

      
        
        44
        +    Assets:Cash   $-50.00 == $500.00

      
        
        45
        +    Assets:Total        === $0.00

      
        
        46
        +

      
        
        47
        +2026/03/01 Multi-posting

      
        
        48
        +    Expenses:Food    $30.00

      
        
        49
        +    Expenses:Dining  $20.00

      
        
        50
        +    Assets:Cash     $-50.00

      
        
        51
        +

      
        
        52
        +01-01 test

      
        
        53
        +    a  $123.00

      
        
        54
        +    b

      
        
        55
        +

      
        
        56
        +2026/01/15 * Comments test

      
        
        57
        +    ; header comment

      
        
        58
        +    Expenses:Food    $50.00

      
        
        59
        +    Assets:Cash     $-50.00

      
        
        60
        +    ; inline comment

      
        
        61
        +

      
        
        62
        +-- expect --

      
        
        63
        +; i am a comment

      
        
        64
        +;; i am a comment

      
        
        65
        +# i am a comment

      
        
        66
        +* i am a comment

      
        
        67
        +**** i'm a fat comment

      
        
        68
        +% and i am a comment

      
        
        69
        +

      
        
        70
        +2026-01-15 * Test restaurant

      
        
        71
        +  ; header comment on transaction (before payee)

      
        
        72
        +  Expenses:Food  50.00$

      
        
        73
        +  Assets:Cash  -50.00$

      
        
        74
        +  ; inline comment on posting

      
        
        75
        +

      
        
        76
        +; A simple comment line

      
        
        77
        +

      
        
        78
        +2026-01-16 another entry

      
        
        79
        +  Expenses:Food  30.00$

      
        
        80
        +  Assets:Cash  -30.00$

      
        
        81
        +

      
        
        82
        +~ monthly *

      
        
        83
        +  Expenses:Food  100.00$

      
        
        84
        +  Assets:Cash  -100.00$

      
        
        85
        +

      
        
        86
        += account:Expenses

      
        
        87
        +  Expenses:Food  100.00$

      
        
        88
        +  Assets:Cash  -100.00$

      
        
        89
        +

      
        
        90
        +2026-01-15 * (CODE) Test with code and note | Some note

      
        
        91
        +  Expenses:Food  100.00$

      
        
        92
        +  Assets:Cash  -100.00$

      
        
        93
        +

      
        
        94
        +2026-01-15=2026-01-20 * Virtual posting

      
        
        95
        +  (Expenses:Food)  100.00$

      
        
        96
        +  [Assets:Cash]  -100.00$

      
        
        97
        +

      
        
        98
        +2026-01-15 * Cost test

      
        
        99
        +  Expenses:Food  100.00$ @ 1.50$

      
        
        100
        +  Assets:Cash  -100.00$ @@ 1.50$

      
        
        101
        +  ; continuation comment

      
        
        102
        +

      
        
        103
        +2026-02-01 * Balance assertion test

      
        
        104
        +  Expenses:Food  50.00$ = 500.00$

      
        
        105
        +  Assets:Cash  -50.00$ == 500.00$

      
        
        106
        +  Assets:Total  === 0.00$

      
        
        107
        +

      
        
        108
        +2026-03-01 Multi-posting

      
        
        109
        +  Expenses:Food  30.00$

      
        
        110
        +  Expenses:Dining  20.00$

      
        
        111
        +  Assets:Cash  -50.00$

      
        
        112
        +

      
        
        113
        +0000-01-01 test

      
        
        114
        +  a  123.00$

      
        
        115
        +  b

      
        
        116
        +

      
        
        117
        +2026-01-15 * Comments test

      
        
        118
        +  ; header comment

      
        
        119
        +  Expenses:Food  50.00$

      
        
        120
        +  Assets:Cash  -50.00$

      
        
        121
        +  ; inline comment

      
D journal/printer/testdata/indent_width.golden
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -    Expenses:Food  50.00$

      
        3
        
        -    Assets:Cash  -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 Another entry

      
        6
        
        -    Expenses:Food  30.00$

      
        7
        
        -    Assets:Cash  -30.00$

      
D journal/printer/testdata/indent_width.input
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -    Expenses:Food  50.00$

      
        3
        
        -    Assets:Cash  -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 Another entry

      
        6
        
        -    Expenses:Food  30.00$

      
        7
        
        -    Assets:Cash  -30.00$

      
A journal/printer/testdata/indent_width.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-01-15 * Test restaurant

      
        
        3
        +    Expenses:Food  50.00$

      
        
        4
        +    Assets:Cash  -50.00$

      
        
        5
        +

      
        
        6
        +2026-01-16 Another entry

      
        
        7
        +    Expenses:Food  30.00$

      
        
        8
        +    Assets:Cash  -30.00$

      
        
        9
        +

      
        
        10
        +-- expect --

      
        
        11
        +2026-01-15 * Test restaurant

      
        
        12
        +    Expenses:Food  50.00$

      
        
        13
        +    Assets:Cash  -50.00$

      
        
        14
        +

      
        
        15
        +2026-01-16 Another entry

      
        
        16
        +    Expenses:Food  30.00$

      
        
        17
        +    Assets:Cash  -30.00$

      
D journal/printer/testdata/preserve_blanks.golden
···
        1
        
        -; accounts {{{

      
        2
        
        -account assets

      
        3
        
        -account assets:bank

      
        4
        
        -account assets:cash

      
        5
        
        -account equity

      
        6
        
        -account income

      
        7
        
        -account income:salary

      
        8
        
        -account income:invenstment

      
        9
        
        -account expenses

      
        10
        
        -account expenses:education

      
        11
        
        -; }}}

      
        12
        
        -; 01 Jan {{{

      
        13
        
        -2026-01-01 opening balances

      
        14
        
        -  assets:bank  123.00 USD

      
        15
        
        -  assets:cash  1234.12 USD

      
        16
        
        -  equity:open

      
        17
        
        -

      
        18
        
        -2026-01-04 selary

      
        19
        
        -  income:salary  543.22$

      
        20
        
        -  assets:bank

      
        21
        
        -; }}}

      
        22
        
        -; 02 Feb {{{

      
        23
        
        -2026-02-01 internet

      
        24
        
        -  expenses:utilities  350.00 UAH

      
        25
        
        -  assets:bank

      
        26
        
        -

      
        27
        
        -

      
        28
        
        -2026-02-10 * something | 2 months of *income*

      
        29
        
        -  assets:bank  1.50 USD  ; jan

      
        30
        
        -  assets:bank  1.00 USD  ; feb

      
        31
        
        -  income:invenstment

      
D journal/printer/testdata/preserve_blanks.input
···
        1
        
        -; accounts {{{

      
        2
        
        -account assets

      
        3
        
        -account assets:bank

      
        4
        
        -account assets:cash

      
        5
        
        -account equity

      
        6
        
        -account income

      
        7
        
        -account income:salary

      
        8
        
        -account income:invenstment

      
        9
        
        -account expenses

      
        10
        
        -account expenses:education

      
        11
        
        -; }}}

      
        12
        
        -; 01 Jan {{{

      
        13
        
        -2026-01-01 opening balances

      
        14
        
        -  assets:bank  123.00 USD

      
        15
        
        -  assets:cash  1234.12 USD

      
        16
        
        -  equity:open

      
        17
        
        -

      
        18
        
        -2026-01-04 selary

      
        19
        
        -  income:salary  $543.22

      
        20
        
        -  assets:bank

      
        21
        
        -; }}}

      
        22
        
        -; 02 Feb {{{

      
        23
        
        -2026-02-01 internet

      
        24
        
        -  expenses:utilities  350 UAH

      
        25
        
        -  assets:bank

      
        26
        
        -

      
        27
        
        -

      
        28
        
        -2026-02-10 * something | 2 months of *income*

      
        29
        
        -    assets:bank  1.50 USD ; jan

      
        30
        
        -    assets:bank  USD 1 ; feb

      
        31
        
        -    income:invenstment

      
A journal/printer/testdata/preserve_blanks.txtar
···
        
        1
        +-- input --

      
        
        2
        +; accounts {{{

      
        
        3
        +account assets

      
        
        4
        +account assets:bank

      
        
        5
        +account assets:cash

      
        
        6
        +account equity

      
        
        7
        +account income

      
        
        8
        +account income:salary

      
        
        9
        +account income:invenstment

      
        
        10
        +account expenses

      
        
        11
        +account expenses:education

      
        
        12
        +; }}}

      
        
        13
        +; 01 Jan {{{

      
        
        14
        +2026-01-01 opening balances

      
        
        15
        +  assets:bank  123.00 USD

      
        
        16
        +  assets:cash  1234.12 USD

      
        
        17
        +  equity:open

      
        
        18
        +

      
        
        19
        +2026-01-04 selary

      
        
        20
        +  income:salary  $543.22

      
        
        21
        +  assets:bank

      
        
        22
        +; }}}

      
        
        23
        +; 02 Feb {{{

      
        
        24
        +2026-02-01 internet

      
        
        25
        +  expenses:utilities  350 UAH

      
        
        26
        +  assets:bank

      
        
        27
        +

      
        
        28
        +

      
        
        29
        +2026-02-10 * something | 2 months of *income*

      
        
        30
        +    assets:bank  1.50 USD ; jan

      
        
        31
        +    assets:bank  USD 1 ; feb

      
        
        32
        +    income:invenstment

      
        
        33
        +

      
        
        34
        +-- expect --

      
        
        35
        +; accounts {{{

      
        
        36
        +account assets

      
        
        37
        +account assets:bank

      
        
        38
        +account assets:cash

      
        
        39
        +account equity

      
        
        40
        +account income

      
        
        41
        +account income:salary

      
        
        42
        +account income:invenstment

      
        
        43
        +account expenses

      
        
        44
        +account expenses:education

      
        
        45
        +; }}}

      
        
        46
        +; 01 Jan {{{

      
        
        47
        +2026-01-01 opening balances

      
        
        48
        +  assets:bank  123.00 USD

      
        
        49
        +  assets:cash  1234.12 USD

      
        
        50
        +  equity:open

      
        
        51
        +

      
        
        52
        +2026-01-04 selary

      
        
        53
        +  income:salary  543.22$

      
        
        54
        +  assets:bank

      
        
        55
        +; }}}

      
        
        56
        +; 02 Feb {{{

      
        
        57
        +2026-02-01 internet

      
        
        58
        +  expenses:utilities  350.00 UAH

      
        
        59
        +  assets:bank

      
        
        60
        +

      
        
        61
        +

      
        
        62
        +2026-02-10 * something | 2 months of *income*

      
        
        63
        +  assets:bank  1.50 USD  ; jan

      
        
        64
        +  assets:bank  1.00 USD  ; feb

      
        
        65
        +  income:invenstment

      
D journal/printer/testdata/sample.golden
···
        1
        
        -; accounts {{{

      
        2
        
        -account assets

      
        3
        
        -account assets:bank

      
        4
        
        -account assets:cash

      
        5
        
        -account equity

      
        6
        
        -account income

      
        7
        
        -account income:salary

      
        8
        
        -account income:invenstment

      
        9
        
        -account expenses

      
        10
        
        -account expenses:education

      
        11
        
        -; }}}

      
        12
        
        -; 01 Jan {{{

      
        13
        
        -2026-01-01 opening balances

      
        14
        
        -  assets:bank  123.00 USD

      
        15
        
        -  assets:cash  1234.12 USD

      
        16
        
        -  equity:open

      
        17
        
        -

      
        18
        
        -2026-01-04 selary

      
        19
        
        -  income:salary  543.22$

      
        20
        
        -  assets:bank

      
        21
        
        -; }}}

      
        22
        
        -; 02 Feb {{{

      
        23
        
        -2026-02-01 internet

      
        24
        
        -  expenses:utilities  350.00 UAH

      
        25
        
        -  assets:bank

      
        26
        
        -

      
        27
        
        -2026-02-10 * something | 2 months of *income* + idk

      
        28
        
        -  assets:bank  1.50 USD  ; jan

      
        29
        
        -  assets:bank  1.00 USD  ; feb

      
        30
        
        -  income:invenstment

      
D journal/printer/testdata/sample.input
···
        1
        
        -; accounts {{{

      
        2
        
        -account assets

      
        3
        
        -account assets:bank

      
        4
        
        -account assets:cash

      
        5
        
        -account equity

      
        6
        
        -account income

      
        7
        
        -account income:salary

      
        8
        
        -account income:invenstment

      
        9
        
        -account expenses

      
        10
        
        -account expenses:education

      
        11
        
        -; }}}

      
        12
        
        -; 01 Jan {{{

      
        13
        
        -2026-01-01 opening balances

      
        14
        
        -  assets:bank  123.00 USD

      
        15
        
        -  assets:cash  1234.12 USD

      
        16
        
        -  equity:open

      
        17
        
        -

      
        18
        
        -2026-01-04 selary

      
        19
        
        -  income:salary  $543.22

      
        20
        
        -  assets:bank

      
        21
        
        -; }}}

      
        22
        
        -; 02 Feb {{{

      
        23
        
        -2026-02-01 internet

      
        24
        
        -  expenses:utilities  350 UAH

      
        25
        
        -  assets:bank

      
        26
        
        -

      
        27
        
        -

      
        28
        
        -2026-02-10 * something | 2 months of *income* + idk

      
        29
        
        -    assets:bank  1.50 USD ; jan

      
        30
        
        -    assets:bank  USD 1 ; feb

      
        31
        
        -    income:invenstment

      
A journal/printer/testdata/sample.txtar
···
        
        1
        +-- input --

      
        
        2
        +; accounts {{{

      
        
        3
        +account assets

      
        
        4
        +account assets:bank

      
        
        5
        +account assets:cash

      
        
        6
        +account equity

      
        
        7
        +account income

      
        
        8
        +account income:salary

      
        
        9
        +account income:invenstment

      
        
        10
        +account expenses

      
        
        11
        +account expenses:education

      
        
        12
        +; }}}

      
        
        13
        +; 01 Jan {{{

      
        
        14
        +2026-01-01 opening balances

      
        
        15
        +  assets:bank  123.00 USD

      
        
        16
        +  assets:cash  1234.12 USD

      
        
        17
        +  equity:open

      
        
        18
        +

      
        
        19
        +2026-01-04 selary

      
        
        20
        +  income:salary  $543.22

      
        
        21
        +  assets:bank

      
        
        22
        +; }}}

      
        
        23
        +; 02 Feb {{{

      
        
        24
        +2026-02-01 internet

      
        
        25
        +  expenses:utilities  350 UAH

      
        
        26
        +  assets:bank

      
        
        27
        +

      
        
        28
        +

      
        
        29
        +2026-02-10 * something | 2 months of *income* + idk

      
        
        30
        +    assets:bank  1.50 USD ; jan

      
        
        31
        +    assets:bank  USD 1 ; feb

      
        
        32
        +    income:invenstment

      
        
        33
        +

      
        
        34
        +-- expect --

      
        
        35
        +; accounts {{{

      
        
        36
        +account assets

      
        
        37
        +account assets:bank

      
        
        38
        +account assets:cash

      
        
        39
        +account equity

      
        
        40
        +account income

      
        
        41
        +account income:salary

      
        
        42
        +account income:invenstment

      
        
        43
        +account expenses

      
        
        44
        +account expenses:education

      
        
        45
        +; }}}

      
        
        46
        +; 01 Jan {{{

      
        
        47
        +2026-01-01 opening balances

      
        
        48
        +  assets:bank  123.00 USD

      
        
        49
        +  assets:cash  1234.12 USD

      
        
        50
        +  equity:open

      
        
        51
        +

      
        
        52
        +2026-01-04 selary

      
        
        53
        +  income:salary  543.22$

      
        
        54
        +  assets:bank

      
        
        55
        +; }}}

      
        
        56
        +; 02 Feb {{{

      
        
        57
        +2026-02-01 internet

      
        
        58
        +  expenses:utilities  350.00 UAH

      
        
        59
        +  assets:bank

      
        
        60
        +

      
        
        61
        +2026-02-10 * something | 2 months of *income* + idk

      
        
        62
        +  assets:bank  1.50 USD  ; jan

      
        
        63
        +  assets:bank  1.00 USD  ; feb

      
        
        64
        +  income:invenstment

      
D journal/printer/testdata/tab_indent.golden
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -	Expenses:Food  50.00$

      
        3
        
        -	Assets:Cash  -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 Another entry

      
        6
        
        -	Expenses:Food  30.00$

      
        7
        
        -	Assets:Cash  -30.00$

      
D journal/printer/testdata/tab_indent.input
···
        1
        
        -2026-01-15 * Test restaurant

      
        2
        
        -  Expenses:Food  50.00$

      
        3
        
        -  Assets:Cash  -50.00$

      
        4
        
        -

      
        5
        
        -2026-01-16 Another entry

      
        6
        
        -  Expenses:Food  30.00$

      
        7
        
        -  Assets:Cash  -30.00$

      
A journal/printer/testdata/tab_indent.txtar
···
        
        1
        +-- input --

      
        
        2
        +2026-01-15 * Test restaurant

      
        
        3
        +  Expenses:Food  50.00$

      
        
        4
        +  Assets:Cash  -50.00$

      
        
        5
        +

      
        
        6
        +2026-01-16 Another entry

      
        
        7
        +  Expenses:Food  30.00$

      
        
        8
        +  Assets:Cash  -30.00$

      
        
        9
        +

      
        
        10
        +-- expect --

      
        
        11
        +2026-01-15 * Test restaurant

      
        
        12
        +	Expenses:Food  50.00$

      
        
        13
        +	Assets:Cash  -50.00$

      
        
        14
        +

      
        
        15
        +2026-01-16 Another entry

      
        
        16
        +	Expenses:Food  30.00$

      
        
        17
        +	Assets:Cash  -30.00$

      
M journal/semantic/build_test.go
···
        9
        9
         )

      
        10
        10
         

      
        11
        11
         func TestBuild(t *testing.T) {

      
        12
        
        -	tests := map[string][]string{

      
        13
        
        -		"empty":   {},

      
        14
        
        -		"journal": {"journal"},

      
        15
        
        -		"include": {"include0", "include1"},

      
        16
        
        -	}

      
        
        12
        +	tests := []string{"empty", "journal", "include"}

      
        
        13
        +	for _, tt := range tests {

      
        
        14
        +		t.Run(tt, func(t *testing.T) {

      
        
        15
        +			a := golden.Read(t, tt)

      
        
        16
        +			fsys, err := a.FS()

      
        
        17
        +			if err != nil {

      
        
        18
        +				t.Fatal(err)

      
        
        19
        +			}

      
        17
        20
         

      
        18
        
        -	for name, inputs := range tests {

      
        19
        
        -		t.Run(name, func(t *testing.T) {

      
        20
        
        -			var files []*journal.ParsedFile

      
        21
        
        -			for _, in := range inputs {

      
        22
        
        -				l := journal.NewLoader()

      
        23
        
        -				pf, err := l.LoadBytes(in+".journal", golden.Load(t, in))

      
        24
        
        -				if err != nil {

      
        25
        
        -					t.Fatalf("LoadBytes(%q): %v", in, err)

      
        26
        
        -				}

      
        27
        
        -				files = append(files, pf)

      
        
        21
        +			l := journal.NewLoader()

      
        
        22
        +			if _, err := l.LoadFS(fsys, "in.journal"); err != nil {

      
        
        23
        +				t.Fatalf("LoadFS: %v", err)

      
        28
        24
         			}

      
        29
        
        -			ctx := Build(files)

      
        30
        25
         

      
        31
        26
         			var buf bytes.Buffer

      
        32
        
        -			fprint(&buf, ctx)

      
        33
        
        -			golden.Assert(t, name, buf.String())

      
        
        27
        +			fprint(&buf, Build(l.Ordered()))

      
        
        28
        +			golden.Assert(t, a, buf.String())

      
        34
        29
         		})

      
        35
        30
         	}

      
        36
        31
         }

      
D journal/semantic/testdata/empty.golden
···
        1
        
        -files (0):

      
        2
        
        -

      
        3
        
        -commodities (0):

      
        4
        
        -

      
        5
        
        -accounts (0):

      
        6
        
        -

      
        7
        
        -payees (0):

      
A journal/semantic/testdata/empty.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +

      
        
        3
        +-- expect --

      
        
        4
        +files (1):

      
        
        5
        +  0: in.journal

      
        
        6
        +

      
        
        7
        +commodities (0):

      
        
        8
        +

      
        
        9
        +accounts (0):

      
        
        10
        +

      
        
        11
        +payees (0):

      
M journal/semantic/testdata/include.goldenjournal/semantic/testdata/include.txtar
···
        
        1
        +-- accounts.journal --

      
        
        2
        +account expenses:food

      
        
        3
        +

      
        
        4
        +-- in.journal --

      
        
        5
        +include accounts.journal

      
        
        6
        +

      
        
        7
        +2024/01/01 ! test

      
        
        8
        +    expenses:food  $10

      
        
        9
        +    assets:checking

      
        
        10
        +

      
        
        11
        +-- expect --

      
        1
        12
         files (2):

      
        2
        
        -  0: include0.journal

      
        3
        
        -  1: include1.journal

      
        
        13
        +  0: accounts.journal

      
        
        14
        +  1: in.journal

      
        4
        15
         

      
        5
        16
         commodities (1):

      
        6
        17
           $

      
D journal/semantic/testdata/include0.input
···
        1
        
        -account expenses:food

      
D journal/semantic/testdata/include1.input
···
        1
        
        -include include0.journal

      
        2
        
        -

      
        3
        
        -2024/01/01 ! test

      
        4
        
        -    expenses:food  $10

      
        5
        
        -    assets:checking

      
M journal/semantic/testdata/journal.goldenjournal/semantic/testdata/journal.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +account expenses:food

      
        
        3
        +account expenses:food

      
        
        4
        +account expenses:transport

      
        
        5
        +

      
        
        6
        +commodity $

      
        
        7
        +commodity $

      
        
        8
        +commodity EUR

      
        
        9
        +

      
        
        10
        +~ monthly

      
        
        11
        +    expenses:transport  $50

      
        
        12
        +    assets:checking

      
        
        13
        +

      
        
        14
        += expr:big

      
        
        15
        +    expenses:food  $100

      
        
        16
        +    assets:credit

      
        
        17
        +

      
        
        18
        +2024/01/01 groceries

      
        
        19
        +    expenses:food  $10

      
        
        20
        +    assets:checking

      
        
        21
        +

      
        
        22
        +2024/01/02 lunch

      
        
        23
        +    expenses:food  $5

      
        
        24
        +    assets:cash

      
        
        25
        +

      
        
        26
        +2024/01/04 aws

      
        
        27
        +    expenses:food  1000000.00 USD

      
        
        28
        +    assets:cash

      
        
        29
        +

      
        
        30
        +-- expect --

      
        1
        31
         files (1):

      
        2
        
        -  0: journal.journal

      
        
        32
        +  0: in.journal

      
        3
        33
         

      
        4
        34
         commodities (3):

      
        5
        35
           $

      
D journal/semantic/testdata/journal.input
···
        1
        
        -account expenses:food

      
        2
        
        -account expenses:food

      
        3
        
        -account expenses:transport

      
        4
        
        -

      
        5
        
        -commodity $

      
        6
        
        -commodity $

      
        7
        
        -commodity EUR

      
        8
        
        -

      
        9
        
        -~ monthly

      
        10
        
        -    expenses:transport  $50

      
        11
        
        -    assets:checking

      
        12
        
        -

      
        13
        
        -= expr:big

      
        14
        
        -    expenses:food  $100

      
        15
        
        -    assets:credit

      
        16
        
        -

      
        17
        
        -2024/01/01 groceries

      
        18
        
        -    expenses:food  $10

      
        19
        
        -    assets:checking

      
        20
        
        -

      
        21
        
        -2024/01/02 lunch

      
        22
        
        -    expenses:food  $5

      
        23
        
        -    assets:cash

      
        24
        
        -

      
        25
        
        -2024/01/04 aws

      
        26
        
        -    expenses:food  1000000.00 USD

      
        27
        
        -    assets:cash

      
M journal/tags/tag_test.go
···
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"bytes"

      
        5
        
        -	"path/filepath"

      
        6
        5
         	"testing"

      
        7
        6
         

      
        8
        
        -	"olexsmir.xyz/clerk/internal/testutil"

      
        9
        7
         	"olexsmir.xyz/clerk/internal/testutil/golden"

      
        10
        8
         	"olexsmir.xyz/clerk/journal"

      
        11
        9
         )

      
        12
        10
         

      
        13
        
        -func TestTagsGeneration(t *testing.T) {

      
        14
        
        -	tests := []string{"basic", "kinds", "duplicates"}

      
        
        11
        +func TestTags(t *testing.T) {

      
        
        12
        +	tests := []string{"basic", "kinds", "duplicates", "crossfile"}

      
        15
        13
         	for _, tt := range tests {

      
        16
        14
         		t.Run(tt, func(t *testing.T) {

      
        17
        
        -			inp := golden.Load(t, tt)

      
        18
        
        -			out := generateTags(t, inp, tt+".journal")

      
        19
        
        -			golden.Assert(t, tt, out)

      
        
        15
        +			t.Parallel()

      
        
        16
        +			a := golden.Read(t, tt)

      
        
        17
        +			fsys, err := a.FS()

      
        
        18
        +			if err != nil {

      
        
        19
        +				t.Fatal(err)

      
        
        20
        +			}

      
        
        21
        +			l := journal.NewLoader()

      
        
        22
        +			if _, err := l.LoadFS(fsys, "in.journal"); err != nil {

      
        
        23
        +				t.Fatalf("loading journal: %v", err)

      
        
        24
        +			}

      
        
        25
        +			tagger := New(l, "")

      
        
        26
        +			var buf bytes.Buffer

      
        
        27
        +			if err := tagger.Write(&buf); err != nil {

      
        
        28
        +				t.Fatal(err)

      
        
        29
        +			}

      
        
        30
        +			golden.Assert(t, a, buf.String())

      
        20
        31
         		})

      
        21
        32
         	}

      
        22
        33
         }

      
        23
        
        -

      
        24
        
        -func TestCrossFileResolution(t *testing.T) {

      
        25
        
        -	t.Parallel()

      
        26
        
        -	tmp := t.TempDir()

      
        27
        
        -

      
        28
        
        -	declSrc := golden.Load(t, "crossfile1")

      
        29
        
        -	jrnlSrc := golden.Load(t, "crossfile2")

      
        30
        
        -	testutil.WriteFile(t, filepath.Join(tmp, "decls.journal"), declSrc)

      
        31
        
        -	testutil.WriteFile(t, filepath.Join(tmp, "2026.journal"), jrnlSrc)

      
        32
        
        -

      
        33
        
        -	loader := journal.NewLoader()

      
        34
        
        -	if _, err := loader.Load(filepath.Join(tmp, "2026.journal")); err != nil {

      
        35
        
        -		t.Fatal(err)

      
        36
        
        -	}

      
        37
        
        -

      
        38
        
        -	tagger := New(loader, tmp)

      
        39
        
        -	var buf bytes.Buffer

      
        40
        
        -	if err := tagger.Write(&buf); err != nil {

      
        41
        
        -		t.Fatal(err)

      
        42
        
        -	}

      
        43
        
        -

      
        44
        
        -	golden.Assert(t, "crossfile", buf.String())

      
        45
        
        -}

      
        46
        
        -

      
        47
        
        -func generateTags(t *testing.T, src []byte, fname string) string {

      
        48
        
        -	t.Helper()

      
        49
        
        -

      
        50
        
        -	l := journal.NewLoader()

      
        51
        
        -	_, err := l.LoadBytes(fname, src)

      
        52
        
        -	if err != nil {

      
        53
        
        -		t.Fatalf("loading journal: %v", err)

      
        54
        
        -	}

      
        55
        
        -	absPath, _ := filepath.Abs(fname)

      
        56
        
        -	tagger := New(l, filepath.Dir(absPath))

      
        57
        
        -	var buf bytes.Buffer

      
        58
        
        -	if err := tagger.Write(&buf); err != nil {

      
        59
        
        -		t.Fatalf("writing tags: %v", err)

      
        60
        
        -	}

      
        61
        
        -	return buf.String()

      
        62
        
        -}

      
D journal/tags/testdata/basic.golden
···
        1
        
        -!_TAG_FILE_FORMAT	2	/extended format/

      
        2
        
        -!_TAG_FILE_SORTED	1	/1=sorted/

      
        3
        
        -!_TAG_PROGRAM_NAME	clerk	//

      
        4
        
        -!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        5
        
        -!_TAG_PROGRAM_VERSION	0.1.0	//

      
        6
        
        -!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        7
        
        -!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        8
        
        -!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        9
        
        -!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        10
        
        -$	basic.journal	/^P 2025-01-15 € $1.05$/;"	kind:c	line:16	language:hledger

      
        11
        
        -Groceries	basic.journal	/^2025-01-15 * "Groceries"$/;"	kind:p	line:8	language:hledger

      
        12
        
        -Rent	basic.journal	/^2025-02-01 "Rent"$/;"	kind:p	line:12	language:hledger

      
        13
        
        -assets:bank	basic.journal	/^account assets:bank$/;"	kind:a	line:5	language:hledger

      
        14
        
        -assets:cash	basic.journal	/^account assets:cash$/;"	kind:a	line:6	language:hledger

      
        15
        
        -expenses:food	basic.journal	/^account expenses:food$/;"	kind:a	line:3	language:hledger

      
        16
        
        -expenses:rent	basic.journal	/^account expenses:rent$/;"	kind:a	line:4	language:hledger

      
        17
        
        -€	basic.journal	/^P 2025-01-15 € $1.05$/;"	kind:c	line:16	language:hledger

      
D journal/tags/testdata/basic.input
···
        1
        
        -; basic journal — accounts + commodities from directives and postings

      
        2
        
        -

      
        3
        
        -account expenses:food

      
        4
        
        -account expenses:rent

      
        5
        
        -account assets:bank

      
        6
        
        -account assets:cash

      
        7
        
        -

      
        8
        
        -2025-01-15 * "Groceries"

      
        9
        
        -    expenses:food   $50.00

      
        10
        
        -    assets:bank

      
        11
        
        -

      
        12
        
        -2025-02-01 "Rent"

      
        13
        
        -    expenses:rent   €800.00

      
        14
        
        -    assets:bank     €-800.00

      
        15
        
        -

      
        16
        
        -P 2025-01-15 € $1.05

      
A journal/tags/testdata/basic.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +; basic journal — accounts + commodities from directives and postings

      
        
        3
        +

      
        
        4
        +account expenses:food

      
        
        5
        +account expenses:rent

      
        
        6
        +account assets:bank

      
        
        7
        +account assets:cash

      
        
        8
        +

      
        
        9
        +2025-01-15 * "Groceries"

      
        
        10
        +    expenses:food   $50.00

      
        
        11
        +    assets:bank

      
        
        12
        +

      
        
        13
        +2025-02-01 "Rent"

      
        
        14
        +    expenses:rent   €800.00

      
        
        15
        +    assets:bank     €-800.00

      
        
        16
        +

      
        
        17
        +P 2025-01-15 € $1.05

      
        
        18
        +

      
        
        19
        +-- expect --

      
        
        20
        +!_TAG_FILE_FORMAT	2	/extended format/

      
        
        21
        +!_TAG_FILE_SORTED	1	/1=sorted/

      
        
        22
        +!_TAG_PROGRAM_NAME	clerk	//

      
        
        23
        +!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        
        24
        +!_TAG_PROGRAM_VERSION	0.1.0	//

      
        
        25
        +!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        
        26
        +!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        
        27
        +!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        
        28
        +!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        
        29
        +$	in.journal	/^P 2025-01-15 € $1.05$/;"	kind:c	line:16	language:hledger

      
        
        30
        +Groceries	in.journal	/^2025-01-15 * "Groceries"$/;"	kind:p	line:8	language:hledger

      
        
        31
        +Rent	in.journal	/^2025-02-01 "Rent"$/;"	kind:p	line:12	language:hledger

      
        
        32
        +assets:bank	in.journal	/^account assets:bank$/;"	kind:a	line:5	language:hledger

      
        
        33
        +assets:cash	in.journal	/^account assets:cash$/;"	kind:a	line:6	language:hledger

      
        
        34
        +expenses:food	in.journal	/^account expenses:food$/;"	kind:a	line:3	language:hledger

      
        
        35
        +expenses:rent	in.journal	/^account expenses:rent$/;"	kind:a	line:4	language:hledger

      
        
        36
        +€	in.journal	/^P 2025-01-15 € $1.05$/;"	kind:c	line:16	language:hledger

      
D journal/tags/testdata/crossfile.golden
···
        1
        
        -!_TAG_FILE_FORMAT	2	/extended format/

      
        2
        
        -!_TAG_FILE_SORTED	1	/1=sorted/

      
        3
        
        -!_TAG_PROGRAM_NAME	clerk	//

      
        4
        
        -!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        5
        
        -!_TAG_PROGRAM_VERSION	0.1.0	//

      
        6
        
        -!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        7
        
        -!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        8
        
        -!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        9
        
        -!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        10
        
        -$	decls.journal	/^commodity $$/;"	kind:c	line:1	language:hledger

      
        11
        
        -Amazon	2026.journal	/^2024-01-15 "Amazon"$/;"	kind:p	line:3	language:hledger

      
        12
        
        -assets:bank	decls.journal	/^account assets:bank$/;"	kind:a	line:2	language:hledger

      
        13
        
        -expenses:food	2026.journal	/^    expenses:food   $50.00$/;"	kind:a	line:4	language:hledger

      
A journal/tags/testdata/crossfile.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +include decls.journal

      
        
        3
        +

      
        
        4
        +2024-01-15 "Amazon"

      
        
        5
        +    expenses:food   $50.00

      
        
        6
        +    assets:bank

      
        
        7
        +

      
        
        8
        +-- decls.journal --

      
        
        9
        +commodity $

      
        
        10
        +account assets:bank

      
        
        11
        +payee Amazon

      
        
        12
        +

      
        
        13
        +-- expect --

      
        
        14
        +!_TAG_FILE_FORMAT	2	/extended format/

      
        
        15
        +!_TAG_FILE_SORTED	1	/1=sorted/

      
        
        16
        +!_TAG_PROGRAM_NAME	clerk	//

      
        
        17
        +!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        
        18
        +!_TAG_PROGRAM_VERSION	0.1.0	//

      
        
        19
        +!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        
        20
        +!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        
        21
        +!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        
        22
        +!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        
        23
        +$	decls.journal	/^commodity $$/;"	kind:c	line:1	language:hledger

      
        
        24
        +Amazon	in.journal	/^2024-01-15 "Amazon"$/;"	kind:p	line:3	language:hledger

      
        
        25
        +assets:bank	decls.journal	/^account assets:bank$/;"	kind:a	line:2	language:hledger

      
        
        26
        +expenses:food	in.journal	/^    expenses:food   $50.00$/;"	kind:a	line:4	language:hledger

      
D journal/tags/testdata/crossfile1.input
···
        1
        
        -commodity $

      
        2
        
        -account assets:bank

      
        3
        
        -payee Amazon

      
D journal/tags/testdata/crossfile2.input
···
        1
        
        -include decls.journal

      
        2
        
        -

      
        3
        
        -2024-01-15 "Amazon"

      
        4
        
        -    expenses:food   $50.00

      
        5
        
        -    assets:bank

      
D journal/tags/testdata/duplicates.golden
···
        1
        
        -!_TAG_FILE_FORMAT	2	/extended format/

      
        2
        
        -!_TAG_FILE_SORTED	1	/1=sorted/

      
        3
        
        -!_TAG_PROGRAM_NAME	clerk	//

      
        4
        
        -!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        5
        
        -!_TAG_PROGRAM_VERSION	0.1.0	//

      
        6
        
        -!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        7
        
        -!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        8
        
        -!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        9
        
        -!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        10
        
        -another	duplicates.journal	/^2025-02-01 "another"$/;"	kind:p	line:9	language:hledger

      
        11
        
        -other	duplicates.journal	/^    other$/;"	kind:a	line:7	language:hledger

      
        12
        
        -test	duplicates.journal	/^account test$/;"	kind:a	line:3	language:hledger

      
        13
        
        -test txn	duplicates.journal	/^2025-01-01 "test txn"$/;"	kind:p	line:5	language:hledger

      
D journal/tags/testdata/duplicates.input
···
        1
        
        -; same account name in directive AND postings — should use directive line

      
        2
        
        -

      
        3
        
        -account test

      
        4
        
        -

      
        5
        
        -2025-01-01 "test txn"

      
        6
        
        -    test  1

      
        7
        
        -    other

      
        8
        
        -

      
        9
        
        -2025-02-01 "another"

      
        10
        
        -    test  2

      
        11
        
        -    other

      
A journal/tags/testdata/duplicates.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +; same account name in directive AND postings — should use directive line

      
        
        3
        +

      
        
        4
        +account test

      
        
        5
        +

      
        
        6
        +2025-01-01 "test txn"

      
        
        7
        +    test  1

      
        
        8
        +    other

      
        
        9
        +

      
        
        10
        +2025-02-01 "another"

      
        
        11
        +    test  2

      
        
        12
        +    other

      
        
        13
        +

      
        
        14
        +-- expect --

      
        
        15
        +!_TAG_FILE_FORMAT	2	/extended format/

      
        
        16
        +!_TAG_FILE_SORTED	1	/1=sorted/

      
        
        17
        +!_TAG_PROGRAM_NAME	clerk	//

      
        
        18
        +!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        
        19
        +!_TAG_PROGRAM_VERSION	0.1.0	//

      
        
        20
        +!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        
        21
        +!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        
        22
        +!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        
        23
        +!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        
        24
        +another	in.journal	/^2025-02-01 "another"$/;"	kind:p	line:9	language:hledger

      
        
        25
        +other	in.journal	/^    other$/;"	kind:a	line:7	language:hledger

      
        
        26
        +test	in.journal	/^account test$/;"	kind:a	line:3	language:hledger

      
        
        27
        +test txn	in.journal	/^2025-01-01 "test txn"$/;"	kind:p	line:5	language:hledger

      
D journal/tags/testdata/kinds.golden
···
        1
        
        -!_TAG_FILE_FORMAT	2	/extended format/

      
        2
        
        -!_TAG_FILE_SORTED	1	/1=sorted/

      
        3
        
        -!_TAG_PROGRAM_NAME	clerk	//

      
        4
        
        -!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        5
        
        -!_TAG_PROGRAM_VERSION	0.1.0	//

      
        6
        
        -!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        7
        
        -!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        8
        
        -!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        9
        
        -!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        10
        
        -$	kinds.journal	/^commodity $$/;"	kind:c	line:4	language:hledger

      
        11
        
        -Amazon	kinds.journal	/^2025-03-01 * "Amazon"$/;"	kind:p	line:9	language:hledger

      
        12
        
        -EUR	kinds.journal	/^commodity EUR$/;"	kind:c	line:5	language:hledger

      
        13
        
        -assets:bank	kinds.journal	/^    assets:bank$/;"	kind:a	line:11	language:hledger

      
        14
        
        -expenses:food	kinds.journal	/^account expenses:food$/;"	kind:a	line:3	language:hledger

      
        15
        
        -receipt	kinds.journal	/^tag receipt$/;"	kind:t	line:7	language:hledger

      
D journal/tags/testdata/kinds.input
···
        1
        
        -; all four tag kinds via directives

      
        2
        
        -

      
        3
        
        -account expenses:food

      
        4
        
        -commodity $

      
        5
        
        -commodity EUR

      
        6
        
        -payee Amazon

      
        7
        
        -tag receipt

      
        8
        
        -

      
        9
        
        -2025-03-01 * "Amazon"

      
        10
        
        -    expenses:food   $45.00

      
        11
        
        -    assets:bank

      
A journal/tags/testdata/kinds.txtar
···
        
        1
        +-- in.journal --

      
        
        2
        +; all four tag kinds via directives

      
        
        3
        +

      
        
        4
        +account expenses:food

      
        
        5
        +commodity $

      
        
        6
        +commodity EUR

      
        
        7
        +payee Amazon

      
        
        8
        +tag receipt

      
        
        9
        +

      
        
        10
        +2025-03-01 * "Amazon"

      
        
        11
        +    expenses:food   $45.00

      
        
        12
        +    assets:bank

      
        
        13
        +

      
        
        14
        +-- expect --

      
        
        15
        +!_TAG_FILE_FORMAT	2	/extended format/

      
        
        16
        +!_TAG_FILE_SORTED	1	/1=sorted/

      
        
        17
        +!_TAG_PROGRAM_NAME	clerk	//

      
        
        18
        +!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        
        19
        +!_TAG_PROGRAM_VERSION	0.1.0	//

      
        
        20
        +!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        
        21
        +!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        
        22
        +!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        
        23
        +!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        
        24
        +$	in.journal	/^commodity $$/;"	kind:c	line:4	language:hledger

      
        
        25
        +Amazon	in.journal	/^2025-03-01 * "Amazon"$/;"	kind:p	line:9	language:hledger

      
        
        26
        +EUR	in.journal	/^commodity EUR$/;"	kind:c	line:5	language:hledger

      
        
        27
        +assets:bank	in.journal	/^    assets:bank$/;"	kind:a	line:11	language:hledger

      
        
        28
        +expenses:food	in.journal	/^account expenses:food$/;"	kind:a	line:3	language:hledger

      
        
        29
        +receipt	in.journal	/^tag receipt$/;"	kind:t	line:7	language:hledger

      
D journal/tags/testdata/pseudo.golden
···
        1
        
        -!_TAG_FILE_FORMAT	2	/extended format/

      
        2
        
        -!_TAG_FILE_SORTED	1	/1=sorted/

      
        3
        
        -!_TAG_PROGRAM_NAME	clerk	//

      
        4
        
        -!_TAG_PROGRAM_URL	https://olexsmir.xyz/clerk	//

      
        5
        
        -!_TAG_PROGRAM_VERSION	0.1.0	//

      
        6
        
        -!_TAG_KIND_DESCRIPTION!a	a,account	/account/

      
        7
        
        -!_TAG_KIND_DESCRIPTION!c	c,commodity	/commodity/

      
        8
        
        -!_TAG_KIND_DESCRIPTION!p	p,payee	/payee/

      
        9
        
        -!_TAG_KIND_DESCRIPTION!t	t,tag	/tag/

      
        10
        
        -a	pseudo.journal	/^account a$/;"	kind:a	line:1	language:hledger