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  }