-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
632 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package merge | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/databricks/cli/libs/dyn" | ||
) | ||
|
||
// OverrideVisitor is visiting the changes during the override process | ||
// and allows to control what changes are allowed, or update the effective | ||
// value. | ||
// | ||
// For instance, it can disallow changes outside the specific path(s), or update | ||
// the location of the effective value. | ||
// | ||
// 'VisitDelete' is called when a value is removed from mapping or sequence | ||
// 'VisitInsert' is called when a new value is added to mapping or sequence | ||
// 'VisitUpdate' is called when a leaf value is updated | ||
type OverrideVisitor struct { | ||
VisitDelete func(valuePath dyn.Path, left dyn.Value) error | ||
VisitInsert func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error) | ||
VisitUpdate func(valuePath dyn.Path, left dyn.Value, right dyn.Value) (dyn.Value, error) | ||
} | ||
|
||
// Override overrides value 'leftRoot' with 'rightRoot', keeping 'location' if values | ||
// haven't changed. Preserving 'location' is important to preserve the original source of the value | ||
// for error reporting. | ||
func Override(leftRoot dyn.Value, rightRoot dyn.Value, visitor OverrideVisitor) (dyn.Value, error) { | ||
return override(dyn.EmptyPath, leftRoot, rightRoot, visitor) | ||
} | ||
|
||
func override(basePath dyn.Path, left dyn.Value, right dyn.Value, visitor OverrideVisitor) (dyn.Value, error) { | ||
if left == dyn.NilValue && right == dyn.NilValue { | ||
return dyn.NilValue, nil | ||
} | ||
|
||
if left.Kind() != right.Kind() { | ||
return visitor.VisitUpdate(basePath, left, right) | ||
} | ||
|
||
// NB: we only call 'VisitUpdate' on leaf values, and for sequences and mappings | ||
// we don't know if value was updated or not | ||
|
||
switch left.Kind() { | ||
case dyn.KindMap: | ||
merged, err := overrideMapping(basePath, left.MustMap(), right.MustMap(), visitor) | ||
|
||
if err != nil { | ||
return dyn.InvalidValue, err | ||
} | ||
|
||
return dyn.NewValue(merged, left.Location()), nil | ||
|
||
case dyn.KindSequence: | ||
// some sequences are keyed, and we can detect which elements are added/removed/updated, | ||
// but we don't have this information | ||
merged, err := overrideSequence(basePath, left.MustSequence(), right.MustSequence(), visitor) | ||
|
||
if err != nil { | ||
return dyn.InvalidValue, err | ||
} | ||
|
||
return dyn.NewValue(merged, left.Location()), nil | ||
|
||
case dyn.KindString: | ||
if left.MustString() == right.MustString() { | ||
return left, nil | ||
} else { | ||
return visitor.VisitUpdate(basePath, left, right) | ||
} | ||
|
||
case dyn.KindFloat: | ||
// TODO consider comparison with epsilon if normalization doesn't help, where do we use floats? | ||
|
||
if left.MustFloat() == right.MustFloat() { | ||
return left, nil | ||
} else { | ||
return visitor.VisitUpdate(basePath, left, right) | ||
} | ||
|
||
case dyn.KindBool: | ||
if left.MustBool() == right.MustBool() { | ||
return left, nil | ||
} else { | ||
return visitor.VisitUpdate(basePath, left, right) | ||
} | ||
|
||
case dyn.KindTime: | ||
if left.MustTime() == right.MustTime() { | ||
return left, nil | ||
} else { | ||
return visitor.VisitUpdate(basePath, left, right) | ||
} | ||
|
||
case dyn.KindInt: | ||
if left.MustInt() == right.MustInt() { | ||
return left, nil | ||
} else { | ||
return visitor.VisitUpdate(basePath, left, right) | ||
} | ||
} | ||
|
||
return dyn.InvalidValue, fmt.Errorf("unexpected kind %s", left.Kind()) | ||
} | ||
|
||
func overrideMapping(basePath dyn.Path, leftMapping dyn.Mapping, rightMapping dyn.Mapping, visitor OverrideVisitor) (dyn.Mapping, error) { | ||
out := dyn.NewMapping() | ||
|
||
for _, leftPair := range leftMapping.Pairs() { | ||
// detect if key was removed | ||
if _, ok := rightMapping.GetPair(leftPair.Key); !ok { | ||
path := basePath.Append(dyn.Key(leftPair.Key.MustString())) | ||
|
||
err := visitor.VisitDelete(path, leftPair.Value) | ||
|
||
if err != nil { | ||
return dyn.NewMapping(), err | ||
} | ||
} | ||
} | ||
|
||
// iterating only right mapping will remove keys not present anymore | ||
// and insert new keys | ||
|
||
for _, rightPair := range rightMapping.Pairs() { | ||
if leftPair, ok := leftMapping.GetPair(rightPair.Key); ok { | ||
path := basePath.Append(dyn.Key(rightPair.Key.MustString())) | ||
newValue, err := override(path, leftPair.Value, rightPair.Value, visitor) | ||
|
||
if err != nil { | ||
return dyn.NewMapping(), err | ||
} | ||
|
||
// key was there before, so keep its location | ||
err = out.Set(leftPair.Key, newValue) | ||
|
||
if err != nil { | ||
return dyn.NewMapping(), err | ||
} | ||
} else { | ||
path := basePath.Append(dyn.Key(rightPair.Key.MustString())) | ||
|
||
newValue, err := visitor.VisitInsert(path, rightPair.Value) | ||
|
||
if err != nil { | ||
return dyn.NewMapping(), err | ||
} | ||
|
||
err = out.Set(rightPair.Key, newValue) | ||
|
||
if err != nil { | ||
return dyn.NewMapping(), err | ||
} | ||
} | ||
} | ||
|
||
return out, nil | ||
} | ||
|
||
func overrideSequence(basePath dyn.Path, left []dyn.Value, right []dyn.Value, visitor OverrideVisitor) ([]dyn.Value, error) { | ||
minLen := min(len(left), len(right)) | ||
var values []dyn.Value | ||
|
||
for i := 0; i < minLen; i++ { | ||
path := basePath.Append(dyn.Index(i)) | ||
merged, err := override(path, left[i], right[i], visitor) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
values = append(values, merged) | ||
} | ||
|
||
if len(right) > len(left) { | ||
for i := minLen; i < len(right); i++ { | ||
path := basePath.Append(dyn.Index(i)) | ||
newValue, err := visitor.VisitInsert(path, right[i]) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
values = append(values, newValue) | ||
} | ||
} else if len(left) > len(right) { | ||
for i := minLen; i < len(left); i++ { | ||
path := basePath.Append(dyn.Index(i)) | ||
err := visitor.VisitDelete(path, left[i]) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
return values, nil | ||
} |
Oops, something went wrong.