// 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" "strconv" "strings" "github.com/abcum/surreal/sql" "github.com/abcum/surreal/util/data" "github.com/sergi/go-diff/diffmatchpatch" ) type operations struct { ops []*operation } type operation struct { op string from string path string value interface{} before interface{} } func Diff(old, now map[string]interface{}) []interface{} { out := &operations{} out.diff(old, now, "") return out.diffs() } func Patch(old map[string]interface{}, ops []interface{}) map[string]interface{} { out := &operations{} out.load(ops) return out.patch(old) } func (o *operations) load(ops []interface{}) { for _, v := range ops { if obj, ok := v.(map[string]interface{}); ok { op := &operation{} op.value = obj["value"] if str, ok := obj["op"].(string); ok { op.op = str } if str, ok := obj["from"].(string); ok { op.from = str } if str, ok := obj["path"].(string); ok { op.path = str } o.ops = append(o.ops, op) } } } func (o *operations) diffs() (ops []interface{}) { ops = make([]interface{}, len(o.ops)) sort.Slice(o.ops, func(i, j int) bool { return o.ops[i].path < o.ops[j].path }) for k, v := range o.ops { op := make(map[string]interface{}) if len(v.op) > 0 { op["op"] = v.op } if len(v.from) > 0 { op["from"] = v.from } if len(v.path) > 0 { op["path"] = v.path } if v.value != nil { op["value"] = v.value } ops[k] = op } return } func isIn(a int, list []int) bool { for _, b := range list { if b == a { return true } } return false } func route(path string, part string) string { if len(path) == 0 { return "/" + part } else { if part[0] == '/' { return path + part } else { return path + "/" + 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 (o *operations) patch(old map[string]interface{}) (now map[string]interface{}) { obj := data.Consume(old) for _, v := range o.ops { path := strings.Split(v.path, "/") prev := path[:len(path)-1] switch v.op { case "add": switch obj.Get(prev...).Data().(type) { case []interface{}: obj.Append(v.value, prev...) default: obj.Set(v.value, path...) } case "remove": obj.Del(path...) case "replace": obj.Set(v.value, path...) case "change": switch val := obj.Get(path...).Data().(type) { case fmt.Stringer: txt := val.String() dmp := diffmatchpatch.New() if pch, err := dmp.PatchFromText(v.value.(string)); err == nil { txt, _ := dmp.PatchApply(pch, txt) obj.Set(txt, path...) } case string: dmp := diffmatchpatch.New() if pch, err := dmp.PatchFromText(v.value.(string)); err == nil { txt, _ := dmp.PatchApply(pch, val) obj.Set(txt, path...) } } } } return old } func (o *operations) text(old, now string, path string) { dmp := diffmatchpatch.New() pch := dmp.PatchMake(old, now) txt := dmp.PatchToText(pch) 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 *sql.Thing: nv := now.(*sql.Thing) if ov.TB != nv.TB || ov.ID != nv.ID { 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, strconv.Itoa(i))) } for j := i; j < len(now); j++ { if j >= len(old) || !reflect.DeepEqual(now[j], old[j]) { o.op("add", "", route(path, strconv.Itoa(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, strconv.Itoa(j)), old[j], nil) } } }