-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
diff.go
180 lines (158 loc) · 4.57 KB
/
diff.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package confix
import (
"fmt"
"io"
"sort"
"github.com/creachadair/tomledit"
"github.com/creachadair/tomledit/parser"
"github.com/creachadair/tomledit/transform"
)
type DiffType string
const (
Section DiffType = "S"
Mapping DiffType = "M"
)
type KV struct {
Key string
Value string
Block []string // comment block
}
type Diff struct {
Type DiffType
Deleted bool
KV KV
}
// DiffKeys diffs the keyspaces of the TOML documents in files lhs and rhs.
// Comments, order, and values are ignored for comparison purposes.
func DiffKeys(lhs, rhs *tomledit.Document) []Diff {
// diff sections
diff := diffDocs(allKVs(lhs.Global), allKVs(rhs.Global), false)
lsec, rsec := lhs.Sections, rhs.Sections
transform.SortSectionsByName(lsec)
transform.SortSectionsByName(rsec)
i, j := 0, 0
for i < len(lsec) && j < len(rsec) {
switch {
case lsec[i].Name.Before(rsec[j].Name):
diff = append(diff, Diff{Type: Section, Deleted: true, KV: KV{Key: lsec[i].Name.String()}})
for _, kv := range allKVs(lsec[i]) {
diff = append(diff, Diff{Type: Mapping, Deleted: true, KV: kv})
}
i++
case rsec[j].Name.Before(lsec[i].Name):
diff = append(diff, Diff{Type: Section, KV: KV{Key: rsec[j].Name.String()}})
for _, kv := range allKVs(rsec[j]) {
diff = append(diff, Diff{Type: Mapping, KV: kv})
}
j++
default:
diff = append(diff, diffDocs(allKVs(lsec[i]), allKVs(rsec[j]), false)...)
i++
j++
}
}
for ; i < len(lsec); i++ {
diff = append(diff, Diff{Type: Section, Deleted: true, KV: KV{Key: lsec[i].Name.String()}})
for _, kv := range allKVs(lsec[i]) {
diff = append(diff, Diff{Type: Mapping, Deleted: true, KV: kv})
}
}
for ; j < len(rsec); j++ {
diff = append(diff, Diff{Type: Section, KV: KV{Key: rsec[j].Name.String()}})
for _, kv := range allKVs(rsec[j]) {
diff = append(diff, Diff{Type: Mapping, KV: kv})
}
}
return diff
}
// DiffValues diffs the keyspaces with different values of the TOML documents in files lhs and rhs.
func DiffValues(lhs, rhs *tomledit.Document) []Diff {
diff := diffDocs(allKVs(lhs.Global), allKVs(rhs.Global), true)
lsec, rsec := lhs.Sections, rhs.Sections
transform.SortSectionsByName(lsec)
transform.SortSectionsByName(rsec)
i, j := 0, 0
for i < len(lsec) && j < len(rsec) {
switch {
case lsec[i].Name.Before(rsec[j].Name):
// skip keys present in lhs but not in rhs
i++
case rsec[j].Name.Before(lsec[i].Name):
// skip keys present in rhs but not in lhs
j++
default:
for _, d := range diffDocs(allKVs(lsec[i]), allKVs(rsec[j]), true) {
if !d.Deleted {
diff = append(diff, d)
}
}
i++
j++
}
}
return diff
}
func allKVs(s *tomledit.Section) []KV {
keys := []KV{}
s.Scan(func(key parser.Key, entry *tomledit.Entry) bool {
keys = append(keys, KV{
Key: key.String(),
// we get the value of the current configuration (i.e the one we want to compare/migrate)
Value: entry.Value.String(),
Block: entry.Block,
})
return true
})
return keys
}
// diffDocs get the diff between all keys in lhs and rhs.
// when a key is in both lhs and rhs, it is ignored, unless value is true in which case the value is as well compared.
func diffDocs(lhs, rhs []KV, value bool) []Diff {
diff := []Diff{}
sort.Slice(lhs, func(i, j int) bool {
return lhs[i].Key < lhs[j].Key
})
sort.Slice(rhs, func(i, j int) bool {
return rhs[i].Key < rhs[j].Key
})
i, j := 0, 0
for i < len(lhs) && j < len(rhs) {
switch {
case lhs[i].Key < rhs[j].Key:
diff = append(diff, Diff{Type: Mapping, Deleted: true, KV: lhs[i]})
i++
case lhs[i].Key > rhs[j].Key:
diff = append(diff, Diff{Type: Mapping, KV: rhs[j]})
j++
default:
// key exists in both lhs and rhs
// if value is true, compare the values
if value && lhs[i].Value != rhs[j].Value {
diff = append(diff, Diff{Type: Mapping, KV: lhs[i]})
}
i++
j++
}
}
for ; i < len(lhs); i++ {
diff = append(diff, Diff{Type: Mapping, Deleted: true, KV: lhs[i]})
}
for ; j < len(rhs); j++ {
diff = append(diff, Diff{Type: Mapping, KV: rhs[j]})
}
return diff
}
// PrintDiff output prints one line per key that differs:
// -S name -- section exists in f1 but not f2
// +S name -- section exists in f2 but not f1
// -M name -- mapping exists in f1 but not f2
// +M name -- mapping exists in f2 but not f1
func PrintDiff(w io.Writer, diffs []Diff) {
for _, diff := range diffs {
if diff.Deleted {
fmt.Fprintln(w, fmt.Sprintf("-%s", diff.Type), fmt.Sprintf("%s=%s", diff.KV.Key, diff.KV.Value))
} else {
fmt.Fprintln(w, fmt.Sprintf("+%s", diff.Type), fmt.Sprintf("%s=%s", diff.KV.Key, diff.KV.Value))
}
}
}