cuelang.org/go@v0.10.1/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 "cuelang.org/go/cue" 22 "cuelang.org/go/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) println(s string) { 79 fmt.Fprintln(p, s) 80 } 81 82 func (p *printer) printf(format string, args ...interface{}) { 83 fmt.Fprintf(p, format, args...) 84 } 85 86 func (p *printer) script(e *EditScript) { 87 switch e.x.Kind() { 88 case cue.StructKind: 89 p.printStruct(e) 90 case cue.ListKind: 91 p.printList(e) 92 default: 93 p.printElem("-", e.x) 94 p.printElem("+", e.y) 95 } 96 } 97 98 func (p *printer) findRun(es *EditScript, i int) (start, end int) { 99 lastEnd := i 100 101 for ; i < es.Len() && es.edits[i].kind == Identity; i++ { 102 } 103 start = i 104 105 // Find end of run 106 include := p.context 107 for ; i < es.Len(); i++ { 108 e := es.edits[i] 109 if e.kind != Identity { 110 include = p.context + 1 111 continue 112 } 113 if include--; include == 0 { 114 break 115 } 116 } 117 118 if i-start > 0 { 119 // Adjust start of run 120 if s := start - p.context; s > lastEnd { 121 start = s 122 } else { 123 start = lastEnd 124 } 125 } 126 return start, i 127 } 128 129 func (p *printer) printStruct(es *EditScript) { 130 // TODO: consider not printing outer curlies, or make it an option. 131 // if p.indent > 0 { 132 p.println("{") 133 defer p.println("}") 134 // } 135 p.indent += 4 136 defer func() { 137 p.indent -= 4 138 }() 139 140 var start, i int 141 for i < es.Len() { 142 lastEnd := i 143 // Find provisional start of run. 144 start, i = p.findRun(es, i) 145 146 p.printSkipped(start - lastEnd) 147 p.printFieldRun(es, start, i) 148 } 149 p.printSkipped(es.Len() - i) 150 } 151 152 func (p *printer) printList(es *EditScript) { 153 p.println("[") 154 p.indent += 4 155 defer func() { 156 p.indent -= 4 157 p.println("]") 158 }() 159 160 x := getElems(es.x) 161 y := getElems(es.y) 162 163 var start, i int 164 for i < es.Len() { 165 lastEnd := i 166 // Find provisional start of run. 167 start, i = p.findRun(es, i) 168 169 p.printSkipped(start - lastEnd) 170 p.printElemRun(es, x, y, start, i) 171 } 172 p.printSkipped(es.Len() - i) 173 } 174 175 func getElems(x cue.Value) (a []cue.Value) { 176 for i, _ := x.List(); i.Next(); { 177 a = append(a, i.Value()) 178 } 179 return a 180 } 181 182 func (p *printer) printSkipped(n int) { 183 if n > 0 { 184 p.printf("... // %d identical elements\n", n) 185 } 186 } 187 188 func (p *printer) printValue(v cue.Value) { 189 // TODO: have indent option. 190 s := fmt.Sprintf("%+v", v) 191 io.WriteString(p, s) 192 } 193 194 func (p *printer) printFieldRun(es *EditScript, start, end int) { 195 // Determine max field len. 196 for i := start; i < end; i++ { 197 e := es.edits[i] 198 199 switch e.kind { 200 case UniqueX: 201 p.printField("-", es, es.LabelX(i), es.ValueX(i)) 202 203 case UniqueY: 204 p.printField("+", es, es.LabelY(i), es.ValueY(i)) 205 206 case Modified: 207 if e.sub != nil { 208 io.WriteString(p, es.LabelX(i)) 209 io.WriteString(p, " ") 210 p.script(e.sub) 211 break 212 } 213 // TODO: show per-line differences for multiline strings. 214 p.printField("-", es, es.LabelX(i), es.ValueX(i)) 215 p.printField("+", es, es.LabelY(i), es.ValueY(i)) 216 217 case Identity: 218 // TODO: write on one line 219 p.printField("", es, es.LabelX(i), es.ValueX(i)) 220 } 221 } 222 } 223 224 func (p *printer) printField(prefix string, es *EditScript, label string, v cue.Value) { 225 p.prefix = prefix 226 io.WriteString(p, label) 227 io.WriteString(p, " ") 228 p.printValue(v) 229 io.WriteString(p, "\n") 230 p.prefix = "" 231 } 232 233 func (p *printer) printElemRun(es *EditScript, x, y []cue.Value, start, end int) { 234 for _, e := range es.edits[start:end] { 235 switch e.kind { 236 case UniqueX: 237 p.printElem("-", x[e.XPos()]) 238 239 case UniqueY: 240 p.printElem("+", y[e.YPos()]) 241 242 case Modified: 243 if e.sub != nil { 244 p.script(e.sub) 245 break 246 } 247 // TODO: show per-line differences for multiline strings. 248 p.printElem("-", x[e.XPos()]) 249 p.printElem("+", y[e.YPos()]) 250 251 case Identity: 252 // TODO: write on one line 253 p.printElem("", x[e.XPos()]) 254 } 255 } 256 } 257 258 func (p *printer) printElem(prefix string, v cue.Value) { 259 p.prefix = prefix 260 p.printValue(v) 261 io.WriteString(p, ",\n") 262 p.prefix = "" 263 }