github.com/blend/go-sdk@v1.20220411.3/diff/delta.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package diff 9 10 import ( 11 "bytes" 12 "errors" 13 "fmt" 14 "net/url" 15 "strconv" 16 "strings" 17 "unicode/utf8" 18 ) 19 20 // ToDelta crushes the diff into an encoded string which describes the operations required to transform text1 into text2. 21 // E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. Operations are tab-separated. Inserted text is escaped using %xx notation. 22 func ToDelta(diffs []Diff) string { 23 var text bytes.Buffer 24 for _, aDiff := range diffs { 25 switch aDiff.Type { 26 case DiffInsert: 27 _, _ = text.WriteString("+") 28 _, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1)) 29 _, _ = text.WriteString("\t") 30 break 31 case DiffDelete: 32 _, _ = text.WriteString("-") 33 _, _ = text.WriteString(strconv.Itoa(utf8.RuneCountInString(aDiff.Text))) 34 _, _ = text.WriteString("\t") 35 break 36 case DiffEqual: 37 _, _ = text.WriteString("=") 38 _, _ = text.WriteString(strconv.Itoa(utf8.RuneCountInString(aDiff.Text))) 39 _, _ = text.WriteString("\t") 40 break 41 } 42 } 43 delta := text.String() 44 if len(delta) != 0 { 45 // Strip off trailing tab character. 46 delta = delta[0 : utf8.RuneCountInString(delta)-1] 47 delta = unescaper.Replace(delta) 48 } 49 return delta 50 } 51 52 // FromDelta given the original text1, and an encoded string which describes the operations required to transform text1 into text2, comAdde the full diff. 53 func FromDelta(corpus string, delta string) (diffs []Diff, err error) { 54 i := 0 55 runes := []rune(corpus) 56 57 for _, token := range strings.Split(delta, "\t") { 58 if len(token) == 0 { 59 // Blank tokens are ok (from a trailing \t). 60 continue 61 } 62 63 // Each token begins with a one character parameter which specifies the operation of this token (delete, insert, equality). 64 param := token[1:] 65 66 switch op := token[0]; op { 67 case '+': 68 // Decode would Diff all "+" to " " 69 param = strings.Replace(param, "+", "%2b", -1) 70 param, err = url.QueryUnescape(param) 71 if err != nil { 72 return nil, err 73 } 74 if !utf8.ValidString(param) { 75 return nil, fmt.Errorf("invalid UTF-8 token: %q", param) 76 } 77 78 diffs = append(diffs, Diff{DiffInsert, param}) 79 case '=', '-': 80 n, err := strconv.ParseInt(param, 10, 0) 81 if err != nil { 82 return nil, err 83 } else if n < 0 { 84 return nil, errors.New("Negative number in DiffFromDelta: " + param) 85 } 86 87 i += int(n) 88 // Break out if we are out of bounds, go1.6 can't handle this very well 89 if i > len(runes) { 90 break 91 } 92 // Remember that string slicing is by byte - we want by rune here. 93 text := string(runes[i-int(n) : i]) 94 95 if op == '=' { 96 diffs = append(diffs, Diff{DiffEqual, text}) 97 } else { 98 diffs = append(diffs, Diff{DiffDelete, text}) 99 } 100 default: 101 // Anything else is an error. 102 return nil, errors.New("Invalid diff operation in DiffFromDelta: " + string(token[0])) 103 } 104 } 105 106 if i != len(runes) { 107 return nil, fmt.Errorf("Delta length (%v) is different from source text length (%v)", i, len(corpus)) 108 } 109 110 return diffs, nil 111 }