// 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. package diff import ( "fmt" "reflect" "sort" "strings" "github.com/fatih/structs" "github.com/sergi/go-diff/diffmatchpatch" ) type Operation struct { Op string `cork:"op,omietmpty" json:"op,omietmpty" structs:"op,omitempty"` From string `cork:"from,omitempty" json:"from,omitempty" structs:"from,omitempty"` Path string `cork:"path,omitempty" json:"path,omitempty" structs:"path,omitempty"` Value interface{} `cork:"value,omitempty" json:"value,omitempty" structs:"value,omitempty"` Before interface{} `cork:"-" json:"-" structs:"-"` } type Operations struct { Ops []*Operation } func Diff(old, now map[string]interface{}) (ops *Operations) { ops = &Operations{} ops.diff(old, now, "") return } func (o *Operations) Patch(old map[string]interface{}) (now map[string]interface{}, err error) { return nil, nil } func (o *Operations) Rebase(other *Operations) (ops *Operations, err error) { return nil, nil } func (o *Operations) Out() (ops []map[string]interface{}) { for _, v := range o.Ops { ops = append(ops, structs.Map(v)) } return } func route(path string, part interface{}) string { if path == "" { return fmt.Sprintf("/%v", part) } else { if strings.HasSuffix(path, "/") { return path + fmt.Sprintf("%v", part) } else { return path + fmt.Sprintf("/%v", part) } } } func (o *Operations) op(op, from, path string, before, after interface{}) { o.Ops = append(o.Ops, &Operation{ Op: op, From: from, Path: path, Value: after, Before: before, }) } func (o *Operations) diff(old, now map[string]interface{}, path string) { for key, after := range now { p := route(path, key) // Check if the value existed before, ok := old[key] // Value did not previously exist if !ok { o.op("add", "", p, nil, after) continue } // Data type is now completely different if reflect.TypeOf(after) != reflect.TypeOf(before) { o.op("replace", "", p, before, after) continue } // Check whether the values have changed o.vals(before, after, p) } for key, before := range old { p := route(path, key) // Check if the value exists after, ok := now[key] // Value now no longer exists if !ok { o.op("remove", "", p, before, after) continue } } var used []int for i := len(o.Ops) - 1; i >= 0; i-- { if iv := o.Ops[i]; !isIn(i, used) && iv.Op == "add" { for j := len(o.Ops) - 1; j >= 0; j-- { if jv := o.Ops[j]; !isIn(j, used) && jv.Op == "remove" { if reflect.DeepEqual(iv.Value, jv.Before) { used = append(used, []int{i, j}...) o.op("move", jv.Path, iv.Path, nil, nil) } } } } } sort.Sort(sort.Reverse(sort.IntSlice(used))) for _, i := range used { o.Ops = append(o.Ops[:i], o.Ops[i+1:]...) } } func isIn(a int, list []int) bool { for _, b := range list { if b == a { return true } } return false } func (o *Operations) text(old, now string, path string) { dmp := diffmatchpatch.New() dif := dmp.DiffMain(old, now, false) txt := dmp.DiffToDelta(dif) o.op("change", "", path, old, txt) } func (o *Operations) vals(old, now interface{}, path string) { if reflect.TypeOf(old) != reflect.TypeOf(now) { o.op("replace", "", path, old, now) return } switch ov := old.(type) { default: if !reflect.DeepEqual(old, now) { o.op("replace", "", path, old, now) } case bool: if ov != now.(bool) { o.op("replace", "", path, old, now) } case int64: if ov != now.(int64) { o.op("replace", "", path, old, now) } case float64: if ov != now.(float64) { o.op("replace", "", path, old, now) } case string: if ov != now.(string) { o.text(ov, now.(string), path) } case nil: switch now.(type) { case nil: default: o.op("replace", "", path, old, now) } case map[string]interface{}: o.diff(ov, now.(map[string]interface{}), path) case []interface{}: o.arrs(ov, now.([]interface{}), path) } } func (o *Operations) arrs(old, now []interface{}, path string) { var i int for i = 0; i < len(old) && i < len(now); i++ { o.vals(old[i], now[i], route(path, i)) } for j := i; j < len(now); j++ { if j >= len(old) || !reflect.DeepEqual(now[j], old[j]) { o.op("add", "", route(path, j), nil, now[j]) } } for j := i; j < len(old); j++ { if j >= len(now) || !reflect.DeepEqual(old[j], now[j]) { o.op("remove", "", route(path, j), old[j], nil) } } }