all repos

clerk @ d12c079

missing tooling for ledger/hledger

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
linter: performance; resolve cwd once per flush, buffer output, 13 days ago
1
package ast
2
3
import (
4
	"fmt"
5
	"strings"
6
	"time"
7
8
	"olexsmir.xyz/clerk/journal/token"
9
)
10
11
type Journal struct {
12
	Entries []Entry
13
	Errors  []*ParseError
14
}
15
16
type Entry interface {
17
	entryNode()
18
}
19
20
type ParseError struct {
21
	Span    token.Span
22
	Message string
23
}
24
25
type FileError struct {
26
	Path    string
27
	Span    token.Span
28
	Message string
29
}
30
31
type Date struct {
32
	Year, Month, Day int
33
	Sep              byte // '-' '/' '.'
34
	Span             token.Span
35
}
36
37
// DateOf builds a Date from t, sets '-' as the separator. Helper for handbuilding ast.
38
func DateOf(t time.Time) Date {
39
	return Date{
40
		Year:  t.Year(),
41
		Month: int(t.Month()),
42
		Day:   t.Day(),
43
		Sep:   '-',
44
	}
45
}
46
47
// Compare returns -1 if d is before other, 0 if equal, 1 if after.
48
func (d Date) Compare(other Date) int {
49
	if d.Year != other.Year {
50
		if d.Year < other.Year {
51
			return -1
52
		}
53
		return 1
54
	}
55
	if d.Month != other.Month {
56
		if d.Month < other.Month {
57
			return -1
58
		}
59
		return 1
60
	}
61
	if d.Day != other.Day {
62
		if d.Day < other.Day {
63
			return -1
64
		}
65
		return 1
66
	}
67
	return 0
68
}
69
70
// String renders d as written: with its own separator, and without the year
71
// when the date carried none. Returns "" for an unset or invalid date.
72
func (d Date) String() string {
73
	if (d.Year == 0 && d.Month == 0 && d.Day == 0) ||
74
		(d.Month < 1 || d.Month > 12 || d.Day < 1 || d.Day > 31) {
75
		return ""
76
	}
77
	sep := d.Sep
78
	if sep == 0 {
79
		sep = '-'
80
	}
81
	// NOTE(perf): replace sprint with strings.Builder or other kind of buffer
82
	if d.Year == 0 {
83
		return fmt.Sprintf("%d%c%d", d.Month, sep, d.Day)
84
	}
85
	return fmt.Sprintf("%04d%c%02d%c%02d", d.Year, sep, d.Month, sep, d.Day)
86
}
87
88
type Time struct {
89
	Hour, Minute, Second int
90
	Span                 token.Span
91
}
92
93
type DateTime struct {
94
	Date Date
95
	Time *Time
96
	Span token.Span
97
}
98
99
type Tag struct {
100
	Key, Value string
101
	Span       token.Span
102
}
103
104
type Comment struct {
105
	Marker byte // ';' '#' '%' '*'
106
	Tags   []Tag
107
	Text   string
108
	Span   token.Span
109
}
110
111
func (Comment) entryNode() {}
112
113
type StatusType int
114
115
func (s StatusType) String() string {
116
	switch s {
117
	case StatusCleared:
118
		return "*"
119
	case StatusPending:
120
		return "!"
121
	case StatusNone:
122
		return ""
123
	default:
124
		panic("unreachable")
125
	}
126
}
127
128
const (
129
	StatusNone    StatusType = iota // not set
130
	StatusCleared                   // * cleared
131
	StatusPending                   // ! pending
132
)
133
134
type SubAccount struct {
135
	Name string
136
	Span token.Span
137
}
138
139
type Account struct {
140
	Name []SubAccount // ['expenses' 'food']
141
	Span token.Span
142
}
143
144
// AccountFromString splits s on ':' into subaccounts. Helper for handbuilding ast.
145
func AccountFromString(s string) Account {
146
	subs := strings.Split(s, ":")
147
	acc := Account{Name: make([]SubAccount, len(subs))}
148
	for i := range subs {
149
		acc.Name[i] = SubAccount{Name: subs[i]}
150
	}
151
	return acc
152
}
153
154
func (a Account) String() string {
155
	if len(a.Name) == 0 {
156
		return ""
157
	}
158
	var b strings.Builder
159
	b.Grow(len(a.Name))
160
	b.WriteString(a.Name[0].Name)
161
	for _, s := range a.Name[1:] {
162
		b.WriteByte(':')
163
		b.WriteString(s.Name)
164
	}
165
	return b.String()
166
}