Remove old diffing utility packages
This commit is contained in:
parent
244645ac0c
commit
a460ee4a06
5 changed files with 0 additions and 1441 deletions
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in a new issue