7 files changed,
108 insertions(+),
41 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-06-08 21:10:56 +0300
Authored at:
2026-06-08 20:15:32 +0300
Change ID:
spxltvqklvtrnvqnnmulzlzkwnyssvzy
Parent:
263a106
jump to
M
journal/printer/printer.go
··· 26 26 ) 27 27 28 28 type Config struct { 29 - TabIndent bool // true = tabs, false = spaces 30 - IndentWidth int // spaces per indent level (default: 2) 31 - AlignStyle AlignStyle // (default AlignTwoSpaces) 32 - AlignColumn int // fixed column for AlignRight 33 - CommodityPos CommodityPos // where to place commodity 29 + TabIndent bool // true = tabs, false = spaces 30 + IndentWidth int // spaces per indent level (default: 2) 31 + PreserveBlankLines bool // preserve consecutive blank lines as-is 32 + AlignStyle AlignStyle // (default AlignTwoSpaces) 33 + AlignColumn int // fixed column for AlignRight 34 + CommodityPos CommodityPos // where to place commodity 34 35 } 35 36 36 37 var defaultConfig = &Config{ 37 - TabIndent: false, 38 - IndentWidth: 2, 39 - AlignStyle: AlignTwoSpaces, 40 - AlignColumn: 70, 41 - CommodityPos: CommodityAfter, 38 + TabIndent: false, 39 + IndentWidth: 2, 40 + PreserveBlankLines: false, 41 + AlignStyle: AlignTwoSpaces, 42 + AlignColumn: 70, 43 + CommodityPos: CommodityAfter, 42 44 } 43 45 44 46 func (c *Config) indent() string { ··· 54 56 55 57 // printer holds formatting state for a single Fprint call. 56 58 type printer struct { 57 - buf strings.Builder 58 - cfg *Config 59 - indent string 59 + buf strings.Builder 60 + cfg *Config 61 + indent string 62 + prevWasBlank bool 60 63 } 61 64 62 65 // Fprint formats using the default config. ··· 80 83 } 81 84 82 85 // FprintEntry formats a single ast entry using the default config. 83 -func FprintEntry(w io.Writer, e ast.Entry) error { 84 - return defaultConfig.FprintEntry(w, e) 85 -} 86 +func FprintEntry(w io.Writer, e ast.Entry) error { return defaultConfig.FprintEntry(w, e) } 86 87 87 88 // FprintEntry formats a single journal entry. 88 89 func (c *Config) FprintEntry(w io.Writer, e ast.Entry) error { ··· 97 98 98 99 func (p *printer) formatEntry(e ast.Entry) { 99 100 switch e := e.(type) { 101 + case *ast.BlankLine: 102 + if !p.prevWasBlank || p.cfg.PreserveBlankLines { 103 + p.buf.WriteByte('\n') 104 + } 105 + p.prevWasBlank = true 106 + return 100 107 case *ast.Transaction: 108 + p.prevWasBlank = false 101 109 p.writeTransaction(e) 102 110 return 103 111 case *ast.PeriodicTransaction: 112 + p.prevWasBlank = false 104 113 p.writePeriodicTransaction(e) 105 114 return 106 115 case *ast.AutomatedTransaction: 116 + p.prevWasBlank = false 107 117 p.writeAutomatedTransaction(e) 108 118 return 109 - case *ast.BlankLine: 110 - p.buf.WriteByte('\n') 111 - return 119 + 112 120 case *ast.IgnoredDirective: 113 121 return // TODO: 114 122 ··· 145 153 default: 146 154 fmt.Fprintf(&p.buf, "; unknown entry %T", e) 147 155 } 156 + p.prevWasBlank = false 148 157 p.buf.WriteByte('\n') 149 158 } 150 159
M
journal/printer/printer_test.go
··· 8 8 "olexsmir.xyz/clerk/journal" 9 9 ) 10 10 11 +var tests = []string{"entries", "directives", "sample"} 12 + 11 13 func TestRoundTrip(t *testing.T) { 12 - tests := []string{"entries", "directives", "sample"} 13 14 for _, tname := range tests { 14 15 t.Run(tname, func(t *testing.T) { 15 16 inp := golden.Load(t, tname) ··· 29 30 } 30 31 31 32 func BenchmarkPrinter(b *testing.B) { 32 - for _, tname := range []string{"entries", "directives", "sample"} { 33 + for _, tname := range tests { 33 34 b.Run(tname, func(b *testing.B) { 34 35 b.ReportAllocs() 35 36 inp := golden.Load(b, tname) ··· 48 49 } 49 50 } 50 51 52 +var testsWithConfig = map[string]*Config{ 53 + "align_right": {AlignStyle: AlignRight, AlignColumn: 50}, 54 + "align_tab": {AlignStyle: AlignTab, AlignColumn: 50}, 55 + "commodity_before": {CommodityPos: CommodityBefore}, 56 + "preserve_blanks": {PreserveBlankLines: true}, 57 + "tab_indent": {TabIndent: true}, 58 + "indent_width": {IndentWidth: 4}, 59 +} 60 + 51 61 func TestRoundTrip_WithConfig(t *testing.T) { 52 - tests := map[string]*Config{ 53 - "align_right": {AlignStyle: AlignRight, AlignColumn: 50}, 54 - "align_tab": {AlignStyle: AlignTab, AlignColumn: 50}, 55 - "commodity_before": {CommodityPos: CommodityBefore}, 56 - "tab_indent": {TabIndent: true}, 57 - "indent_width": {IndentWidth: 4}, 58 - } 59 - for tname, tt := range tests { 62 + for tname, tt := range testsWithConfig { 60 63 t.Run(tname, func(t *testing.T) { 61 64 inp := golden.Load(t, tname) 62 65 pf, err := journal.NewLoader().LoadBytes(tname+".journal", inp) ··· 74 77 } 75 78 76 79 func BenchmarkPrinter_Config(b *testing.B) { 77 - configs := map[string]*Config{ 78 - "default": defaultConfig, 79 - "align_right": {AlignStyle: AlignRight, AlignColumn: 50}, 80 - "align_tab": {AlignStyle: AlignTab, AlignColumn: 50}, 81 - "commodity_before": {CommodityPos: CommodityBefore}, 82 - "tab_indent": {TabIndent: true}, 83 - "indent_width": {IndentWidth: 4}, 84 - } 85 80 inp := golden.Load(b, "entries") 86 81 pf, err := journal.NewLoader().LoadBytes("entries.journal", inp) 87 82 if err != nil { 88 83 b.Fatal(err) 89 84 } 90 - for name, cfg := range configs { 85 + for name, cfg := range testsWithConfig { 91 86 b.Run(name, func(b *testing.B) { 92 87 b.ReportAllocs() 93 88 b.ResetTimer()
A
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
A
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