Move patch functionality into patch*.go
diff --git a/diffmatchpatch/diffmatchpatch.go b/diffmatchpatch/diffmatchpatch.go
index f0b0d3d..f1ec0c2 100644
--- a/diffmatchpatch/diffmatchpatch.go
+++ b/diffmatchpatch/diffmatchpatch.go
@@ -11,12 +11,7 @@
package diffmatchpatch
import (
- "bytes"
- "errors"
- "math"
- "net/url"
"regexp"
- "strconv"
"strings"
"time"
"unicode/utf8"
@@ -151,58 +146,6 @@
Text string
}
-// Patch represents one patch operation.
-type Patch struct {
- diffs []Diff
- start1 int
- start2 int
- length1 int
- length2 int
-}
-
-// String emulates GNU diff's format.
-// Header: @@ -382,8 +481,9 @@
-// Indicies are printed as 1-based, not 0-based.
-func (p *Patch) String() string {
- var coords1, coords2 string
-
- if p.length1 == 0 {
- coords1 = strconv.Itoa(p.start1) + ",0"
- } else if p.length1 == 1 {
- coords1 = strconv.Itoa(p.start1 + 1)
- } else {
- coords1 = strconv.Itoa(p.start1+1) + "," + strconv.Itoa(p.length1)
- }
-
- if p.length2 == 0 {
- coords2 = strconv.Itoa(p.start2) + ",0"
- } else if p.length2 == 1 {
- coords2 = strconv.Itoa(p.start2 + 1)
- } else {
- coords2 = strconv.Itoa(p.start2+1) + "," + strconv.Itoa(p.length2)
- }
-
- var text bytes.Buffer
- _, _ = text.WriteString("@@ -" + coords1 + " +" + coords2 + " @@\n")
-
- // Escape the body of the patch with %xx notation.
- for _, aDiff := range p.diffs {
- switch aDiff.Type {
- case DiffInsert:
- _, _ = text.WriteString("+")
- case DiffDelete:
- _, _ = text.WriteString("-")
- case DiffEqual:
- _, _ = text.WriteString(" ")
- }
-
- _, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1))
- _, _ = text.WriteString("\n")
- }
-
- return unescaper.Replace(text.String())
-}
-
// DiffMatchPatch holds the configuration for diff-match-patch operations.
type DiffMatchPatch struct {
// Number of seconds to map a diff before giving up (0 for infinity).
@@ -239,504 +182,3 @@
MatchMaxBits: 32,
}
}
-
-// PatchAddContext increases the context until it is unique,
-// but doesn't let the pattern expand beyond MatchMaxBits.
-func (dmp *DiffMatchPatch) PatchAddContext(patch Patch, text string) Patch {
- if len(text) == 0 {
- return patch
- }
-
- pattern := text[patch.start2 : patch.start2+patch.length1]
- padding := 0
-
- // Look for the first and last matches of pattern in text. If two
- // different matches are found, increase the pattern length.
- for strings.Index(text, pattern) != strings.LastIndex(text, pattern) &&
- len(pattern) < dmp.MatchMaxBits-2*dmp.PatchMargin {
- padding += dmp.PatchMargin
- maxStart := max(0, patch.start2-padding)
- minEnd := min(len(text), patch.start2+patch.length1+padding)
- pattern = text[maxStart:minEnd]
- }
- // Add one chunk for good luck.
- padding += dmp.PatchMargin
-
- // Add the prefix.
- prefix := text[max(0, patch.start2-padding):patch.start2]
- if len(prefix) != 0 {
- patch.diffs = append([]Diff{Diff{DiffEqual, prefix}}, patch.diffs...)
- }
- // Add the suffix.
- suffix := text[patch.start2+patch.length1 : min(len(text), patch.start2+patch.length1+padding)]
- if len(suffix) != 0 {
- patch.diffs = append(patch.diffs, Diff{DiffEqual, suffix})
- }
-
- // Roll back the start points.
- patch.start1 -= len(prefix)
- patch.start2 -= len(prefix)
- // Extend the lengths.
- patch.length1 += len(prefix) + len(suffix)
- patch.length2 += len(prefix) + len(suffix)
-
- return patch
-}
-
-// PatchMake computes a list of patches.
-func (dmp *DiffMatchPatch) PatchMake(opt ...interface{}) []Patch {
- if len(opt) == 1 {
- diffs, _ := opt[0].([]Diff)
- text1 := dmp.DiffText1(diffs)
- return dmp.PatchMake(text1, diffs)
- } else if len(opt) == 2 {
- text1 := opt[0].(string)
- switch t := opt[1].(type) {
- case string:
- diffs := dmp.DiffMain(text1, t, true)
- if len(diffs) > 2 {
- diffs = dmp.DiffCleanupSemantic(diffs)
- diffs = dmp.DiffCleanupEfficiency(diffs)
- }
- return dmp.PatchMake(text1, diffs)
- case []Diff:
- return dmp.patchMake2(text1, t)
- }
- } else if len(opt) == 3 {
- return dmp.PatchMake(opt[0], opt[2])
- }
- return []Patch{}
-}
-
-// patchMake2 computes a list of patches to turn text1 into text2.
-// text2 is not provided, diffs are the delta between text1 and text2.
-func (dmp *DiffMatchPatch) patchMake2(text1 string, diffs []Diff) []Patch {
- // Check for null inputs not needed since null can't be passed in C#.
- patches := []Patch{}
- if len(diffs) == 0 {
- return patches // Get rid of the null case.
- }
-
- patch := Patch{}
- charCount1 := 0 // Number of characters into the text1 string.
- charCount2 := 0 // Number of characters into the text2 string.
- // Start with text1 (prepatchText) and apply the diffs until we arrive at
- // text2 (postpatchText). We recreate the patches one by one to determine
- // context info.
- prepatchText := text1
- postpatchText := text1
-
- for i, aDiff := range diffs {
- if len(patch.diffs) == 0 && aDiff.Type != DiffEqual {
- // A new patch starts here.
- patch.start1 = charCount1
- patch.start2 = charCount2
- }
-
- switch aDiff.Type {
- case DiffInsert:
- patch.diffs = append(patch.diffs, aDiff)
- patch.length2 += len(aDiff.Text)
- postpatchText = postpatchText[:charCount2] +
- aDiff.Text + postpatchText[charCount2:]
- case DiffDelete:
- patch.length1 += len(aDiff.Text)
- patch.diffs = append(patch.diffs, aDiff)
- postpatchText = postpatchText[:charCount2] + postpatchText[charCount2+len(aDiff.Text):]
- case DiffEqual:
- if len(aDiff.Text) <= 2*dmp.PatchMargin &&
- len(patch.diffs) != 0 && i != len(diffs)-1 {
- // Small equality inside a patch.
- patch.diffs = append(patch.diffs, aDiff)
- patch.length1 += len(aDiff.Text)
- patch.length2 += len(aDiff.Text)
- }
- if len(aDiff.Text) >= 2*dmp.PatchMargin {
- // Time for a new patch.
- if len(patch.diffs) != 0 {
- patch = dmp.PatchAddContext(patch, prepatchText)
- patches = append(patches, patch)
- patch = Patch{}
- // Unlike Unidiff, our patch lists have a rolling context.
- // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
- // Update prepatch text & pos to reflect the application of the
- // just completed patch.
- prepatchText = postpatchText
- charCount1 = charCount2
- }
- }
- }
-
- // Update the current character count.
- if aDiff.Type != DiffInsert {
- charCount1 += len(aDiff.Text)
- }
- if aDiff.Type != DiffDelete {
- charCount2 += len(aDiff.Text)
- }
- }
-
- // Pick up the leftover patch if not empty.
- if len(patch.diffs) != 0 {
- patch = dmp.PatchAddContext(patch, prepatchText)
- patches = append(patches, patch)
- }
-
- return patches
-}
-
-// PatchDeepCopy returns an array that is identical to a
-// given an array of patches.
-func (dmp *DiffMatchPatch) PatchDeepCopy(patches []Patch) []Patch {
- patchesCopy := []Patch{}
- for _, aPatch := range patches {
- patchCopy := Patch{}
- for _, aDiff := range aPatch.diffs {
- patchCopy.diffs = append(patchCopy.diffs, Diff{
- aDiff.Type,
- aDiff.Text,
- })
- }
- patchCopy.start1 = aPatch.start1
- patchCopy.start2 = aPatch.start2
- patchCopy.length1 = aPatch.length1
- patchCopy.length2 = aPatch.length2
- patchesCopy = append(patchesCopy, patchCopy)
- }
- return patchesCopy
-}
-
-// PatchApply merges a set of patches onto the text. Returns a patched text, as well
-// as an array of true/false values indicating which patches were applied.
-func (dmp *DiffMatchPatch) PatchApply(patches []Patch, text string) (string, []bool) {
- if len(patches) == 0 {
- return text, []bool{}
- }
-
- // Deep copy the patches so that no changes are made to originals.
- patches = dmp.PatchDeepCopy(patches)
-
- nullPadding := dmp.PatchAddPadding(patches)
- text = nullPadding + text + nullPadding
- patches = dmp.PatchSplitMax(patches)
-
- x := 0
- // delta keeps track of the offset between the expected and actual
- // location of the previous patch. If there are patches expected at
- // positions 10 and 20, but the first patch was found at 12, delta is 2
- // and the second patch has an effective expected position of 22.
- delta := 0
- results := make([]bool, len(patches))
- for _, aPatch := range patches {
- expectedLoc := aPatch.start2 + delta
- text1 := dmp.DiffText1(aPatch.diffs)
- var startLoc int
- endLoc := -1
- if len(text1) > dmp.MatchMaxBits {
- // PatchSplitMax will only provide an oversized pattern
- // in the case of a monster delete.
- startLoc = dmp.MatchMain(text, text1[:dmp.MatchMaxBits], expectedLoc)
- if startLoc != -1 {
- endLoc = dmp.MatchMain(text,
- text1[len(text1)-dmp.MatchMaxBits:], expectedLoc+len(text1)-dmp.MatchMaxBits)
- if endLoc == -1 || startLoc >= endLoc {
- // Can't find valid trailing context. Drop this patch.
- startLoc = -1
- }
- }
- } else {
- startLoc = dmp.MatchMain(text, text1, expectedLoc)
- }
- if startLoc == -1 {
- // No match found. :(
- results[x] = false
- // Subtract the delta for this failed patch from subsequent patches.
- delta -= aPatch.length2 - aPatch.length1
- } else {
- // Found a match. :)
- results[x] = true
- delta = startLoc - expectedLoc
- var text2 string
- if endLoc == -1 {
- text2 = text[startLoc:int(math.Min(float64(startLoc+len(text1)), float64(len(text))))]
- } else {
- text2 = text[startLoc:int(math.Min(float64(endLoc+dmp.MatchMaxBits), float64(len(text))))]
- }
- if text1 == text2 {
- // Perfect match, just shove the Replacement text in.
- text = text[:startLoc] + dmp.DiffText2(aPatch.diffs) + text[startLoc+len(text1):]
- } else {
- // Imperfect match. Run a diff to get a framework of equivalent
- // indices.
- diffs := dmp.DiffMain(text1, text2, false)
- if len(text1) > dmp.MatchMaxBits && float64(dmp.DiffLevenshtein(diffs))/float64(len(text1)) > dmp.PatchDeleteThreshold {
- // The end points match, but the content is unacceptably bad.
- results[x] = false
- } else {
- diffs = dmp.DiffCleanupSemanticLossless(diffs)
- index1 := 0
- for _, aDiff := range aPatch.diffs {
- if aDiff.Type != DiffEqual {
- index2 := dmp.DiffXIndex(diffs, index1)
- if aDiff.Type == DiffInsert {
- // Insertion
- text = text[:startLoc+index2] + aDiff.Text + text[startLoc+index2:]
- } else if aDiff.Type == DiffDelete {
- // Deletion
- startIndex := startLoc + index2
- text = text[:startIndex] +
- text[startIndex+dmp.DiffXIndex(diffs, index1+len(aDiff.Text))-index2:]
- }
- }
- if aDiff.Type != DiffDelete {
- index1 += len(aDiff.Text)
- }
- }
- }
- }
- }
- x++
- }
- // Strip the padding off.
- text = text[len(nullPadding) : len(nullPadding)+(len(text)-2*len(nullPadding))]
- return text, results
-}
-
-// PatchAddPadding adds some padding on text start and end so that edges can match something.
-// Intended to be called only from within patchApply.
-func (dmp *DiffMatchPatch) PatchAddPadding(patches []Patch) string {
- paddingLength := dmp.PatchMargin
- nullPadding := ""
- for x := 1; x <= paddingLength; x++ {
- nullPadding += string(x)
- }
-
- // Bump all the patches forward.
- for i := range patches {
- patches[i].start1 += paddingLength
- patches[i].start2 += paddingLength
- }
-
- // Add some padding on start of first diff.
- if len(patches[0].diffs) == 0 || patches[0].diffs[0].Type != DiffEqual {
- // Add nullPadding equality.
- patches[0].diffs = append([]Diff{Diff{DiffEqual, nullPadding}}, patches[0].diffs...)
- patches[0].start1 -= paddingLength // Should be 0.
- patches[0].start2 -= paddingLength // Should be 0.
- patches[0].length1 += paddingLength
- patches[0].length2 += paddingLength
- } else if paddingLength > len(patches[0].diffs[0].Text) {
- // Grow first equality.
- extraLength := paddingLength - len(patches[0].diffs[0].Text)
- patches[0].diffs[0].Text = nullPadding[len(patches[0].diffs[0].Text):] + patches[0].diffs[0].Text
- patches[0].start1 -= extraLength
- patches[0].start2 -= extraLength
- patches[0].length1 += extraLength
- patches[0].length2 += extraLength
- }
-
- // Add some padding on end of last diff.
- last := len(patches) - 1
- if len(patches[last].diffs) == 0 || patches[last].diffs[len(patches[last].diffs)-1].Type != DiffEqual {
- // Add nullPadding equality.
- patches[last].diffs = append(patches[last].diffs, Diff{DiffEqual, nullPadding})
- patches[last].length1 += paddingLength
- patches[last].length2 += paddingLength
- } else if paddingLength > len(patches[last].diffs[len(patches[last].diffs)-1].Text) {
- // Grow last equality.
- lastDiff := patches[last].diffs[len(patches[last].diffs)-1]
- extraLength := paddingLength - len(lastDiff.Text)
- patches[last].diffs[len(patches[last].diffs)-1].Text += nullPadding[:extraLength]
- patches[last].length1 += extraLength
- patches[last].length2 += extraLength
- }
-
- return nullPadding
-}
-
-// PatchSplitMax looks through the patches and breaks up any which are longer than the
-// maximum limit of the match algorithm.
-// Intended to be called only from within patchApply.
-func (dmp *DiffMatchPatch) PatchSplitMax(patches []Patch) []Patch {
- patchSize := dmp.MatchMaxBits
- for x := 0; x < len(patches); x++ {
- if patches[x].length1 <= patchSize {
- continue
- }
- bigpatch := patches[x]
- // Remove the big old patch.
- patches = append(patches[:x], patches[x+1:]...)
- x--
-
- start1 := bigpatch.start1
- start2 := bigpatch.start2
- precontext := ""
- for len(bigpatch.diffs) != 0 {
- // Create one of several smaller patches.
- patch := Patch{}
- empty := true
- patch.start1 = start1 - len(precontext)
- patch.start2 = start2 - len(precontext)
- if len(precontext) != 0 {
- patch.length1 = len(precontext)
- patch.length2 = len(precontext)
- patch.diffs = append(patch.diffs, Diff{DiffEqual, precontext})
- }
- for len(bigpatch.diffs) != 0 && patch.length1 < patchSize-dmp.PatchMargin {
- diffType := bigpatch.diffs[0].Type
- diffText := bigpatch.diffs[0].Text
- if diffType == DiffInsert {
- // Insertions are harmless.
- patch.length2 += len(diffText)
- start2 += len(diffText)
- patch.diffs = append(patch.diffs, bigpatch.diffs[0])
- bigpatch.diffs = bigpatch.diffs[1:]
- empty = false
- } else if diffType == DiffDelete && len(patch.diffs) == 1 && patch.diffs[0].Type == DiffEqual && len(diffText) > 2*patchSize {
- // This is a large deletion. Let it pass in one chunk.
- patch.length1 += len(diffText)
- start1 += len(diffText)
- empty = false
- patch.diffs = append(patch.diffs, Diff{diffType, diffText})
- bigpatch.diffs = bigpatch.diffs[1:]
- } else {
- // Deletion or equality. Only take as much as we can stomach.
- diffText = diffText[:min(len(diffText), patchSize-patch.length1-dmp.PatchMargin)]
-
- patch.length1 += len(diffText)
- start1 += len(diffText)
- if diffType == DiffEqual {
- patch.length2 += len(diffText)
- start2 += len(diffText)
- } else {
- empty = false
- }
- patch.diffs = append(patch.diffs, Diff{diffType, diffText})
- if diffText == bigpatch.diffs[0].Text {
- bigpatch.diffs = bigpatch.diffs[1:]
- } else {
- bigpatch.diffs[0].Text =
- bigpatch.diffs[0].Text[len(diffText):]
- }
- }
- }
- // Compute the head context for the next patch.
- precontext = dmp.DiffText2(patch.diffs)
- precontext = precontext[max(0, len(precontext)-dmp.PatchMargin):]
-
- postcontext := ""
- // Append the end context for this patch.
- if len(dmp.DiffText1(bigpatch.diffs)) > dmp.PatchMargin {
- postcontext = dmp.DiffText1(bigpatch.diffs)[:dmp.PatchMargin]
- } else {
- postcontext = dmp.DiffText1(bigpatch.diffs)
- }
-
- if len(postcontext) != 0 {
- patch.length1 += len(postcontext)
- patch.length2 += len(postcontext)
- if len(patch.diffs) != 0 && patch.diffs[len(patch.diffs)-1].Type == DiffEqual {
- patch.diffs[len(patch.diffs)-1].Text += postcontext
- } else {
- patch.diffs = append(patch.diffs, Diff{DiffEqual, postcontext})
- }
- }
- if !empty {
- x++
- patches = append(patches[:x], append([]Patch{patch}, patches[x:]...)...)
- }
- }
- }
- return patches
-}
-
-// PatchToText takes a list of patches and returns a textual representation.
-func (dmp *DiffMatchPatch) PatchToText(patches []Patch) string {
- var text bytes.Buffer
- for _, aPatch := range patches {
- _, _ = text.WriteString(aPatch.String())
- }
- return text.String()
-}
-
-// PatchFromText parses a textual representation of patches and returns a List of Patch
-// objects.
-func (dmp *DiffMatchPatch) PatchFromText(textline string) ([]Patch, error) {
- patches := []Patch{}
- if len(textline) == 0 {
- return patches, nil
- }
- text := strings.Split(textline, "\n")
- textPointer := 0
- patchHeader := regexp.MustCompile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$")
-
- var patch Patch
- var sign uint8
- var line string
- for textPointer < len(text) {
-
- if !patchHeader.MatchString(text[textPointer]) {
- return patches, errors.New("Invalid patch string: " + text[textPointer])
- }
-
- patch = Patch{}
- m := patchHeader.FindStringSubmatch(text[textPointer])
-
- patch.start1, _ = strconv.Atoi(m[1])
- if len(m[2]) == 0 {
- patch.start1--
- patch.length1 = 1
- } else if m[2] == "0" {
- patch.length1 = 0
- } else {
- patch.start1--
- patch.length1, _ = strconv.Atoi(m[2])
- }
-
- patch.start2, _ = strconv.Atoi(m[3])
-
- if len(m[4]) == 0 {
- patch.start2--
- patch.length2 = 1
- } else if m[4] == "0" {
- patch.length2 = 0
- } else {
- patch.start2--
- patch.length2, _ = strconv.Atoi(m[4])
- }
- textPointer++
-
- for textPointer < len(text) {
- if len(text[textPointer]) > 0 {
- sign = text[textPointer][0]
- } else {
- textPointer++
- continue
- }
-
- line = text[textPointer][1:]
- line = strings.Replace(line, "+", "%2b", -1)
- line, _ = url.QueryUnescape(line)
- if sign == '-' {
- // Deletion.
- patch.diffs = append(patch.diffs, Diff{DiffDelete, line})
- } else if sign == '+' {
- // Insertion.
- patch.diffs = append(patch.diffs, Diff{DiffInsert, line})
- } else if sign == ' ' {
- // Minor equality.
- patch.diffs = append(patch.diffs, Diff{DiffEqual, line})
- } else if sign == '@' {
- // Start of next patch.
- break
- } else {
- // WTF?
- return patches, errors.New("Invalid patch mode '" + string(sign) + "' in: " + string(line))
- }
- textPointer++
- }
-
- patches = append(patches, patch)
- }
- return patches, nil
-}
diff --git a/diffmatchpatch/diffmatchpatch_test.go b/diffmatchpatch/diffmatchpatch_test.go
index 8fad70a..9f62f7b 100644
--- a/diffmatchpatch/diffmatchpatch_test.go
+++ b/diffmatchpatch/diffmatchpatch_test.go
@@ -13,7 +13,6 @@
"fmt"
"io/ioutil"
"runtime"
- "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -108,323 +107,6 @@
}
}
-func TestPatchString(t *testing.T) {
- type TestCase struct {
- Patch Patch
-
- Expected string
- }
-
- for i, tc := range []TestCase{
- {
- Patch: Patch{
- start1: 20,
- start2: 21,
- length1: 18,
- length2: 17,
-
- diffs: []Diff{
- {DiffEqual, "jump"},
- {DiffDelete, "s"},
- {DiffInsert, "ed"},
- {DiffEqual, " over "},
- {DiffDelete, "the"},
- {DiffInsert, "a"},
- {DiffEqual, "\nlaz"},
- },
- },
-
- Expected: "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n",
- },
- } {
- actual := tc.Patch.String()
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))
- }
-}
-
-func TestPatchFromText(t *testing.T) {
- type TestCase struct {
- Patch string
-
- ErrorMessagePrefix string
- }
-
- dmp := New()
-
- for i, tc := range []TestCase{
- {"", ""},
- {"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n", ""},
- {"@@ -1 +1 @@\n-a\n+b\n", ""},
- {"@@ -1,3 +0,0 @@\n-abc\n", ""},
- {"@@ -0,0 +1,3 @@\n+abc\n", ""},
- {"Bad\nPatch\n", "Invalid patch string"},
- } {
- patches, err := dmp.PatchFromText(tc.Patch)
- if tc.ErrorMessagePrefix == "" {
- assert.Nil(t, err)
-
- if tc.Patch == "" {
- assert.Equal(t, []Patch{}, patches, fmt.Sprintf("Test case #%d, %#v", i, tc))
- } else {
- assert.Equal(t, tc.Patch, patches[0].String(), fmt.Sprintf("Test case #%d, %#v", i, tc))
- }
- } else {
- e := err.Error()
- if strings.HasPrefix(e, tc.ErrorMessagePrefix) {
- e = tc.ErrorMessagePrefix
- }
- assert.Equal(t, tc.ErrorMessagePrefix, e)
- }
- }
-
- diffs := []Diff{
- {DiffDelete, "`1234567890-=[]\\;',./"},
- {DiffInsert, "~!@#$%^&*()_+{}|:\"<>?"},
- }
-
- patches, err := dmp.PatchFromText("@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n")
- assert.Len(t, patches, 1)
- assert.Equal(t, diffs,
- patches[0].diffs,
- )
- assert.Nil(t, err)
-}
-
-func TestPatchToText(t *testing.T) {
- type TestCase struct {
- Patch string
- }
-
- dmp := New()
-
- for i, tc := range []TestCase{
- {"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
- {"@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n"},
- } {
- patches, err := dmp.PatchFromText(tc.Patch)
- assert.Nil(t, err)
-
- actual := dmp.PatchToText(patches)
- assert.Equal(t, tc.Patch, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))
- }
-}
-
-func TestPatchAddContext(t *testing.T) {
- type TestCase struct {
- Name string
-
- Patch string
- Text string
-
- Expected string
- }
-
- dmp := New()
- dmp.PatchMargin = 4
-
- for i, tc := range []TestCase{
- {"Simple case", "@@ -21,4 +21,10 @@\n-jump\n+somersault\n", "The quick brown fox jumps over the lazy dog.", "@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n"},
- {"Not enough trailing context", "@@ -21,4 +21,10 @@\n-jump\n+somersault\n", "The quick brown fox jumps.", "@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n"},
- {"Not enough leading context", "@@ -3 +3,2 @@\n-e\n+at\n", "The quick brown fox jumps.", "@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n"},
- {"Ambiguity", "@@ -3 +3,2 @@\n-e\n+at\n", "The quick brown fox jumps. The quick brown fox crashes.", "@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n"},
- } {
- patches, err := dmp.PatchFromText(tc.Patch)
- assert.Nil(t, err)
-
- actual := dmp.PatchAddContext(patches[0], tc.Text)
- assert.Equal(t, tc.Expected, actual.String(), fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-}
-
-func TestPatchMakeAndPatchToText(t *testing.T) {
- type TestCase struct {
- Name string
-
- Input1 interface{}
- Input2 interface{}
- Input3 interface{}
-
- Expected string
- }
-
- dmp := New()
-
- text1 := "The quick brown fox jumps over the lazy dog."
- text2 := "That quick brown fox jumped over a lazy dog."
-
- for i, tc := range []TestCase{
- {"Null case", "", "", nil, ""},
- {"Text2+Text1 inputs", text2, text1, nil, "@@ -1,8 +1,7 @@\n Th\n-at\n+e\n qui\n@@ -21,17 +21,18 @@\n jump\n-ed\n+s\n over \n-a\n+the\n laz\n"},
- {"Text1+Text2 inputs", text1, text2, nil, "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
- {"Diff input", dmp.DiffMain(text1, text2, false), nil, nil, "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
- {"Text1+Diff inputs", text1, dmp.DiffMain(text1, text2, false), nil, "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
- {"Text1+Text2+Diff inputs (deprecated)", text1, text2, dmp.DiffMain(text1, text2, false), "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
- {"Character encoding", "`1234567890-=[]\\;',./", "~!@#$%^&*()_+{}|:\"<>?", nil, "@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n"},
- {"Long string with repeats", strings.Repeat("abcdef", 100), strings.Repeat("abcdef", 100) + "123", nil, "@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n"},
- {"Corner case of #31 fixed by #32", "2016-09-01T03:07:14.807830741Z", "2016-09-01T03:07:15.154800781Z", nil, "@@ -15,16 +15,16 @@\n 07:1\n+5.15\n 4\n-.\n 80\n+0\n 78\n-3074\n 1Z\n"},
- } {
- var patches []Patch
- if tc.Input3 != nil {
- patches = dmp.PatchMake(tc.Input1, tc.Input2, tc.Input3)
- } else if tc.Input2 != nil {
- patches = dmp.PatchMake(tc.Input1, tc.Input2)
- } else if ps, ok := tc.Input1.([]Patch); ok {
- patches = ps
- } else {
- patches = dmp.PatchMake(tc.Input1)
- }
-
- actual := dmp.PatchToText(patches)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-
- // Corner case of #28 wrong patch with timeout of 0
- dmp.DiffTimeout = 0
-
- text1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ut risus et enim consectetur convallis a non ipsum. Sed nec nibh cursus, interdum libero vel."
- text2 = "Lorem a ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ut risus et enim consectetur convallis a non ipsum. Sed nec nibh cursus, interdum liberovel."
-
- diffs := dmp.DiffMain(text1, text2, true)
- // Additional check that the diff texts are equal to the originals even if we are using DiffMain with checklines=true #29
- assert.Equal(t, text1, dmp.DiffText1(diffs))
- assert.Equal(t, text2, dmp.DiffText2(diffs))
-
- patches := dmp.PatchMake(text1, diffs)
-
- actual := dmp.PatchToText(patches)
- assert.Equal(t, "@@ -1,14 +1,16 @@\n Lorem \n+a \n ipsum do\n@@ -148,13 +148,12 @@\n m libero\n- \n vel.\n", actual)
-}
-
-func TestPatchSplitMax(t *testing.T) {
- type TestCase struct {
- Text1 string
- Text2 string
-
- Expected string
- }
-
- dmp := New()
-
- for i, tc := range []TestCase{
- {"abcdefghijklmnopqrstuvwxyz01234567890", "XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0", "@@ -1,32 +1,46 @@\n+X\n ab\n+X\n cd\n+X\n ef\n+X\n gh\n+X\n ij\n+X\n kl\n+X\n mn\n+X\n op\n+X\n qr\n+X\n st\n+X\n uv\n+X\n wx\n+X\n yz\n+X\n 012345\n@@ -25,13 +39,18 @@\n zX01\n+X\n 23\n+X\n 45\n+X\n 67\n+X\n 89\n+X\n 0\n"},
- {"abcdef1234567890123456789012345678901234567890123456789012345678901234567890uvwxyz", "abcdefuvwxyz", "@@ -3,78 +3,8 @@\n cdef\n-1234567890123456789012345678901234567890123456789012345678901234567890\n uvwx\n"},
- {"1234567890123456789012345678901234567890123456789012345678901234567890", "abc", "@@ -1,32 +1,4 @@\n-1234567890123456789012345678\n 9012\n@@ -29,32 +1,4 @@\n-9012345678901234567890123456\n 7890\n@@ -57,14 +1,3 @@\n-78901234567890\n+abc\n"},
- {"abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1", "abcdefghij , h : 1 , t : 1 abcdefghij , h : 1 , t : 1 abcdefghij , h : 0 , t : 1", "@@ -2,32 +2,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n@@ -29,32 +29,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n"},
- } {
- patches := dmp.PatchMake(tc.Text1, tc.Text2)
- patches = dmp.PatchSplitMax(patches)
-
- actual := dmp.PatchToText(patches)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))
- }
-}
-
-func TestPatchAddPadding(t *testing.T) {
- type TestCase struct {
- Name string
-
- Text1 string
- Text2 string
-
- Expected string
- ExpectedWithPadding string
- }
-
- dmp := New()
-
- for i, tc := range []TestCase{
- {"Both edges full", "", "test", "@@ -0,0 +1,4 @@\n+test\n", "@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n"},
- {"Both edges partial", "XY", "XtestY", "@@ -1,2 +1,6 @@\n X\n+test\n Y\n", "@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n"},
- {"Both edges none", "XXXXYYYY", "XXXXtestYYYY", "@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n", "@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n"},
- } {
- patches := dmp.PatchMake(tc.Text1, tc.Text2)
-
- actual := dmp.PatchToText(patches)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
-
- dmp.PatchAddPadding(patches)
-
- actualWithPadding := dmp.PatchToText(patches)
- assert.Equal(t, tc.ExpectedWithPadding, actualWithPadding, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-}
-
-func TestPatchApply(t *testing.T) {
- type TestCase struct {
- Name string
-
- Text1 string
- Text2 string
- TextBase string
-
- Expected string
- ExpectedApplies []bool
- }
-
- dmp := New()
- dmp.MatchDistance = 1000
- dmp.MatchThreshold = 0.5
- dmp.PatchDeleteThreshold = 0.5
-
- for i, tc := range []TestCase{
- {"Null case", "", "", "Hello world.", "Hello world.", []bool{}},
- {"Exact match", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", []bool{true, true}},
- {"Partial match", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", "The quick red rabbit jumps over the tired tiger.", "That quick red rabbit jumped over a tired tiger.", []bool{true, true}},
- {"Failed match", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", "I am the very model of a modern major general.", "I am the very model of a modern major general.", []bool{false, false}},
- {"Big delete, small Diff", "x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy", "x123456789012345678901234567890-----++++++++++-----123456789012345678901234567890y", "xabcy", []bool{true, true}},
- {"Big delete, big Diff 1", "x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy", "x12345678901234567890---------------++++++++++---------------12345678901234567890y", "xabc12345678901234567890---------------++++++++++---------------12345678901234567890y", []bool{false, true}},
- } {
- patches := dmp.PatchMake(tc.Text1, tc.Text2)
-
- actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-
- dmp.PatchDeleteThreshold = 0.6
-
- for i, tc := range []TestCase{
- {"Big delete, big Diff 2", "x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy", "x12345678901234567890---------------++++++++++---------------12345678901234567890y", "xabcy", []bool{true, true}},
- } {
- patches := dmp.PatchMake(tc.Text1, tc.Text2)
-
- actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-
- dmp.MatchDistance = 0
- dmp.MatchThreshold = 0.0
- dmp.PatchDeleteThreshold = 0.5
-
- for i, tc := range []TestCase{
- {"Compensate for failed patch", "abcdefghijklmnopqrstuvwxyz--------------------1234567890", "abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------1234567YYYYYYYYYY890", "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890", "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890", []bool{false, true}},
- } {
- patches := dmp.PatchMake(tc.Text1, tc.Text2)
-
- actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-
- dmp.MatchThreshold = 0.5
- dmp.MatchDistance = 1000
-
- for i, tc := range []TestCase{
- {"No side effects", "", "test", "", "test", []bool{true}},
- {"No side effects with major delete", "The quick brown fox jumps over the lazy dog.", "Woof", "The quick brown fox jumps over the lazy dog.", "Woof", []bool{true, true}},
- {"Edge exact match", "", "test", "", "test", []bool{true}},
- {"Near edge exact match", "XY", "XtestY", "XY", "XtestY", []bool{true}},
- {"Edge partial match", "y", "y123", "x", "x123", []bool{true}},
- } {
- patches := dmp.PatchMake(tc.Text1, tc.Text2)
-
- actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
- assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
- }
-}
-
func TestIndexOf(t *testing.T) {
type TestCase struct {
String string
diff --git a/diffmatchpatch/patch.go b/diffmatchpatch/patch.go
new file mode 100644
index 0000000..be16c4e
--- /dev/null
+++ b/diffmatchpatch/patch.go
@@ -0,0 +1,572 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+import (
+ "bytes"
+ "errors"
+ "math"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// Patch represents one patch operation.
+type Patch struct {
+ diffs []Diff
+ start1 int
+ start2 int
+ length1 int
+ length2 int
+}
+
+// String emulates GNU diff's format.
+// Header: @@ -382,8 +481,9 @@
+// Indicies are printed as 1-based, not 0-based.
+func (p *Patch) String() string {
+ var coords1, coords2 string
+
+ if p.length1 == 0 {
+ coords1 = strconv.Itoa(p.start1) + ",0"
+ } else if p.length1 == 1 {
+ coords1 = strconv.Itoa(p.start1 + 1)
+ } else {
+ coords1 = strconv.Itoa(p.start1+1) + "," + strconv.Itoa(p.length1)
+ }
+
+ if p.length2 == 0 {
+ coords2 = strconv.Itoa(p.start2) + ",0"
+ } else if p.length2 == 1 {
+ coords2 = strconv.Itoa(p.start2 + 1)
+ } else {
+ coords2 = strconv.Itoa(p.start2+1) + "," + strconv.Itoa(p.length2)
+ }
+
+ var text bytes.Buffer
+ _, _ = text.WriteString("@@ -" + coords1 + " +" + coords2 + " @@\n")
+
+ // Escape the body of the patch with %xx notation.
+ for _, aDiff := range p.diffs {
+ switch aDiff.Type {
+ case DiffInsert:
+ _, _ = text.WriteString("+")
+ case DiffDelete:
+ _, _ = text.WriteString("-")
+ case DiffEqual:
+ _, _ = text.WriteString(" ")
+ }
+
+ _, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1))
+ _, _ = text.WriteString("\n")
+ }
+
+ return unescaper.Replace(text.String())
+}
+
+// PatchAddContext increases the context until it is unique,
+// but doesn't let the pattern expand beyond MatchMaxBits.
+func (dmp *DiffMatchPatch) PatchAddContext(patch Patch, text string) Patch {
+ if len(text) == 0 {
+ return patch
+ }
+
+ pattern := text[patch.start2 : patch.start2+patch.length1]
+ padding := 0
+
+ // Look for the first and last matches of pattern in text. If two
+ // different matches are found, increase the pattern length.
+ for strings.Index(text, pattern) != strings.LastIndex(text, pattern) &&
+ len(pattern) < dmp.MatchMaxBits-2*dmp.PatchMargin {
+ padding += dmp.PatchMargin
+ maxStart := max(0, patch.start2-padding)
+ minEnd := min(len(text), patch.start2+patch.length1+padding)
+ pattern = text[maxStart:minEnd]
+ }
+ // Add one chunk for good luck.
+ padding += dmp.PatchMargin
+
+ // Add the prefix.
+ prefix := text[max(0, patch.start2-padding):patch.start2]
+ if len(prefix) != 0 {
+ patch.diffs = append([]Diff{Diff{DiffEqual, prefix}}, patch.diffs...)
+ }
+ // Add the suffix.
+ suffix := text[patch.start2+patch.length1 : min(len(text), patch.start2+patch.length1+padding)]
+ if len(suffix) != 0 {
+ patch.diffs = append(patch.diffs, Diff{DiffEqual, suffix})
+ }
+
+ // Roll back the start points.
+ patch.start1 -= len(prefix)
+ patch.start2 -= len(prefix)
+ // Extend the lengths.
+ patch.length1 += len(prefix) + len(suffix)
+ patch.length2 += len(prefix) + len(suffix)
+
+ return patch
+}
+
+// PatchMake computes a list of patches.
+func (dmp *DiffMatchPatch) PatchMake(opt ...interface{}) []Patch {
+ if len(opt) == 1 {
+ diffs, _ := opt[0].([]Diff)
+ text1 := dmp.DiffText1(diffs)
+ return dmp.PatchMake(text1, diffs)
+ } else if len(opt) == 2 {
+ text1 := opt[0].(string)
+ switch t := opt[1].(type) {
+ case string:
+ diffs := dmp.DiffMain(text1, t, true)
+ if len(diffs) > 2 {
+ diffs = dmp.DiffCleanupSemantic(diffs)
+ diffs = dmp.DiffCleanupEfficiency(diffs)
+ }
+ return dmp.PatchMake(text1, diffs)
+ case []Diff:
+ return dmp.patchMake2(text1, t)
+ }
+ } else if len(opt) == 3 {
+ return dmp.PatchMake(opt[0], opt[2])
+ }
+ return []Patch{}
+}
+
+// patchMake2 computes a list of patches to turn text1 into text2.
+// text2 is not provided, diffs are the delta between text1 and text2.
+func (dmp *DiffMatchPatch) patchMake2(text1 string, diffs []Diff) []Patch {
+ // Check for null inputs not needed since null can't be passed in C#.
+ patches := []Patch{}
+ if len(diffs) == 0 {
+ return patches // Get rid of the null case.
+ }
+
+ patch := Patch{}
+ charCount1 := 0 // Number of characters into the text1 string.
+ charCount2 := 0 // Number of characters into the text2 string.
+ // Start with text1 (prepatchText) and apply the diffs until we arrive at
+ // text2 (postpatchText). We recreate the patches one by one to determine
+ // context info.
+ prepatchText := text1
+ postpatchText := text1
+
+ for i, aDiff := range diffs {
+ if len(patch.diffs) == 0 && aDiff.Type != DiffEqual {
+ // A new patch starts here.
+ patch.start1 = charCount1
+ patch.start2 = charCount2
+ }
+
+ switch aDiff.Type {
+ case DiffInsert:
+ patch.diffs = append(patch.diffs, aDiff)
+ patch.length2 += len(aDiff.Text)
+ postpatchText = postpatchText[:charCount2] +
+ aDiff.Text + postpatchText[charCount2:]
+ case DiffDelete:
+ patch.length1 += len(aDiff.Text)
+ patch.diffs = append(patch.diffs, aDiff)
+ postpatchText = postpatchText[:charCount2] + postpatchText[charCount2+len(aDiff.Text):]
+ case DiffEqual:
+ if len(aDiff.Text) <= 2*dmp.PatchMargin &&
+ len(patch.diffs) != 0 && i != len(diffs)-1 {
+ // Small equality inside a patch.
+ patch.diffs = append(patch.diffs, aDiff)
+ patch.length1 += len(aDiff.Text)
+ patch.length2 += len(aDiff.Text)
+ }
+ if len(aDiff.Text) >= 2*dmp.PatchMargin {
+ // Time for a new patch.
+ if len(patch.diffs) != 0 {
+ patch = dmp.PatchAddContext(patch, prepatchText)
+ patches = append(patches, patch)
+ patch = Patch{}
+ // Unlike Unidiff, our patch lists have a rolling context.
+ // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
+ // Update prepatch text & pos to reflect the application of the
+ // just completed patch.
+ prepatchText = postpatchText
+ charCount1 = charCount2
+ }
+ }
+ }
+
+ // Update the current character count.
+ if aDiff.Type != DiffInsert {
+ charCount1 += len(aDiff.Text)
+ }
+ if aDiff.Type != DiffDelete {
+ charCount2 += len(aDiff.Text)
+ }
+ }
+
+ // Pick up the leftover patch if not empty.
+ if len(patch.diffs) != 0 {
+ patch = dmp.PatchAddContext(patch, prepatchText)
+ patches = append(patches, patch)
+ }
+
+ return patches
+}
+
+// PatchDeepCopy returns an array that is identical to a
+// given an array of patches.
+func (dmp *DiffMatchPatch) PatchDeepCopy(patches []Patch) []Patch {
+ patchesCopy := []Patch{}
+ for _, aPatch := range patches {
+ patchCopy := Patch{}
+ for _, aDiff := range aPatch.diffs {
+ patchCopy.diffs = append(patchCopy.diffs, Diff{
+ aDiff.Type,
+ aDiff.Text,
+ })
+ }
+ patchCopy.start1 = aPatch.start1
+ patchCopy.start2 = aPatch.start2
+ patchCopy.length1 = aPatch.length1
+ patchCopy.length2 = aPatch.length2
+ patchesCopy = append(patchesCopy, patchCopy)
+ }
+ return patchesCopy
+}
+
+// PatchApply merges a set of patches onto the text. Returns a patched text, as well
+// as an array of true/false values indicating which patches were applied.
+func (dmp *DiffMatchPatch) PatchApply(patches []Patch, text string) (string, []bool) {
+ if len(patches) == 0 {
+ return text, []bool{}
+ }
+
+ // Deep copy the patches so that no changes are made to originals.
+ patches = dmp.PatchDeepCopy(patches)
+
+ nullPadding := dmp.PatchAddPadding(patches)
+ text = nullPadding + text + nullPadding
+ patches = dmp.PatchSplitMax(patches)
+
+ x := 0
+ // delta keeps track of the offset between the expected and actual
+ // location of the previous patch. If there are patches expected at
+ // positions 10 and 20, but the first patch was found at 12, delta is 2
+ // and the second patch has an effective expected position of 22.
+ delta := 0
+ results := make([]bool, len(patches))
+ for _, aPatch := range patches {
+ expectedLoc := aPatch.start2 + delta
+ text1 := dmp.DiffText1(aPatch.diffs)
+ var startLoc int
+ endLoc := -1
+ if len(text1) > dmp.MatchMaxBits {
+ // PatchSplitMax will only provide an oversized pattern
+ // in the case of a monster delete.
+ startLoc = dmp.MatchMain(text, text1[:dmp.MatchMaxBits], expectedLoc)
+ if startLoc != -1 {
+ endLoc = dmp.MatchMain(text,
+ text1[len(text1)-dmp.MatchMaxBits:], expectedLoc+len(text1)-dmp.MatchMaxBits)
+ if endLoc == -1 || startLoc >= endLoc {
+ // Can't find valid trailing context. Drop this patch.
+ startLoc = -1
+ }
+ }
+ } else {
+ startLoc = dmp.MatchMain(text, text1, expectedLoc)
+ }
+ if startLoc == -1 {
+ // No match found. :(
+ results[x] = false
+ // Subtract the delta for this failed patch from subsequent patches.
+ delta -= aPatch.length2 - aPatch.length1
+ } else {
+ // Found a match. :)
+ results[x] = true
+ delta = startLoc - expectedLoc
+ var text2 string
+ if endLoc == -1 {
+ text2 = text[startLoc:int(math.Min(float64(startLoc+len(text1)), float64(len(text))))]
+ } else {
+ text2 = text[startLoc:int(math.Min(float64(endLoc+dmp.MatchMaxBits), float64(len(text))))]
+ }
+ if text1 == text2 {
+ // Perfect match, just shove the Replacement text in.
+ text = text[:startLoc] + dmp.DiffText2(aPatch.diffs) + text[startLoc+len(text1):]
+ } else {
+ // Imperfect match. Run a diff to get a framework of equivalent
+ // indices.
+ diffs := dmp.DiffMain(text1, text2, false)
+ if len(text1) > dmp.MatchMaxBits && float64(dmp.DiffLevenshtein(diffs))/float64(len(text1)) > dmp.PatchDeleteThreshold {
+ // The end points match, but the content is unacceptably bad.
+ results[x] = false
+ } else {
+ diffs = dmp.DiffCleanupSemanticLossless(diffs)
+ index1 := 0
+ for _, aDiff := range aPatch.diffs {
+ if aDiff.Type != DiffEqual {
+ index2 := dmp.DiffXIndex(diffs, index1)
+ if aDiff.Type == DiffInsert {
+ // Insertion
+ text = text[:startLoc+index2] + aDiff.Text + text[startLoc+index2:]
+ } else if aDiff.Type == DiffDelete {
+ // Deletion
+ startIndex := startLoc + index2
+ text = text[:startIndex] +
+ text[startIndex+dmp.DiffXIndex(diffs, index1+len(aDiff.Text))-index2:]
+ }
+ }
+ if aDiff.Type != DiffDelete {
+ index1 += len(aDiff.Text)
+ }
+ }
+ }
+ }
+ }
+ x++
+ }
+ // Strip the padding off.
+ text = text[len(nullPadding) : len(nullPadding)+(len(text)-2*len(nullPadding))]
+ return text, results
+}
+
+// PatchAddPadding adds some padding on text start and end so that edges can match something.
+// Intended to be called only from within patchApply.
+func (dmp *DiffMatchPatch) PatchAddPadding(patches []Patch) string {
+ paddingLength := dmp.PatchMargin
+ nullPadding := ""
+ for x := 1; x <= paddingLength; x++ {
+ nullPadding += string(x)
+ }
+
+ // Bump all the patches forward.
+ for i := range patches {
+ patches[i].start1 += paddingLength
+ patches[i].start2 += paddingLength
+ }
+
+ // Add some padding on start of first diff.
+ if len(patches[0].diffs) == 0 || patches[0].diffs[0].Type != DiffEqual {
+ // Add nullPadding equality.
+ patches[0].diffs = append([]Diff{Diff{DiffEqual, nullPadding}}, patches[0].diffs...)
+ patches[0].start1 -= paddingLength // Should be 0.
+ patches[0].start2 -= paddingLength // Should be 0.
+ patches[0].length1 += paddingLength
+ patches[0].length2 += paddingLength
+ } else if paddingLength > len(patches[0].diffs[0].Text) {
+ // Grow first equality.
+ extraLength := paddingLength - len(patches[0].diffs[0].Text)
+ patches[0].diffs[0].Text = nullPadding[len(patches[0].diffs[0].Text):] + patches[0].diffs[0].Text
+ patches[0].start1 -= extraLength
+ patches[0].start2 -= extraLength
+ patches[0].length1 += extraLength
+ patches[0].length2 += extraLength
+ }
+
+ // Add some padding on end of last diff.
+ last := len(patches) - 1
+ if len(patches[last].diffs) == 0 || patches[last].diffs[len(patches[last].diffs)-1].Type != DiffEqual {
+ // Add nullPadding equality.
+ patches[last].diffs = append(patches[last].diffs, Diff{DiffEqual, nullPadding})
+ patches[last].length1 += paddingLength
+ patches[last].length2 += paddingLength
+ } else if paddingLength > len(patches[last].diffs[len(patches[last].diffs)-1].Text) {
+ // Grow last equality.
+ lastDiff := patches[last].diffs[len(patches[last].diffs)-1]
+ extraLength := paddingLength - len(lastDiff.Text)
+ patches[last].diffs[len(patches[last].diffs)-1].Text += nullPadding[:extraLength]
+ patches[last].length1 += extraLength
+ patches[last].length2 += extraLength
+ }
+
+ return nullPadding
+}
+
+// PatchSplitMax looks through the patches and breaks up any which are longer than the
+// maximum limit of the match algorithm.
+// Intended to be called only from within patchApply.
+func (dmp *DiffMatchPatch) PatchSplitMax(patches []Patch) []Patch {
+ patchSize := dmp.MatchMaxBits
+ for x := 0; x < len(patches); x++ {
+ if patches[x].length1 <= patchSize {
+ continue
+ }
+ bigpatch := patches[x]
+ // Remove the big old patch.
+ patches = append(patches[:x], patches[x+1:]...)
+ x--
+
+ start1 := bigpatch.start1
+ start2 := bigpatch.start2
+ precontext := ""
+ for len(bigpatch.diffs) != 0 {
+ // Create one of several smaller patches.
+ patch := Patch{}
+ empty := true
+ patch.start1 = start1 - len(precontext)
+ patch.start2 = start2 - len(precontext)
+ if len(precontext) != 0 {
+ patch.length1 = len(precontext)
+ patch.length2 = len(precontext)
+ patch.diffs = append(patch.diffs, Diff{DiffEqual, precontext})
+ }
+ for len(bigpatch.diffs) != 0 && patch.length1 < patchSize-dmp.PatchMargin {
+ diffType := bigpatch.diffs[0].Type
+ diffText := bigpatch.diffs[0].Text
+ if diffType == DiffInsert {
+ // Insertions are harmless.
+ patch.length2 += len(diffText)
+ start2 += len(diffText)
+ patch.diffs = append(patch.diffs, bigpatch.diffs[0])
+ bigpatch.diffs = bigpatch.diffs[1:]
+ empty = false
+ } else if diffType == DiffDelete && len(patch.diffs) == 1 && patch.diffs[0].Type == DiffEqual && len(diffText) > 2*patchSize {
+ // This is a large deletion. Let it pass in one chunk.
+ patch.length1 += len(diffText)
+ start1 += len(diffText)
+ empty = false
+ patch.diffs = append(patch.diffs, Diff{diffType, diffText})
+ bigpatch.diffs = bigpatch.diffs[1:]
+ } else {
+ // Deletion or equality. Only take as much as we can stomach.
+ diffText = diffText[:min(len(diffText), patchSize-patch.length1-dmp.PatchMargin)]
+
+ patch.length1 += len(diffText)
+ start1 += len(diffText)
+ if diffType == DiffEqual {
+ patch.length2 += len(diffText)
+ start2 += len(diffText)
+ } else {
+ empty = false
+ }
+ patch.diffs = append(patch.diffs, Diff{diffType, diffText})
+ if diffText == bigpatch.diffs[0].Text {
+ bigpatch.diffs = bigpatch.diffs[1:]
+ } else {
+ bigpatch.diffs[0].Text =
+ bigpatch.diffs[0].Text[len(diffText):]
+ }
+ }
+ }
+ // Compute the head context for the next patch.
+ precontext = dmp.DiffText2(patch.diffs)
+ precontext = precontext[max(0, len(precontext)-dmp.PatchMargin):]
+
+ postcontext := ""
+ // Append the end context for this patch.
+ if len(dmp.DiffText1(bigpatch.diffs)) > dmp.PatchMargin {
+ postcontext = dmp.DiffText1(bigpatch.diffs)[:dmp.PatchMargin]
+ } else {
+ postcontext = dmp.DiffText1(bigpatch.diffs)
+ }
+
+ if len(postcontext) != 0 {
+ patch.length1 += len(postcontext)
+ patch.length2 += len(postcontext)
+ if len(patch.diffs) != 0 && patch.diffs[len(patch.diffs)-1].Type == DiffEqual {
+ patch.diffs[len(patch.diffs)-1].Text += postcontext
+ } else {
+ patch.diffs = append(patch.diffs, Diff{DiffEqual, postcontext})
+ }
+ }
+ if !empty {
+ x++
+ patches = append(patches[:x], append([]Patch{patch}, patches[x:]...)...)
+ }
+ }
+ }
+ return patches
+}
+
+// PatchToText takes a list of patches and returns a textual representation.
+func (dmp *DiffMatchPatch) PatchToText(patches []Patch) string {
+ var text bytes.Buffer
+ for _, aPatch := range patches {
+ _, _ = text.WriteString(aPatch.String())
+ }
+ return text.String()
+}
+
+// PatchFromText parses a textual representation of patches and returns a List of Patch
+// objects.
+func (dmp *DiffMatchPatch) PatchFromText(textline string) ([]Patch, error) {
+ patches := []Patch{}
+ if len(textline) == 0 {
+ return patches, nil
+ }
+ text := strings.Split(textline, "\n")
+ textPointer := 0
+ patchHeader := regexp.MustCompile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$")
+
+ var patch Patch
+ var sign uint8
+ var line string
+ for textPointer < len(text) {
+
+ if !patchHeader.MatchString(text[textPointer]) {
+ return patches, errors.New("Invalid patch string: " + text[textPointer])
+ }
+
+ patch = Patch{}
+ m := patchHeader.FindStringSubmatch(text[textPointer])
+
+ patch.start1, _ = strconv.Atoi(m[1])
+ if len(m[2]) == 0 {
+ patch.start1--
+ patch.length1 = 1
+ } else if m[2] == "0" {
+ patch.length1 = 0
+ } else {
+ patch.start1--
+ patch.length1, _ = strconv.Atoi(m[2])
+ }
+
+ patch.start2, _ = strconv.Atoi(m[3])
+
+ if len(m[4]) == 0 {
+ patch.start2--
+ patch.length2 = 1
+ } else if m[4] == "0" {
+ patch.length2 = 0
+ } else {
+ patch.start2--
+ patch.length2, _ = strconv.Atoi(m[4])
+ }
+ textPointer++
+
+ for textPointer < len(text) {
+ if len(text[textPointer]) > 0 {
+ sign = text[textPointer][0]
+ } else {
+ textPointer++
+ continue
+ }
+
+ line = text[textPointer][1:]
+ line = strings.Replace(line, "+", "%2b", -1)
+ line, _ = url.QueryUnescape(line)
+ if sign == '-' {
+ // Deletion.
+ patch.diffs = append(patch.diffs, Diff{DiffDelete, line})
+ } else if sign == '+' {
+ // Insertion.
+ patch.diffs = append(patch.diffs, Diff{DiffInsert, line})
+ } else if sign == ' ' {
+ // Minor equality.
+ patch.diffs = append(patch.diffs, Diff{DiffEqual, line})
+ } else if sign == '@' {
+ // Start of next patch.
+ break
+ } else {
+ // WTF?
+ return patches, errors.New("Invalid patch mode '" + string(sign) + "' in: " + string(line))
+ }
+ textPointer++
+ }
+
+ patches = append(patches, patch)
+ }
+ return patches, nil
+}
diff --git a/diffmatchpatch/patch_test.go b/diffmatchpatch/patch_test.go
new file mode 100644
index 0000000..12d1441
--- /dev/null
+++ b/diffmatchpatch/patch_test.go
@@ -0,0 +1,334 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPatchString(t *testing.T) {
+ type TestCase struct {
+ Patch Patch
+
+ Expected string
+ }
+
+ for i, tc := range []TestCase{
+ {
+ Patch: Patch{
+ start1: 20,
+ start2: 21,
+ length1: 18,
+ length2: 17,
+
+ diffs: []Diff{
+ {DiffEqual, "jump"},
+ {DiffDelete, "s"},
+ {DiffInsert, "ed"},
+ {DiffEqual, " over "},
+ {DiffDelete, "the"},
+ {DiffInsert, "a"},
+ {DiffEqual, "\nlaz"},
+ },
+ },
+
+ Expected: "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n",
+ },
+ } {
+ actual := tc.Patch.String()
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))
+ }
+}
+
+func TestPatchFromText(t *testing.T) {
+ type TestCase struct {
+ Patch string
+
+ ErrorMessagePrefix string
+ }
+
+ dmp := New()
+
+ for i, tc := range []TestCase{
+ {"", ""},
+ {"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n", ""},
+ {"@@ -1 +1 @@\n-a\n+b\n", ""},
+ {"@@ -1,3 +0,0 @@\n-abc\n", ""},
+ {"@@ -0,0 +1,3 @@\n+abc\n", ""},
+ {"Bad\nPatch\n", "Invalid patch string"},
+ } {
+ patches, err := dmp.PatchFromText(tc.Patch)
+ if tc.ErrorMessagePrefix == "" {
+ assert.Nil(t, err)
+
+ if tc.Patch == "" {
+ assert.Equal(t, []Patch{}, patches, fmt.Sprintf("Test case #%d, %#v", i, tc))
+ } else {
+ assert.Equal(t, tc.Patch, patches[0].String(), fmt.Sprintf("Test case #%d, %#v", i, tc))
+ }
+ } else {
+ e := err.Error()
+ if strings.HasPrefix(e, tc.ErrorMessagePrefix) {
+ e = tc.ErrorMessagePrefix
+ }
+ assert.Equal(t, tc.ErrorMessagePrefix, e)
+ }
+ }
+
+ diffs := []Diff{
+ {DiffDelete, "`1234567890-=[]\\;',./"},
+ {DiffInsert, "~!@#$%^&*()_+{}|:\"<>?"},
+ }
+
+ patches, err := dmp.PatchFromText("@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n")
+ assert.Len(t, patches, 1)
+ assert.Equal(t, diffs,
+ patches[0].diffs,
+ )
+ assert.Nil(t, err)
+}
+
+func TestPatchToText(t *testing.T) {
+ type TestCase struct {
+ Patch string
+ }
+
+ dmp := New()
+
+ for i, tc := range []TestCase{
+ {"@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
+ {"@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n"},
+ } {
+ patches, err := dmp.PatchFromText(tc.Patch)
+ assert.Nil(t, err)
+
+ actual := dmp.PatchToText(patches)
+ assert.Equal(t, tc.Patch, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))
+ }
+}
+
+func TestPatchAddContext(t *testing.T) {
+ type TestCase struct {
+ Name string
+
+ Patch string
+ Text string
+
+ Expected string
+ }
+
+ dmp := New()
+ dmp.PatchMargin = 4
+
+ for i, tc := range []TestCase{
+ {"Simple case", "@@ -21,4 +21,10 @@\n-jump\n+somersault\n", "The quick brown fox jumps over the lazy dog.", "@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n"},
+ {"Not enough trailing context", "@@ -21,4 +21,10 @@\n-jump\n+somersault\n", "The quick brown fox jumps.", "@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n"},
+ {"Not enough leading context", "@@ -3 +3,2 @@\n-e\n+at\n", "The quick brown fox jumps.", "@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n"},
+ {"Ambiguity", "@@ -3 +3,2 @@\n-e\n+at\n", "The quick brown fox jumps. The quick brown fox crashes.", "@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n"},
+ } {
+ patches, err := dmp.PatchFromText(tc.Patch)
+ assert.Nil(t, err)
+
+ actual := dmp.PatchAddContext(patches[0], tc.Text)
+ assert.Equal(t, tc.Expected, actual.String(), fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+}
+
+func TestPatchMakeAndPatchToText(t *testing.T) {
+ type TestCase struct {
+ Name string
+
+ Input1 interface{}
+ Input2 interface{}
+ Input3 interface{}
+
+ Expected string
+ }
+
+ dmp := New()
+
+ text1 := "The quick brown fox jumps over the lazy dog."
+ text2 := "That quick brown fox jumped over a lazy dog."
+
+ for i, tc := range []TestCase{
+ {"Null case", "", "", nil, ""},
+ {"Text2+Text1 inputs", text2, text1, nil, "@@ -1,8 +1,7 @@\n Th\n-at\n+e\n qui\n@@ -21,17 +21,18 @@\n jump\n-ed\n+s\n over \n-a\n+the\n laz\n"},
+ {"Text1+Text2 inputs", text1, text2, nil, "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
+ {"Diff input", dmp.DiffMain(text1, text2, false), nil, nil, "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
+ {"Text1+Diff inputs", text1, dmp.DiffMain(text1, text2, false), nil, "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
+ {"Text1+Text2+Diff inputs (deprecated)", text1, text2, dmp.DiffMain(text1, text2, false), "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"},
+ {"Character encoding", "`1234567890-=[]\\;',./", "~!@#$%^&*()_+{}|:\"<>?", nil, "@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n"},
+ {"Long string with repeats", strings.Repeat("abcdef", 100), strings.Repeat("abcdef", 100) + "123", nil, "@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n"},
+ {"Corner case of #31 fixed by #32", "2016-09-01T03:07:14.807830741Z", "2016-09-01T03:07:15.154800781Z", nil, "@@ -15,16 +15,16 @@\n 07:1\n+5.15\n 4\n-.\n 80\n+0\n 78\n-3074\n 1Z\n"},
+ } {
+ var patches []Patch
+ if tc.Input3 != nil {
+ patches = dmp.PatchMake(tc.Input1, tc.Input2, tc.Input3)
+ } else if tc.Input2 != nil {
+ patches = dmp.PatchMake(tc.Input1, tc.Input2)
+ } else if ps, ok := tc.Input1.([]Patch); ok {
+ patches = ps
+ } else {
+ patches = dmp.PatchMake(tc.Input1)
+ }
+
+ actual := dmp.PatchToText(patches)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+
+ // Corner case of #28 wrong patch with timeout of 0
+ dmp.DiffTimeout = 0
+
+ text1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ut risus et enim consectetur convallis a non ipsum. Sed nec nibh cursus, interdum libero vel."
+ text2 = "Lorem a ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ut risus et enim consectetur convallis a non ipsum. Sed nec nibh cursus, interdum liberovel."
+
+ diffs := dmp.DiffMain(text1, text2, true)
+ // Additional check that the diff texts are equal to the originals even if we are using DiffMain with checklines=true #29
+ assert.Equal(t, text1, dmp.DiffText1(diffs))
+ assert.Equal(t, text2, dmp.DiffText2(diffs))
+
+ patches := dmp.PatchMake(text1, diffs)
+
+ actual := dmp.PatchToText(patches)
+ assert.Equal(t, "@@ -1,14 +1,16 @@\n Lorem \n+a \n ipsum do\n@@ -148,13 +148,12 @@\n m libero\n- \n vel.\n", actual)
+}
+
+func TestPatchSplitMax(t *testing.T) {
+ type TestCase struct {
+ Text1 string
+ Text2 string
+
+ Expected string
+ }
+
+ dmp := New()
+
+ for i, tc := range []TestCase{
+ {"abcdefghijklmnopqrstuvwxyz01234567890", "XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0", "@@ -1,32 +1,46 @@\n+X\n ab\n+X\n cd\n+X\n ef\n+X\n gh\n+X\n ij\n+X\n kl\n+X\n mn\n+X\n op\n+X\n qr\n+X\n st\n+X\n uv\n+X\n wx\n+X\n yz\n+X\n 012345\n@@ -25,13 +39,18 @@\n zX01\n+X\n 23\n+X\n 45\n+X\n 67\n+X\n 89\n+X\n 0\n"},
+ {"abcdef1234567890123456789012345678901234567890123456789012345678901234567890uvwxyz", "abcdefuvwxyz", "@@ -3,78 +3,8 @@\n cdef\n-1234567890123456789012345678901234567890123456789012345678901234567890\n uvwx\n"},
+ {"1234567890123456789012345678901234567890123456789012345678901234567890", "abc", "@@ -1,32 +1,4 @@\n-1234567890123456789012345678\n 9012\n@@ -29,32 +1,4 @@\n-9012345678901234567890123456\n 7890\n@@ -57,14 +1,3 @@\n-78901234567890\n+abc\n"},
+ {"abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1", "abcdefghij , h : 1 , t : 1 abcdefghij , h : 1 , t : 1 abcdefghij , h : 0 , t : 1", "@@ -2,32 +2,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n@@ -29,32 +29,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n"},
+ } {
+ patches := dmp.PatchMake(tc.Text1, tc.Text2)
+ patches = dmp.PatchSplitMax(patches)
+
+ actual := dmp.PatchToText(patches)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %#v", i, tc))
+ }
+}
+
+func TestPatchAddPadding(t *testing.T) {
+ type TestCase struct {
+ Name string
+
+ Text1 string
+ Text2 string
+
+ Expected string
+ ExpectedWithPadding string
+ }
+
+ dmp := New()
+
+ for i, tc := range []TestCase{
+ {"Both edges full", "", "test", "@@ -0,0 +1,4 @@\n+test\n", "@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n"},
+ {"Both edges partial", "XY", "XtestY", "@@ -1,2 +1,6 @@\n X\n+test\n Y\n", "@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n"},
+ {"Both edges none", "XXXXYYYY", "XXXXtestYYYY", "@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n", "@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n"},
+ } {
+ patches := dmp.PatchMake(tc.Text1, tc.Text2)
+
+ actual := dmp.PatchToText(patches)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+
+ dmp.PatchAddPadding(patches)
+
+ actualWithPadding := dmp.PatchToText(patches)
+ assert.Equal(t, tc.ExpectedWithPadding, actualWithPadding, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+}
+
+func TestPatchApply(t *testing.T) {
+ type TestCase struct {
+ Name string
+
+ Text1 string
+ Text2 string
+ TextBase string
+
+ Expected string
+ ExpectedApplies []bool
+ }
+
+ dmp := New()
+ dmp.MatchDistance = 1000
+ dmp.MatchThreshold = 0.5
+ dmp.PatchDeleteThreshold = 0.5
+
+ for i, tc := range []TestCase{
+ {"Null case", "", "", "Hello world.", "Hello world.", []bool{}},
+ {"Exact match", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", []bool{true, true}},
+ {"Partial match", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", "The quick red rabbit jumps over the tired tiger.", "That quick red rabbit jumped over a tired tiger.", []bool{true, true}},
+ {"Failed match", "The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog.", "I am the very model of a modern major general.", "I am the very model of a modern major general.", []bool{false, false}},
+ {"Big delete, small Diff", "x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy", "x123456789012345678901234567890-----++++++++++-----123456789012345678901234567890y", "xabcy", []bool{true, true}},
+ {"Big delete, big Diff 1", "x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy", "x12345678901234567890---------------++++++++++---------------12345678901234567890y", "xabc12345678901234567890---------------++++++++++---------------12345678901234567890y", []bool{false, true}},
+ } {
+ patches := dmp.PatchMake(tc.Text1, tc.Text2)
+
+ actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+
+ dmp.PatchDeleteThreshold = 0.6
+
+ for i, tc := range []TestCase{
+ {"Big delete, big Diff 2", "x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy", "x12345678901234567890---------------++++++++++---------------12345678901234567890y", "xabcy", []bool{true, true}},
+ } {
+ patches := dmp.PatchMake(tc.Text1, tc.Text2)
+
+ actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+
+ dmp.MatchDistance = 0
+ dmp.MatchThreshold = 0.0
+ dmp.PatchDeleteThreshold = 0.5
+
+ for i, tc := range []TestCase{
+ {"Compensate for failed patch", "abcdefghijklmnopqrstuvwxyz--------------------1234567890", "abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------1234567YYYYYYYYYY890", "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890", "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890", []bool{false, true}},
+ } {
+ patches := dmp.PatchMake(tc.Text1, tc.Text2)
+
+ actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+
+ dmp.MatchThreshold = 0.5
+ dmp.MatchDistance = 1000
+
+ for i, tc := range []TestCase{
+ {"No side effects", "", "test", "", "test", []bool{true}},
+ {"No side effects with major delete", "The quick brown fox jumps over the lazy dog.", "Woof", "The quick brown fox jumps over the lazy dog.", "Woof", []bool{true, true}},
+ {"Edge exact match", "", "test", "", "test", []bool{true}},
+ {"Near edge exact match", "XY", "XtestY", "XY", "XtestY", []bool{true}},
+ {"Edge partial match", "y", "y123", "x", "x123", []bool{true}},
+ } {
+ patches := dmp.PatchMake(tc.Text1, tc.Text2)
+
+ actual, actualApplies := dmp.PatchApply(patches, tc.TextBase)
+ assert.Equal(t, tc.Expected, actual, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
+ }
+}