all repos

clerk @ 6fdb9097048e212574439fb0da84d0c94aa7e01b

missing tooling for ledger/hledger

clerk/journal/printer/printer.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
formatter, 17 hours ago
1
package printer
2
3
import (
4
	"fmt"
5
	"io"
6
	"strings"
7
8
	"olexsmir.xyz/clerk/journal/ast"
9
)
10
11
// AlignStyle controls how postings are aligned
12
type AlignStyle int
13
14
const (
15
	AlignTwoSpaces AlignStyle = iota // "  Account  $10.00"
16
	AlignRight                       // amounts at fixed col: "  Account         $10.00"
17
	AlignTab                         // elastic tabstops
18
)
19
20
// CommodityPos controls where the commodity marker is placed
21
type CommodityPos int
22
23
const (
24
	CommodityAfter  CommodityPos = iota // "10.00 EUR"
25
	CommodityBefore                     // "$10.00"
26
)
27
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
	CommentWidth int          // wrap standalone comments at this width
34
	CommodityPos CommodityPos // where to place commodity
35
}
36
37
var defaultConfig = &Config{
38
	TabIndent:    false,
39
	IndentWidth:  2,
40
	AlignStyle:   AlignTwoSpaces,
41
	AlignColumn:  70,
42
	CommentWidth: 90,
43
	CommodityPos: CommodityAfter,
44
}
45
46
func (c *Config) indent() string {
47
	if c.TabIndent {
48
		return "\t"
49
	}
50
	n := 2
51
	if c.IndentWidth > 0 {
52
		n = c.IndentWidth
53
	}
54
	return spaces(n)
55
}
56
57
// printer holds formatting state for a single Fprint call.
58
type printer struct {
59
	buf    strings.Builder
60
	cfg    *Config
61
	indent string
62
}
63
64
// Fprint formats using the default config.
65
func Fprint(w io.Writer, j *ast.Journal) error { return defaultConfig.Fprint(w, j) }
66
67
// Fprint formats a parsed journal.
68
func (c *Config) Fprint(w io.Writer, j *ast.Journal) error {
69
	if c == nil {
70
		c = defaultConfig
71
	}
72
	p := printer{cfg: c, indent: c.indent()}
73
74
	for _, e := range j.Entries {
75
		p.formatEntry(e)
76
	}
77
78
	// allow exactly one trailing newline
79
	out := strings.TrimRight(p.buf.String(), "\n") + "\n"
80
	_, err := io.WriteString(w, out)
81
	return err
82
}
83
84
func (p *printer) formatEntry(e ast.Entry) {
85
	switch e := e.(type) {
86
	case *ast.Transaction:
87
		p.writeTransaction(e)
88
		return
89
	case *ast.PeriodicTransaction:
90
		p.writePeriodicTransaction(e)
91
		return
92
	case *ast.AutomatedTransaction:
93
		p.writeAutomatedTransaction(e)
94
		return
95
	case *ast.BlankLine:
96
		p.buf.WriteByte('\n')
97
		return
98
99
	case *ast.Comment:
100
		p.writeComment(e)
101
	case *ast.AccountDirective:
102
		p.writeAccountDirective(e)
103
	case *ast.CommodityDirective:
104
		p.writeCommodityDirective(e)
105
	case *ast.IncludeDirective:
106
		p.writeIncludeDirective(e)
107
	case *ast.AliasDirective:
108
		p.writeAliasDirective(e)
109
	case *ast.PayeeDirective:
110
		p.writePayeeDirective(e)
111
	case *ast.TagDirective:
112
		p.writeTagDirective(e)
113
	case *ast.YearDirective:
114
		p.writeYearDirective(e)
115
	case *ast.DecimalMarkDirective:
116
		p.writeDecimalMarkDirective(e)
117
	case *ast.MarketPriceDirective:
118
		p.writeMarketPriceDirective(e)
119
	case *ast.ConversionDirective:
120
		p.writeConversionDirective(e)
121
	case *ast.DefaultCommodityDirective:
122
		p.writeDefaultCommodityDirective(e)
123
	case *ast.ApplyDirective:
124
		p.writeApplyDirective(e)
125
	case *ast.EndDirective:
126
		p.writeEndDirective(e)
127
	case *ast.CommentBlockDirective:
128
		p.writeCommentBlockDirective(e)
129
	case *ast.IgnoredDirective:
130
		return // TODO:
131
	default:
132
		fmt.Fprintf(&p.buf, "; unknown entry %T", e)
133
	}
134
	p.buf.WriteByte('\n')
135
}
136
137
func (p *printer) writeSpaces(n int) {
138
	for range n {
139
		p.buf.WriteByte(' ')
140
	}
141
}
142
143
func (p *printer) writeComment(c *ast.Comment) {
144
	if c != nil && c.Text != "" {
145
		p.buf.WriteByte(c.Marker) // TODO: support other markers
146
		p.buf.WriteByte(' ')
147
		p.buf.WriteString(c.Text)
148
	}
149
}
150
151
func (p *printer) writeInlineComment(c *ast.Comment) {
152
	if c != nil && c.Text != "" {
153
		p.buf.WriteByte(' ')
154
		p.buf.WriteByte(c.Marker) // TODO: support other markers
155
		p.buf.WriteByte(' ')
156
		p.buf.WriteString(c.Text)
157
	}
158
}
159
160
func spaces(n int) string {
161
	b := make([]byte, n)
162
	for i := range b {
163
		b[i] = ' '
164
	}
165
	return string(b)
166
}