github.com/google/yamlfmt@v0.12.2-0.20240514121411-7f77800e2681/internal/multilinediff/multilinediff.go (about) 1 // Copyright 2024 Google LLC 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 multilinediff 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/google/go-cmp/cmp" 22 "github.com/google/go-cmp/cmp/cmpopts" 23 ) 24 25 // Get the diff between two strings. 26 func Diff(a, b, lineSep string) (string, int) { 27 reporter := Reporter{LineSep: lineSep} 28 cmp.Diff( 29 a, b, 30 cmpopts.AcyclicTransformer("multiline", func(s string) []string { 31 return strings.Split(s, lineSep) 32 }), 33 cmp.Reporter(&reporter), 34 ) 35 return reporter.String(), reporter.DiffCount 36 } 37 38 type diffType int 39 40 const ( 41 diffTypeEqual diffType = iota 42 diffTypeChange 43 diffTypeAdd 44 ) 45 46 type diffLine struct { 47 diff diffType 48 old string 49 new string 50 } 51 52 func (l diffLine) toLine(length int) string { 53 line := "" 54 55 switch l.diff { 56 case diffTypeChange: 57 line += "- " 58 case diffTypeAdd: 59 line += "+ " 60 default: 61 line += " " 62 } 63 64 line += l.old 65 66 for i := 0; i < length-len(l.old); i++ { 67 line += " " 68 } 69 70 line += " " 71 72 line += l.new 73 74 return line 75 } 76 77 // A pretty reporter to pass into cmp.Diff using the cmd.Reporter function. 78 type Reporter struct { 79 LineSep string 80 DiffCount int 81 82 path cmp.Path 83 lines []diffLine 84 } 85 86 func (r *Reporter) PushStep(ps cmp.PathStep) { 87 r.path = append(r.path, ps) 88 } 89 90 func (r *Reporter) Report(rs cmp.Result) { 91 line := diffLine{} 92 vOld, vNew := r.path.Last().Values() 93 if !rs.Equal() { 94 r.DiffCount++ 95 if vOld.IsValid() { 96 line.diff = diffTypeChange 97 line.old = fmt.Sprintf("%+v", vOld) 98 } 99 if vNew.IsValid() { 100 if line.diff == diffTypeEqual { 101 line.diff = diffTypeAdd 102 } 103 line.new = fmt.Sprintf("%+v", vNew) 104 } 105 } else { 106 line.old = fmt.Sprintf("%+v", vOld) 107 line.new = fmt.Sprintf("%+v", vOld) 108 } 109 r.lines = append(r.lines, line) 110 } 111 112 func (r *Reporter) PopStep() { 113 r.path = r.path[:len(r.path)-1] 114 } 115 116 func (r *Reporter) String() string { 117 maxLen := 0 118 for _, l := range r.lines { 119 if len(l.old) > maxLen { 120 maxLen = len(l.old) 121 } 122 } 123 124 diffLines := []string{} 125 for _, l := range r.lines { 126 diffLines = append(diffLines, l.toLine(maxLen)) 127 } 128 129 return strings.Join(diffLines, r.LineSep) 130 }