blob: 251d13f16cc8cf49678ff115a68c37a291b97c05 [file] [log] [blame]
Marcel van Lohuizen31cd2b72019-11-09 17:08:49 +01001// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package diff
16
17import (
18 "fmt"
19 "io"
20
Marcel van Lohuizen845df052020-07-26 13:15:45 +020021 "cuelang.org/go/cue"
Marcel van Lohuizen31cd2b72019-11-09 17:08:49 +010022 "cuelang.org/go/cue/errors"
Marcel van Lohuizen31cd2b72019-11-09 17:08:49 +010023)
24
25// Print the differences between two structs represented by an edit script.
26func Print(w io.Writer, es *EditScript) error {
27 p := printer{
28 w: w,
29 margin: 2,
30 context: 2,
31 }
32 p.script(es)
33 return p.errs
34}
35
36type printer struct {
37 w io.Writer
38 context int
39 margin int
40 indent int
41 prefix string
42 hasPrefix bool
43 hasPrint bool
44 errs errors.Error
45}
46
47func (p *printer) writeRaw(b []byte) {
48 if len(b) == 0 {
49 return
50 }
51 if !p.hasPrefix {
52 io.WriteString(p.w, p.prefix)
53 p.hasPrefix = true
54 }
55 if !p.hasPrint {
56 fmt.Fprintf(p.w, "% [1]*s", p.indent+p.margin-len(p.prefix), "")
57 p.hasPrint = true
58 }
59 p.w.Write(b)
60}
61
62func (p *printer) Write(b []byte) (n int, err error) {
63 i, last := 0, 0
64 for ; i < len(b); i++ {
65 if b[i] != '\n' {
66 continue
67 }
68 p.writeRaw(b[last:i])
69 last = i + 1
70 io.WriteString(p.w, "\n")
71 p.hasPrefix = false
72 p.hasPrint = false
73 }
74 p.writeRaw(b[last:])
75 return len(b), nil
76}
77
78func (p *printer) write(b []byte) {
79 _, _ = p.Write(b)
80}
81
82func (p *printer) printLen(align int, str string) {
83 fmt.Fprintf(p, "% -[1]*s", align, str)
84}
85
86func (p *printer) println(s string) {
87 fmt.Fprintln(p, s)
88}
89
90func (p *printer) printf(format string, args ...interface{}) {
91 fmt.Fprintf(p, format, args...)
92}
93
94func (p *printer) script(e *EditScript) {
95 switch e.x.Kind() {
96 case cue.StructKind:
97 p.printStruct(e)
98 case cue.ListKind:
99 p.printList(e)
100 default:
Marcel van Lohuizen72e8fb42021-04-08 09:27:54 +0200101 p.printElem("-", e.x)
102 p.printElem("+", e.y)
Marcel van Lohuizen31cd2b72019-11-09 17:08:49 +0100103 }
104}
105
106func (p *printer) findRun(es *EditScript, i int) (start, end int) {
107 lastEnd := i
108
109 for ; i < es.Len() && es.edits[i].kind == Identity; i++ {
110 }
111 start = i
112
113 // Find end of run
114 include := p.context
115 for ; i < es.Len(); i++ {
116 e := es.edits[i]
117 if e.kind != Identity {
118 include = p.context + 1
119 continue
120 }
121 if include--; include == 0 {
122 break
123 }
124 }
125
126 if i-start > 0 {
127 // Adjust start of run
128 if s := start - p.context; s > lastEnd {
129 start = s
130 } else {
131 start = lastEnd
132 }
133 }
134 return start, i
135}
136
137func (p *printer) printStruct(es *EditScript) {
138 // TODO: consider not printing outer curlies, or make it an option.
139 // if p.indent > 0 {
140 p.println("{")
141 defer p.println("}")
142 // }
143 p.indent += 4
144 defer func() {
145 p.indent -= 4
146 }()
147
148 var start, i int
149 for i < es.Len() {
150 lastEnd := i
151 // Find provisional start of run.
152 start, i = p.findRun(es, i)
153
154 p.printSkipped(start - lastEnd)
155 p.printFieldRun(es, start, i)
156 }
157 p.printSkipped(es.Len() - i)
158}
159
160func (p *printer) printList(es *EditScript) {
161 p.println("[")
162 p.indent += 4
163 defer func() {
164 p.indent -= 4
165 p.println("]")
166 }()
167
168 x := getElems(es.x)
169 y := getElems(es.y)
170
171 var start, i int
172 for i < es.Len() {
173 lastEnd := i
174 // Find provisional start of run.
175 start, i = p.findRun(es, i)
176
177 p.printSkipped(start - lastEnd)
178 p.printElemRun(es, x, y, start, i)
179 }
180 p.printSkipped(es.Len() - i)
181}
182
183func getElems(x cue.Value) (a []cue.Value) {
184 for i, _ := x.List(); i.Next(); {
185 a = append(a, i.Value())
186 }
187 return a
188}
189
190func (p *printer) printSkipped(n int) {
191 if n > 0 {
192 p.printf("... // %d identical elements\n", n)
193 }
194}
195
196func (p *printer) printValue(v cue.Value) {
197 // TODO: have indent option.
Marcel van Lohuizen72e8fb42021-04-08 09:27:54 +0200198 s := fmt.Sprintf("%+v", v)
Marcel van Lohuizen29dd2502020-07-20 22:07:34 +0200199 io.WriteString(p, s)
Marcel van Lohuizen31cd2b72019-11-09 17:08:49 +0100200}
201
202func (p *printer) printFieldRun(es *EditScript, start, end int) {
203 // Determine max field len.
204 for i := start; i < end; i++ {
205 e := es.edits[i]
206
207 switch e.kind {
208 case UniqueX:
209 p.printField("-", es, es.LabelX(i), es.ValueX(i))
210
211 case UniqueY:
212 p.printField("+", es, es.LabelY(i), es.ValueY(i))
213
214 case Modified:
215 if e.sub != nil {
216 io.WriteString(p, es.LabelX(i))
217 io.WriteString(p, " ")
218 p.script(e.sub)
219 break
220 }
221 // TODO: show per-line differences for multiline strings.
222 p.printField("-", es, es.LabelX(i), es.ValueX(i))
223 p.printField("+", es, es.LabelY(i), es.ValueY(i))
224
225 case Identity:
226 // TODO: write on one line
227 p.printField("", es, es.LabelX(i), es.ValueX(i))
228 }
229 }
230}
231
232func (p *printer) printField(prefix string, es *EditScript, label string, v cue.Value) {
233 p.prefix = prefix
234 io.WriteString(p, label)
235 io.WriteString(p, " ")
236 p.printValue(v)
237 io.WriteString(p, "\n")
238 p.prefix = ""
239}
240
241func (p *printer) printElemRun(es *EditScript, x, y []cue.Value, start, end int) {
242 for _, e := range es.edits[start:end] {
243 switch e.kind {
244 case UniqueX:
245 p.printElem("-", x[e.XPos()])
246
247 case UniqueY:
248 p.printElem("+", y[e.YPos()])
249
250 case Modified:
251 if e.sub != nil {
252 p.script(e.sub)
253 break
254 }
255 // TODO: show per-line differences for multiline strings.
256 p.printElem("-", x[e.XPos()])
257 p.printElem("+", y[e.YPos()])
258
259 case Identity:
260 // TODO: write on one line
261 p.printElem("", x[e.XPos()])
262 }
263 }
264}
265
266func (p *printer) printElem(prefix string, v cue.Value) {
267 p.prefix = prefix
268 p.printValue(v)
269 io.WriteString(p, ",\n")
270 p.prefix = ""
271}