Files
dolt/go/diff/print_diff.go
Aaron Boodman 06cebbe346 Introduce @target annotation for paths (#3352)
Introduce @target annotation for paths

Fixes #2172
2017-04-13 14:56:31 -07:00

169 lines
4.1 KiB
Go

// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package diff
import (
"io"
"github.com/attic-labs/noms/go/types"
"github.com/attic-labs/noms/go/util/writers"
"github.com/dustin/go-humanize"
)
type prefixOp string
const (
ADD = "+ "
DEL = "- "
)
type (
printFunc func(w io.Writer, op prefixOp, key, val types.Value) error
)
// PrintDiff writes a textual reprensentation of the diff from |v1| to |v2|
// to |w|. If |leftRight| is true then the left-right diff is used for ordered
// sequences - see Diff vs DiffLeftRight in Set and Map.
func PrintDiff(w io.Writer, v1, v2 types.Value, leftRight bool) (err error) {
// In the case where the diff involves two simple values, just print out the
// diff and return. This is needed because the code below assumes that the
// values being compared have a parent.
if !shouldDescend(v1, v2) {
line(w, DEL, nil, v1)
return line(w, ADD, nil, v2)
}
dChan := make(chan Difference, 16)
stopChan := make(chan struct{})
stopDiff := func() {
close(stopChan)
for range dChan {
}
}
// From here on, we can assume that every Difference will have at least one
// element in the Path
go func() {
Diff(v1, v2, dChan, stopChan, leftRight)
close(dChan)
}()
var lastParentPath types.Path
wroteHdr := false
firstTime := true
for d := range dChan {
parentPath := d.Path[:len(d.Path)-1]
parentPathChanged := !parentPath.Equals(lastParentPath)
lastParentPath = parentPath
if parentPathChanged && wroteHdr {
err = writeFooter(w, &wroteHdr)
}
if parentPathChanged || firstTime {
firstTime = false
err = writeHeader(w, parentPath, &wroteHdr)
}
lastPart := d.Path[len(d.Path)-1]
parentEl := parentPath.Resolve(v1, nil)
var key types.Value
var pfunc printFunc = line
switch parent := parentEl.(type) {
case types.Map:
if indexPath, ok := lastPart.(types.IndexPath); ok {
key = indexPath.Index
} else if hip, ok := lastPart.(types.HashIndexPath); ok {
// In this case, the map has a non-primitive key so the value
// is a ref to the key. We need the actual key, not a ref to it.
hip1 := hip
hip1.IntoKey = true
key = hip1.Resolve(parent, nil)
} else {
panic("unexpected Path type")
}
case types.Set:
// default values are ok
case types.Struct:
key = types.String(lastPart.(types.FieldPath).Name)
pfunc = field
case types.List:
// default values are ok
}
if d.OldValue != nil {
err = pfunc(w, DEL, key, d.OldValue)
}
if d.NewValue != nil {
err = pfunc(w, ADD, key, d.NewValue)
}
if err != nil {
stopDiff()
break
}
}
err = writeFooter(w, &wroteHdr)
return
}
func writeHeader(w io.Writer, p types.Path, wroteHdr *bool) error {
if *wroteHdr {
return nil
}
*wroteHdr = true
hdr := "(root)"
if len(p) > 0 {
hdr = p.String()
}
return write(w, []byte(hdr+" {\n"))
}
func writeFooter(w io.Writer, wroteHdr *bool) error {
if !*wroteHdr {
return nil
}
*wroteHdr = false
return write(w, []byte(" }\n"))
}
func line(w io.Writer, op prefixOp, key, val types.Value) error {
genPrefix := func(w *writers.PrefixWriter) []byte {
return []byte(op)
}
pw := &writers.PrefixWriter{Dest: w, PrefixFunc: genPrefix, NeedsPrefix: true}
if key != nil {
writeEncodedValue(pw, key)
write(w, []byte(": "))
}
writeEncodedValue(pw, val)
return write(w, []byte("\n"))
}
func field(w io.Writer, op prefixOp, name, val types.Value) error {
genPrefix := func(w *writers.PrefixWriter) []byte {
return []byte(op)
}
pw := &writers.PrefixWriter{Dest: w, PrefixFunc: genPrefix, NeedsPrefix: true}
write(pw, []byte(name.(types.String)))
write(w, []byte(": "))
writeEncodedValue(pw, val)
return write(w, []byte("\n"))
}
func writeEncodedValue(w io.Writer, v types.Value) error {
if v.Kind() != types.BlobKind {
return types.WriteEncodedValue(w, v)
}
write(w, []byte("Blob ("))
write(w, []byte(humanize.Bytes(v.(types.Blob).Len())))
return write(w, []byte(")"))
}
func write(w io.Writer, b []byte) error {
_, err := w.Write(b)
return err
}