github.com/solo-io/cue@v0.4.7/internal/diff/print.go (about) 1 // 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 15 package diff 16 17 import ( 18 "fmt" 19 "io" 20 21 "github.com/solo-io/cue/cue" 22 "github.com/solo-io/cue/cue/errors" 23 ) 24 25 // Print the differences between two structs represented by an edit script. 26 func 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 36 type 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 47 func (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 62 func (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 78 func (p *printer) write(b []byte) { 79 _, _ = p.Write(b) 80 } 81 82 func (p *printer) printLen(align int, str string) { 83 fmt.Fprintf(p, "% -[1]*s", align, str) 84 } 85 86 func (p *printer) println(s string) { 87 fmt.Fprintln(p, s) 88 } 89 90 func (p *printer) printf(format string, args ...interface{}) { 91 fmt.Fprintf(p, format, args...) 92 } 93 94 func (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: 101 p.printElem("-", e.x) 102 p.printElem("+", e.y) 103 } 104 } 105 106 func (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 137 func (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 160 func (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 183 func 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 190 func (p *printer) printSkipped(n int) { 191 if n > 0 { 192 p.printf("... // %d identical elements\n", n) 193 } 194 } 195 196 func (p *printer) printValue(v cue.Value) { 197 // TODO: have indent option. 198 s := fmt.Sprintf("%+v", v) 199 io.WriteString(p, s) 200 } 201 202 func (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 232 func (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 241 func (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 266 func (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 }