diff --git a/util/diff/delta.go b/util/diff/delta.go deleted file mode 100644 index 2c840bc3..00000000 --- a/util/diff/delta.go +++ /dev/null @@ -1,461 +0,0 @@ -package diff - -import ( - "errors" - dmp "github.com/sergi/go-diff/diffmatchpatch" - "reflect" - "strconv" -) - -// A Delta represents an atomic difference between two JSON objects. -type Delta interface { - // Similarity calculates the similarity of the Delta values. - // The return value is normalized from 0 to 1, - // 0 is completely different and 1 is they are same - Similarity() (similarity float64) -} - -// To cache the calculated similarity, -// concrete Deltas can use similariter and similarityCache -type similariter interface { - similarity() (similarity float64) -} - -type similarityCache struct { - similariter - value float64 -} - -func newSimilarityCache(sim similariter) similarityCache { - cache := similarityCache{similariter: sim, value: -1} - return cache -} - -func (cache similarityCache) Similarity() (similarity float64) { - if cache.value < 0 { - cache.value = cache.similariter.similarity() - } - return cache.value -} - -// A Position represents the position of a Delta in an object or an array. -type Position interface { - // String returns the position as a string - String() (name string) - - // CompareTo returns a true if the Position is smaller than another Position. - // This function is used to sort Positions by the sort package. - CompareTo(another Position) bool -} - -// A Name is a Postition with a string, which means the delta is in an object. -type Name string - -func (n Name) String() (name string) { - return string(n) -} - -func (n Name) CompareTo(another Position) bool { - return n < another.(Name) -} - -// A Index is a Position with an int value, which means the Delta is in an Array. -type Index int - -func (i Index) String() (name string) { - return strconv.Itoa(int(i)) -} - -func (i Index) CompareTo(another Position) bool { - return i < another.(Index) -} - -// A PreDelta is a Delta that has a position of the left side JSON object. -// Deltas implements this interface should be applies before PostDeltas. -type PreDelta interface { - // PrePosition returns the Position. - PrePosition() Position - - // PreApply applies the delta to object. - PreApply(object interface{}) interface{} -} - -type preDelta struct{ Position } - -func (i preDelta) PrePosition() Position { - return Position(i.Position) -} - -type preDeltas []PreDelta - -// for sorting -func (s preDeltas) Len() int { - return len(s) -} - -// for sorting -func (s preDeltas) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// for sorting -func (s preDeltas) Less(i, j int) bool { - return !s[i].PrePosition().CompareTo(s[j].PrePosition()) -} - -// A PreDelta is a Delta that has a position of the right side JSON object. -// Deltas implements this interface should be applies after PreDeltas. -type PostDelta interface { - // PostPosition returns the Position. - PostPosition() Position - - // PostApply applies the delta to object. - PostApply(object interface{}) interface{} -} - -type postDelta struct{ Position } - -func (i postDelta) PostPosition() Position { - return Position(i.Position) -} - -type postDeltas []PostDelta - -// for sorting -func (s postDeltas) Len() int { - return len(s) -} - -// for sorting -func (s postDeltas) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// for sorting -func (s postDeltas) Less(i, j int) bool { - return s[i].PostPosition().CompareTo(s[j].PostPosition()) -} - -// An Object is a Delta that represents an object of JSON -type Object struct { - postDelta - similarityCache - - // Deltas holds internal Deltas - Deltas []Delta -} - -// NewObject returns an Object -func NewObject(position Position, deltas []Delta) *Object { - d := Object{postDelta: postDelta{position}, Deltas: deltas} - d.similarityCache = newSimilarityCache(&d) - return &d -} - -func (d *Object) PostApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - o := object.(map[string]interface{}) - n := string(d.PostPosition().(Name)) - o[n] = applyDeltas(d.Deltas, o[n]) - case []interface{}: - o := object.([]interface{}) - n := int(d.PostPosition().(Index)) - o[n] = applyDeltas(d.Deltas, o[n]) - } - return object -} - -func (d *Object) similarity() (similarity float64) { - similarity = deltasSimilarity(d.Deltas) - return -} - -// An Array is a Delta that represents an array of JSON -type Array struct { - postDelta - similarityCache - - // Deltas holds internal Deltas - Deltas []Delta -} - -// NewArray returns an Array -func NewArray(position Position, deltas []Delta) *Array { - d := Array{postDelta: postDelta{position}, Deltas: deltas} - d.similarityCache = newSimilarityCache(&d) - return &d -} - -func (d *Array) PostApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - o := object.(map[string]interface{}) - n := string(d.PostPosition().(Name)) - o[n] = applyDeltas(d.Deltas, o[n]) - case []interface{}: - o := object.([]interface{}) - n := int(d.PostPosition().(Index)) - o[n] = applyDeltas(d.Deltas, o[n]) - } - return object -} - -func (d *Array) similarity() (similarity float64) { - similarity = deltasSimilarity(d.Deltas) - return -} - -// An Added represents a new added field of an object or an array -type Added struct { - postDelta - similarityCache - - // Values holds the added value - Value interface{} -} - -// NewAdded returns a new Added -func NewAdded(position Position, value interface{}) *Added { - d := Added{postDelta: postDelta{position}, Value: value} - return &d -} - -func (d *Added) PostApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - object.(map[string]interface{})[string(d.PostPosition().(Name))] = d.Value - case []interface{}: - i := int(d.PostPosition().(Index)) - o := object.([]interface{}) - if i < len(o) { - o = append(o, 0) //dummy - copy(o[i+1:], o[i:]) - o[i] = d.Value - object = o - } else { - object = append(o, d.Value) - } - } - - return object -} - -func (d *Added) similarity() (similarity float64) { - return 0 -} - -// A Modified represents a field whose value is changed. -type Modified struct { - postDelta - similarityCache - - // The value before modification - OldValue interface{} - - // The value after modification - NewValue interface{} -} - -// NewModified returns a Modified -func NewModified(position Position, oldValue, newValue interface{}) *Modified { - d := Modified{ - postDelta: postDelta{position}, - OldValue: oldValue, - NewValue: newValue, - } - d.similarityCache = newSimilarityCache(&d) - return &d - -} - -func (d *Modified) PostApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - // TODO check old value - object.(map[string]interface{})[string(d.PostPosition().(Name))] = d.NewValue - case []interface{}: - object.([]interface{})[int(d.PostPosition().(Index))] = d.NewValue - } - return object -} - -func (d *Modified) similarity() (similarity float64) { - similarity += 0.3 // at least, they are at the same position - if reflect.TypeOf(d.OldValue) == reflect.TypeOf(d.NewValue) { - similarity += 0.3 // types are same - - switch d.OldValue.(type) { - case string: - similarity += 0.4 * stringSimilarity(d.OldValue.(string), d.NewValue.(string)) - case float64: - ratio := d.OldValue.(float64) / d.NewValue.(float64) - if ratio > 1 { - ratio = 1 / ratio - } - similarity += 0.4 * ratio - } - } - return -} - -// A TextDiff represents a Modified with TextDiff between the old and the new values. -type TextDiff struct { - Modified - - // Diff string - Diff []dmp.Patch -} - -// NewTextDiff returns -func NewTextDiff(position Position, diff []dmp.Patch, oldValue, newValue interface{}) *TextDiff { - d := TextDiff{ - Modified: *NewModified(position, oldValue, newValue), - Diff: diff, - } - return &d -} - -func (d *TextDiff) PostApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - o := object.(map[string]interface{}) - i := string(d.PostPosition().(Name)) - // TODO error - d.OldValue = o[i] - // TODO error - d.patch() - o[i] = d.NewValue - case []interface{}: - o := object.([]interface{}) - i := d.PostPosition().(Index) - d.OldValue = o[i] - // TODO error - d.patch() - o[i] = d.NewValue - } - return object -} - -func (d *TextDiff) patch() error { - if d.OldValue == nil { - return errors.New("Old Value is not set") - } - patcher := dmp.New() - patched, successes := patcher.PatchApply(d.Diff, d.OldValue.(string)) - for _, success := range successes { - if !success { - return errors.New("Failed to apply a patch") - } - } - d.NewValue = patched - return nil -} - -func (d *TextDiff) DiffString() string { - dmp := dmp.New() - return dmp.PatchToText(d.Diff) -} - -// A Delted represents deleted field or index of an Object or an Array. -type Deleted struct { - preDelta - - // The value deleted - Value interface{} -} - -// NewDeleted returns a Deleted -func NewDeleted(position Position, value interface{}) *Deleted { - d := Deleted{ - preDelta: preDelta{position}, - Value: value, - } - return &d - -} - -func (d *Deleted) PreApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - // TODO check old value - delete(object.(map[string]interface{}), string(d.PrePosition().(Name))) - case []interface{}: - i := int(d.PrePosition().(Index)) - o := object.([]interface{}) - object = append(o[:i], o[i+1:]...) - } - return object -} - -func (d Deleted) Similarity() (similarity float64) { - return 0 -} - -// A Moved represents field that is moved, which means the index or name is -// changed. Note that, in this library, assigning a Moved and a Modified to -// a single position is not allowed. For the compatibility with jsondiffpatch, -// the Moved in this library can hold the old and new value in it. -type Moved struct { - preDelta - postDelta - similarityCache - // The value before moving - Value interface{} - // The delta applied after moving (for compatibility) - Delta interface{} -} - -func NewMoved(oldPosition Position, newPosition Position, value interface{}, delta Delta) *Moved { - d := Moved{ - preDelta: preDelta{oldPosition}, - postDelta: postDelta{newPosition}, - Value: value, - Delta: delta, - } - d.similarityCache = newSimilarityCache(&d) - return &d -} - -func (d *Moved) PreApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - //not supported - case []interface{}: - i := int(d.PrePosition().(Index)) - o := object.([]interface{}) - d.Value = o[i] - object = append(o[:i], o[i+1:]...) - } - return object -} - -func (d *Moved) PostApply(object interface{}) interface{} { - switch object.(type) { - case map[string]interface{}: - //not supported - case []interface{}: - i := int(d.PostPosition().(Index)) - o := object.([]interface{}) - o = append(o, 0) //dummy - copy(o[i+1:], o[i:]) - o[i] = d.Value - object = o - } - - if d.Delta != nil { - d.Delta.(PostDelta).PostApply(object) - } - - return object -} - -func (d *Moved) similarity() (similarity float64) { - similarity = 0.6 // as type and contens are same - ratio := float64(d.PrePosition().(Index)) / float64(d.PostPosition().(Index)) - if ratio > 1 { - ratio = 1 / ratio - } - similarity += 0.4 * ratio - return -} diff --git a/util/diff/diff.go b/util/diff/diff.go deleted file mode 100644 index bcac2302..00000000 --- a/util/diff/diff.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright © 2016 Abcum Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// github.com/sergi/go-diff/diffmatchpatch -// github.com/yudai/gojsondiff -// github.com/yudai/golcs - -package diff - -/*type Diff struct{} - -func (d *Diff) ToPACK() []byte { - return []byte("DIFF") -}*/ - -import ( - "container/list" - "encoding/json" - "reflect" - "sort" - - dmp "github.com/sergi/go-diff/diffmatchpatch" - "github.com/yudai/golcs" -) - -// A Diff holds deltas generated by a Differ -type Diff interface { - // Deltas returns Deltas that describe differences between two JSON objects - Deltas() []Delta - // Modified returnes true if Diff has at least one Delta. - Modified() bool -} - -type diff struct { - deltas []Delta -} - -func (diff *diff) Deltas() []Delta { - return diff.deltas -} - -func (diff *diff) Modified() bool { - return len(diff.deltas) > 0 -} - -// A Differ conmapres JSON objects and apply patches -type Differ struct { - textDiffMinimumLength int -} - -// New returns new Differ with default configuration -func New() *Differ { - return &Differ{ - textDiffMinimumLength: 10, - } -} - -// Compare compares two JSON strings as []bytes and return a Diff object. -func (differ *Differ) Compare(left []byte, right []byte) (Diff, error) { - var leftMap, rightMap map[string]interface{} - err := json.Unmarshal(left, &leftMap) - if err != nil { - return nil, err - } - - err = json.Unmarshal(right, &rightMap) - if err != nil { - return nil, err - } - return differ.CompareObjects(leftMap, rightMap), nil -} - -// CompareObjects compares two JSON object as map[string]interface{} -// and return a Diff object. -func (differ *Differ) CompareObjects(left map[string]interface{}, right map[string]interface{}) Diff { - deltas := differ.compareMaps(left, right) - return &diff{deltas: deltas} -} - -func (differ *Differ) compareMaps(left map[string]interface{}, right map[string]interface{}) (deltas []Delta) { - deltas = make([]Delta, 0) - - names := sortedKeys(left) // stabilize delta order - for _, name := range names { - if rightValue, ok := right[name]; ok { - same, delta := differ.compareValues(Name(name), left[name], rightValue) - if !same { - deltas = append(deltas, delta) - } - } else { - deltas = append(deltas, NewDeleted(Name(name), left[name])) - } - } - - names = sortedKeys(right) // stabilize delta order - for _, name := range names { - if _, ok := left[name]; !ok { - deltas = append(deltas, NewAdded(Name(name), right[name])) - } - } - - return deltas -} - -// ApplyPatch applies a Diff to an JSON object. This method is destructive. -func (differ *Differ) ApplyPatch(json map[string]interface{}, patch Diff) { - applyDeltas(patch.Deltas(), json) -} - -type maybe struct { - index int - lcsIndex int - item interface{} -} - -func (differ *Differ) compareArrays( - left []interface{}, - right []interface{}, -) (deltas []Delta) { - deltas = make([]Delta, 0) - // LCS index pairs - lcsPairs := lcs.New(left, right).IndexPairs() - - // list up items not in LCS, they are maybe deleted - maybeDeleted := list.New() // but maybe moved or modified - lcsI := 0 - for i, leftValue := range left { - if lcsI < len(lcsPairs) && lcsPairs[lcsI].Left == i { - lcsI++ - } else { - maybeDeleted.PushBack(maybe{index: i, lcsIndex: lcsI, item: leftValue}) - } - } - - // list up items not in LCS, they are maybe Added - maybeAdded := list.New() // but maybe moved or modified - lcsI = 0 - for i, rightValue := range right { - if lcsI < len(lcsPairs) && lcsPairs[lcsI].Right == i { - lcsI++ - } else { - maybeAdded.PushBack(maybe{index: i, lcsIndex: lcsI, item: rightValue}) - } - } - - // find moved items - var delNext *list.Element // for prefetch to remove item in iteration - for delCandidate := maybeDeleted.Front(); delCandidate != nil; delCandidate = delNext { - delCan := delCandidate.Value.(maybe) - delNext = delCandidate.Next() - - for addCandidate := maybeAdded.Front(); addCandidate != nil; addCandidate = addCandidate.Next() { - addCan := addCandidate.Value.(maybe) - if reflect.DeepEqual(delCan.item, addCan.item) { - deltas = append(deltas, NewMoved(Index(delCan.index), Index(addCan.index), delCan.item, nil)) - maybeAdded.Remove(addCandidate) - maybeDeleted.Remove(delCandidate) - break - } - } - } - - // find modified or add+del - prevIndexDel := 0 - prevIndexAdd := 0 - delElement := maybeDeleted.Front() - addElement := maybeAdded.Front() - for i := 0; i <= len(lcsPairs); i++ { // not "< len(lcsPairs)" - var lcsPair lcs.IndexPair - var delSize, addSize int - if i < len(lcsPairs) { - lcsPair = lcsPairs[i] - delSize = lcsPair.Left - prevIndexDel - 1 - addSize = lcsPair.Right - prevIndexAdd - 1 - prevIndexDel = lcsPair.Left - prevIndexAdd = lcsPair.Right - } - - var delSlice []maybe - if delSize > 0 { - delSlice = make([]maybe, 0, delSize) - } else { - delSlice = make([]maybe, 0, maybeDeleted.Len()) - } - for ; delElement != nil; delElement = delElement.Next() { - d := delElement.Value.(maybe) - if d.lcsIndex != i { - break - } - delSlice = append(delSlice, d) - } - - var addSlice []maybe - if addSize > 0 { - addSlice = make([]maybe, 0, addSize) - } else { - addSlice = make([]maybe, 0, maybeAdded.Len()) - } - for ; addElement != nil; addElement = addElement.Next() { - a := addElement.Value.(maybe) - if a.lcsIndex != i { - break - } - addSlice = append(addSlice, a) - } - - if len(delSlice) > 0 && len(addSlice) > 0 { - var bestDeltas []Delta - bestDeltas, delSlice, addSlice = differ.maximizeSimilarities(delSlice, addSlice) - for _, delta := range bestDeltas { - deltas = append(deltas, delta) - } - } - - for _, del := range delSlice { - deltas = append(deltas, NewDeleted(Index(del.index), del.item)) - } - for _, add := range addSlice { - deltas = append(deltas, NewAdded(Index(add.index), add.item)) - } - } - - return deltas -} - -func (differ *Differ) compareValues( - position Position, - left interface{}, - right interface{}, -) (same bool, delta Delta) { - if reflect.TypeOf(left) != reflect.TypeOf(right) { - return false, NewModified(position, left, right) - } - - switch left.(type) { - - case map[string]interface{}: - l := left.(map[string]interface{}) - childDeltas := differ.compareMaps(l, right.(map[string]interface{})) - if len(childDeltas) > 0 { - return false, NewObject(position, childDeltas) - } - - case []interface{}: - l := left.([]interface{}) - childDeltas := differ.compareArrays(l, right.([]interface{})) - - if len(childDeltas) > 0 { - return false, NewArray(position, childDeltas) - } - - default: - if !reflect.DeepEqual(left, right) { - - if reflect.ValueOf(left).Kind() == reflect.String && - reflect.ValueOf(right).Kind() == reflect.String && - differ.textDiffMinimumLength <= len(left.(string)) { - - textDiff := dmp.New() - patchs := textDiff.PatchMake(left.(string), right.(string)) - return false, NewTextDiff(position, patchs, left, right) - - } else { - return false, NewModified(position, left, right) - } - } - } - - return true, nil -} - -func applyDeltas(deltas []Delta, object interface{}) interface{} { - preDeltas := make(preDeltas, 0) - for _, delta := range deltas { - switch delta.(type) { - case PreDelta: - preDeltas = append(preDeltas, delta.(PreDelta)) - } - } - sort.Sort(preDeltas) - for _, delta := range preDeltas { - object = delta.PreApply(object) - } - - postDeltas := make(postDeltas, 0, len(deltas)-len(preDeltas)) - for _, delta := range deltas { - switch delta.(type) { - case PostDelta: - postDeltas = append(postDeltas, delta.(PostDelta)) - } - } - sort.Sort(postDeltas) - - for _, delta := range postDeltas { - object = delta.PostApply(object) - } - - return object -} - -func (differ *Differ) maximizeSimilarities(left []maybe, right []maybe) (resultDeltas []Delta, freeLeft, freeRight []maybe) { - deltaTable := make([][]Delta, len(left)) - for i := 0; i < len(left); i++ { - deltaTable[i] = make([]Delta, len(right)) - } - for i, leftValue := range left { - for j, rightValue := range right { - _, delta := differ.compareValues(Index(rightValue.index), leftValue.item, rightValue.item) - deltaTable[i][j] = delta - } - } - - sizeX := len(left) + 1 // margins for both sides - sizeY := len(right) + 1 - - // fill out with similarities - dpTable := make([][]float64, sizeX) - for i := 0; i < sizeX; i++ { - dpTable[i] = make([]float64, sizeY) - } - for x := sizeX - 2; x >= 0; x-- { - for y := sizeY - 2; y >= 0; y-- { - prevX := dpTable[x+1][y] - prevY := dpTable[x][y+1] - score := deltaTable[x][y].Similarity() + dpTable[x+1][y+1] - - dpTable[x][y] = max(prevX, prevY, score) - } - } - - minLength := len(left) - if minLength > len(right) { - minLength = len(right) - } - maxInvalidLength := minLength - 1 - - freeLeft = make([]maybe, 0, len(left)-minLength) - freeRight = make([]maybe, 0, len(right)-minLength) - - resultDeltas = make([]Delta, 0, minLength) - var x, y int - for x, y = 0, 0; x <= sizeX-2 && y <= sizeY-2; { - current := dpTable[x][y] - nextX := dpTable[x+1][y] - nextY := dpTable[x][y+1] - - xValidLength := len(left) - maxInvalidLength + y - yValidLength := len(right) - maxInvalidLength + x - - if x+1 < xValidLength && current == nextX { - freeLeft = append(freeLeft, left[x]) - x++ - } else if y+1 < yValidLength && current == nextY { - freeRight = append(freeRight, right[y]) - y++ - } else { - resultDeltas = append(resultDeltas, deltaTable[x][y]) - x++ - y++ - } - } - for ; x < sizeX-1; x++ { - freeLeft = append(freeLeft, left[x-1]) - } - for ; x < sizeY-1; y++ { - freeLeft = append(freeRight, left[y-1]) - } - - return resultDeltas, freeLeft, freeRight -} - -func deltasSimilarity(deltas []Delta) (similarity float64) { - for _, delta := range deltas { - similarity += delta.Similarity() - } - similarity = similarity / float64(len(deltas)) - return -} - -func stringSimilarity(left, right string) (similarity float64) { - matchingLength := float64( - lcs.New( - stringToInterfaceSlice(left), - stringToInterfaceSlice(right), - ).Length(), - ) - similarity = - (matchingLength / float64(len(left))) * (matchingLength / float64(len(right))) - return -} - -func stringToInterfaceSlice(str string) []interface{} { - s := make([]interface{}, len(str)) - for i, v := range str { - s[i] = v - } - return s -} - -func sortedKeys(m map[string]interface{}) (keys []string) { - keys = make([]string, 0, len(m)) - for key, _ := range m { - keys = append(keys, key) - } - sort.Strings(keys) - return -} - -func max(first float64, rest ...float64) (max float64) { - max = first - for _, value := range rest { - if max < value { - max = value - } - } - return max -} diff --git a/util/diff/unmarshal.go b/util/diff/unmarshal.go deleted file mode 100644 index 4df826f7..00000000 --- a/util/diff/unmarshal.go +++ /dev/null @@ -1,131 +0,0 @@ -package diff - -import ( - "encoding/json" - "errors" - dmp "github.com/sergi/go-diff/diffmatchpatch" - "io" - "strconv" -) - -type Unmarshaller struct { -} - -func NewUnmarshaller() *Unmarshaller { - return &Unmarshaller{} -} - -func (um *Unmarshaller) UnmarshalBytes(diffBytes []byte) (Diff, error) { - var diffObj map[string]interface{} - json.Unmarshal(diffBytes, &diffObj) - return um.UnmarshalObject(diffObj) -} - -func (um *Unmarshaller) UnmarshalString(diffString string) (Diff, error) { - return um.UnmarshalBytes([]byte(diffString)) -} - -func (um *Unmarshaller) UnmarshalReader(diffReader io.Reader) (Diff, error) { - var diffBytes []byte - io.ReadFull(diffReader, diffBytes) - return um.UnmarshalBytes(diffBytes) -} - -func (um *Unmarshaller) UnmarshalObject(diffObj map[string]interface{}) (Diff, error) { - result, err := process(Name(""), diffObj) - if err != nil { - return nil, err - } - return &diff{deltas: result.(*Object).Deltas}, nil -} - -func process(position Position, object interface{}) (Delta, error) { - var delta Delta - switch object.(type) { - case map[string]interface{}: - o := object.(map[string]interface{}) - if isArray, typed := o["_t"]; typed && isArray == "a" { - deltas := make([]Delta, 0, len(o)) - for name, value := range o { - if name == "_t" { - continue - } - - normalizedName := name - if normalizedName[0] == '_' { - normalizedName = name[1:] - } - index, err := strconv.Atoi(normalizedName) - if err != nil { - return nil, err - } - - childDelta, err := process(Index(index), value) - if err != nil { - return nil, err - } - - deltas = append(deltas, childDelta) - } - - for _, d := range deltas { - switch d.(type) { - case *Moved: - moved := d.(*Moved) - - var dd interface{} - var i int - for i, dd = range deltas { - switch dd.(type) { - case *Moved: - case PostDelta: - pd := dd.(PostDelta) - if moved.PostPosition() == pd.PostPosition() { - moved.Delta = pd - deltas = append(deltas[:i], deltas[i+1:]...) - } - } - } - } - } - - delta = NewArray(position, deltas) - } else { - deltas := make([]Delta, 0, len(o)) - for name, value := range o { - childDelta, err := process(Name(name), value) - if err != nil { - return nil, err - } - deltas = append(deltas, childDelta) - } - delta = NewObject(position, deltas) - } - case []interface{}: - o := object.([]interface{}) - switch len(o) { - case 1: - delta = NewAdded(position, o[0]) - case 2: - delta = NewModified(position, o[0], o[1]) - case 3: - switch o[2] { - case float64(0): - delta = NewDeleted(position, o[0]) - case float64(2): - dmp := dmp.New() - patches, err := dmp.PatchFromText(o[0].(string)) - if err != nil { - return nil, err - } - delta = NewTextDiff(position, patches, nil, nil) - case float64(3): - delta = NewMoved(position, Index(int(o[1].(float64))), nil, nil) - default: - return nil, errors.New("Unknown delta type") - } - } - } - - return delta, nil -} diff --git a/util/form/ascii.go b/util/form/ascii.go deleted file mode 100755 index 0247df85..00000000 --- a/util/form/ascii.go +++ /dev/null @@ -1,297 +0,0 @@ -package form - -import ( - "errors" - "fmt" - "sort" - - diff "github.com/abcum/surreal/util/diff" -) - -func NewAsciiFormatter(left map[string]interface{}) *AsciiFormatter { - return &AsciiFormatter{ - left: left, - ShowArrayIndex: false, - } -} - -type AsciiFormatter struct { - left map[string]interface{} - ShowArrayIndex bool - buffer string - path []string - size []int - inArray []bool -} - -func (f *AsciiFormatter) Format(diff diff.Diff) (result string, err error) { - f.buffer = "" - f.path = []string{} - f.size = []int{} - f.inArray = []bool{} - - f.printIndent(AsciiSame) - f.println("{") - f.push("ROOT", len(f.left), false) - f.processObject(f.left, diff.Deltas()) - f.pop() - f.printIndent(AsciiSame) - f.println("}") - - return f.buffer, nil -} - -func (f *AsciiFormatter) processArray(array []interface{}, deltas []diff.Delta) error { - patchedIndex := 0 - for index, value := range array { - f.processItem(value, deltas, diff.Index(index)) - patchedIndex++ - } - - // additional Added - for _, delta := range deltas { - switch delta.(type) { - case *diff.Added: - d := delta.(*diff.Added) - // skip items already processed - if int(d.Position.(diff.Index)) < len(array) { - continue - } - f.printRecursive(d.Position.String(), d.Value, AsciiAdded) - } - } - - return nil -} - -func (f *AsciiFormatter) processObject(object map[string]interface{}, deltas []diff.Delta) error { - names := sortedKeys(object) - for _, name := range names { - value := object[name] - f.processItem(value, deltas, diff.Name(name)) - } - - // Added - for _, delta := range deltas { - switch delta.(type) { - case *diff.Added: - d := delta.(*diff.Added) - f.printRecursive(d.Position.String(), d.Value, AsciiAdded) - } - } - - return nil -} - -func (f *AsciiFormatter) processItem(value interface{}, deltas []diff.Delta, position diff.Position) error { - matchedDeltas := f.searchDeltas(deltas, position) - positionStr := position.String() - if len(matchedDeltas) > 0 { - for _, matchedDelta := range matchedDeltas { - - switch matchedDelta.(type) { - case *diff.Object: - d := matchedDelta.(*diff.Object) - switch value.(type) { - case map[string]interface{}: - //ok - default: - return errors.New("Type mismatch") - } - o := value.(map[string]interface{}) - - f.printKeyWithIndent(positionStr, AsciiSame) - f.println("{") - f.push(positionStr, len(o), false) - f.processObject(o, d.Deltas) - f.pop() - f.printIndent(AsciiSame) - f.print("}") - f.printComma() - - case *diff.Array: - d := matchedDelta.(*diff.Array) - switch value.(type) { - case []interface{}: - //ok - default: - return errors.New("Type mismatch") - } - a := value.([]interface{}) - - f.printKeyWithIndent(positionStr, AsciiSame) - f.println("[") - f.push(positionStr, len(a), true) - f.processArray(a, d.Deltas) - f.pop() - f.printIndent(AsciiSame) - f.print("]") - f.printComma() - - case *diff.Added: - d := matchedDelta.(*diff.Added) - f.printRecursive(positionStr, d.Value, AsciiAdded) - f.size[len(f.size)-1]++ - - case *diff.Modified: - d := matchedDelta.(*diff.Modified) - savedSize := f.size[len(f.size)-1] - f.printRecursive(positionStr, d.OldValue, AsciiDeleted) - f.size[len(f.size)-1] = savedSize - f.printRecursive(positionStr, d.NewValue, AsciiAdded) - - case *diff.TextDiff: - savedSize := f.size[len(f.size)-1] - d := matchedDelta.(*diff.TextDiff) - f.printRecursive(positionStr, d.OldValue, AsciiDeleted) - f.size[len(f.size)-1] = savedSize - f.printRecursive(positionStr, d.NewValue, AsciiAdded) - - case *diff.Deleted: - d := matchedDelta.(*diff.Deleted) - f.printRecursive(positionStr, d.Value, AsciiDeleted) - - default: - return errors.New("Unknown Delta type detected") - } - - } - } else { - f.printRecursive(positionStr, value, AsciiSame) - } - - return nil -} - -func (f *AsciiFormatter) searchDeltas(deltas []diff.Delta, postion diff.Position) (results []diff.Delta) { - results = make([]diff.Delta, 0) - for _, delta := range deltas { - switch delta.(type) { - case diff.PostDelta: - if delta.(diff.PostDelta).PostPosition() == postion { - results = append(results, delta) - } - case diff.PreDelta: - if delta.(diff.PreDelta).PrePosition() == postion { - results = append(results, delta) - } - default: - panic("heh") - } - } - return -} - -const ( - AsciiSame = " " - AsciiAdded = "+" - AsciiDeleted = "-" -) - -func (f *AsciiFormatter) push(name string, size int, array bool) { - f.path = append(f.path, name) - f.size = append(f.size, size) - f.inArray = append(f.inArray, array) -} - -func (f *AsciiFormatter) pop() { - f.path = f.path[0 : len(f.path)-1] - f.size = f.size[0 : len(f.size)-1] - f.inArray = f.inArray[0 : len(f.inArray)-1] -} - -func (f *AsciiFormatter) printIndent(marker string) { - f.print(marker) - for n := 0; n < len(f.path); n++ { - f.print(" ") - } -} - -func (f *AsciiFormatter) printKeyWithIndent(name string, marker string) { - f.printIndent(marker) - if !f.inArray[len(f.inArray)-1] { - f.printf(`"%s": `, name) - } else if f.ShowArrayIndex { - f.printf(`%s: `, name) - } -} - -func (f *AsciiFormatter) printComma() { - f.size[len(f.size)-1]-- - if f.size[len(f.size)-1] > 0 { - f.println(",") - } else { - f.println() - } -} - -func (f *AsciiFormatter) printValue(value interface{}) { - switch value.(type) { - case string: - f.buffer += fmt.Sprintf(`"%s"`, value) - default: - f.buffer += fmt.Sprintf(`%#v`, value) - } -} - -func (f *AsciiFormatter) print(a ...interface{}) { - f.buffer += fmt.Sprint(a...) -} - -func (f *AsciiFormatter) printf(format string, a ...interface{}) { - f.buffer += fmt.Sprintf(format, a...) -} - -func (f *AsciiFormatter) println(a ...interface{}) { - f.buffer += fmt.Sprintln(a...) -} - -func (f *AsciiFormatter) printRecursive(name string, value interface{}, marker string) { - switch value.(type) { - case map[string]interface{}: - f.printKeyWithIndent(name, marker) - f.println("{") - - m := value.(map[string]interface{}) - size := len(m) - f.push(name, size, false) - - keys := sortedKeys(m) - for _, key := range keys { - f.printRecursive(key, m[key], marker) - } - f.pop() - - f.printIndent(marker) - f.print("}") - f.printComma() - case []interface{}: - f.printKeyWithIndent(name, marker) - f.println("[") - - s := value.([]interface{}) - size := len(s) - f.push("", size, true) - for _, item := range s { - f.printRecursive("", item, marker) - } - f.pop() - - f.printIndent(marker) - f.print("]") - f.printComma() - default: - f.printKeyWithIndent(name, marker) - f.printValue(value) - f.printComma() - } -} - -func sortedKeys(m map[string]interface{}) (keys []string) { - keys = make([]string, 0, len(m)) - for key, _ := range m { - keys = append(keys, key) - } - sort.Strings(keys) - return -} diff --git a/util/form/delta.go b/util/form/delta.go deleted file mode 100755 index 53fc65fa..00000000 --- a/util/form/delta.go +++ /dev/null @@ -1,124 +0,0 @@ -package form - -import ( - "encoding/json" - "errors" - "fmt" - - diff "github.com/abcum/surreal/util/diff" -) - -const ( - DeltaDelete = 0 - DeltaTextDiff = 2 - DeltaMove = 3 -) - -func NewDeltaFormatter() *DeltaFormatter { - return &DeltaFormatter{ - PrintIndent: true, - } -} - -type DeltaFormatter struct { - PrintIndent bool -} - -func (f *DeltaFormatter) Format(diff diff.Diff) (result string, err error) { - jsonObject, err := f.formatObject(diff.Deltas()) - if err != nil { - return "", err - } - var resultBytes []byte - if f.PrintIndent { - resultBytes, err = json.MarshalIndent(jsonObject, "", " ") - } else { - resultBytes, err = json.Marshal(jsonObject) - } - if err != nil { - return "", err - } - - return string(resultBytes), nil -} - -func (f *DeltaFormatter) FormatAsJson(diff diff.Diff) (json map[string]interface{}, err error) { - return f.formatObject(diff.Deltas()) -} - -func (f *DeltaFormatter) formatObject(deltas []diff.Delta) (deltaJson map[string]interface{}, err error) { - deltaJson = map[string]interface{}{} - for _, delta := range deltas { - switch delta.(type) { - case *diff.Object: - d := delta.(*diff.Object) - deltaJson[d.Position.String()], err = f.formatObject(d.Deltas) - if err != nil { - return nil, err - } - case *diff.Array: - d := delta.(*diff.Array) - deltaJson[d.Position.String()], err = f.formatArray(d.Deltas) - if err != nil { - return nil, err - } - case *diff.Added: - d := delta.(*diff.Added) - deltaJson[d.PostPosition().String()] = []interface{}{d.Value} - case *diff.Modified: - d := delta.(*diff.Modified) - deltaJson[d.PostPosition().String()] = []interface{}{d.OldValue, d.NewValue} - case *diff.TextDiff: - d := delta.(*diff.TextDiff) - deltaJson[d.PostPosition().String()] = []interface{}{d.DiffString(), 0, DeltaTextDiff} - case *diff.Deleted: - d := delta.(*diff.Deleted) - deltaJson[d.PrePosition().String()] = []interface{}{d.Value, 0, DeltaDelete} - case *diff.Moved: - return nil, errors.New("Delta type 'Move' is not supported in objects") - default: - return nil, errors.New(fmt.Sprintf("Unknown Delta type detected: %#v", delta)) - } - } - return -} - -func (f *DeltaFormatter) formatArray(deltas []diff.Delta) (deltaJson map[string]interface{}, err error) { - deltaJson = map[string]interface{}{ - "_t": "a", - } - for _, delta := range deltas { - switch delta.(type) { - case *diff.Object: - d := delta.(*diff.Object) - deltaJson[d.Position.String()], err = f.formatObject(d.Deltas) - if err != nil { - return nil, err - } - case *diff.Array: - d := delta.(*diff.Array) - deltaJson[d.Position.String()], err = f.formatArray(d.Deltas) - if err != nil { - return nil, err - } - case *diff.Added: - d := delta.(*diff.Added) - deltaJson[d.PostPosition().String()] = []interface{}{d.Value} - case *diff.Modified: - d := delta.(*diff.Modified) - deltaJson[d.PostPosition().String()] = []interface{}{d.OldValue, d.NewValue} - case *diff.TextDiff: - d := delta.(*diff.TextDiff) - deltaJson[d.PostPosition().String()] = []interface{}{d.DiffString(), 0, DeltaTextDiff} - case *diff.Deleted: - d := delta.(*diff.Deleted) - deltaJson["_"+d.PrePosition().String()] = []interface{}{d.Value, 0, DeltaDelete} - case *diff.Moved: - d := delta.(*diff.Moved) - deltaJson["_"+d.PrePosition().String()] = []interface{}{"", d.PostPosition(), DeltaMove} - default: - return nil, errors.New(fmt.Sprintf("Unknown Delta type detected: %#v", delta)) - } - } - return -}