package cmp
import (
)
const numContextRecords = 2
type diffMode byte
const (
diffUnknown diffMode = 0
diffIdentical diffMode = ' '
diffRemoved diffMode = '-'
diffInserted diffMode = '+'
)
type typeMode int
const (
emitType typeMode = iota
elideType
autoType
)
type formatOptions struct {
DiffMode diffMode
TypeMode typeMode
formatValueOptions
}
func ( formatOptions) ( diffMode) formatOptions {
.DiffMode =
return
}
func ( formatOptions) ( typeMode) formatOptions {
.TypeMode =
return
}
func ( formatOptions) ( int) formatOptions {
.VerbosityLevel =
.LimitVerbosity = true
return
}
func ( formatOptions) () uint {
switch {
case .VerbosityLevel < 0:
return 0
case .VerbosityLevel > 16:
return 16
default:
return uint(.VerbosityLevel)
}
}
const maxVerbosityPreset = 6
func ( formatOptions, int) formatOptions {
.VerbosityLevel = int(.verbosity()) + 2*
if > 0 {
.AvoidStringer = true
}
if >= maxVerbosityPreset {
.PrintAddresses = true
.QualifiedNames = true
}
return
}
func ( formatOptions) ( *valueNode, *pointerReferences) ( textNode) {
if .DiffMode == diffIdentical {
= .WithVerbosity(1)
} else if .verbosity() < 3 {
= .WithVerbosity(3)
}
if .CanFormatDiffSlice() {
return .FormatDiffSlice()
}
var reflect.Kind
if .parent != nil && .parent.TransformerName == "" {
= .parent.Type.Kind()
}
:= .Type.Kind() == reflect.Slice && .Type.Elem() == byteType
:= && .NumDiff+.NumIgnored+.NumTransformed == 0
if .MaxDepth == 0 || {
switch .DiffMode {
case diffUnknown, diffIdentical:
if .NumDiff == 0 {
:= .FormatValue(.ValueX, , )
:= .FormatValue(.ValueY, , )
if .NumIgnored > 0 && .NumSame == 0 {
return textEllipsis
} else if .Len() < .Len() {
return
} else {
return
}
}
assert(.DiffMode == diffUnknown)
var textList
:= .WithTypeMode(elideType).FormatValue(.ValueX, , )
:= .WithTypeMode(elideType).FormatValue(.ValueY, , )
for := 0; <= maxVerbosityPreset && != nil && != nil && .Equal(); ++ {
:= verbosityPreset(, ).WithTypeMode(elideType)
= .FormatValue(.ValueX, , )
= .FormatValue(.ValueY, , )
}
if != nil {
= append(, textRecord{Diff: '-', Value: })
}
if != nil {
= append(, textRecord{Diff: '+', Value: })
}
return .WithTypeMode(emitType).FormatType(.Type, )
case diffRemoved:
return .FormatValue(.ValueX, , )
case diffInserted:
return .FormatValue(.ValueY, , )
default:
panic("invalid diff mode")
}
}
if == reflect.Slice {
:= .PushPair(.ValueX, .ValueY, .DiffMode, true)
defer .Pop()
defer func() { = wrapTrunkReferences(, ) }()
}
if .TransformerName != "" {
:= .WithTypeMode(emitType).(.Value, )
= &textWrap{Prefix: "Inverse(" + .TransformerName + ", ", Value: , Suffix: ")"}
return .FormatType(.Type, )
} else {
switch := .Type.Kind(); {
case reflect.Struct, reflect.Array, reflect.Slice:
= .formatDiffList(.Records, , )
= .FormatType(.Type, )
case reflect.Map:
:= .PushPair(.ValueX, .ValueY, .DiffMode, false)
defer .Pop()
= .formatDiffList(.Records, , )
= wrapTrunkReferences(, )
= .FormatType(.Type, )
case reflect.Ptr:
:= .PushPair(.ValueX, .ValueY, .DiffMode, false)
defer .Pop()
= .(.Value, )
= wrapTrunkReferences(, )
= &textWrap{Prefix: "&", Value: }
case reflect.Interface:
= .WithTypeMode(emitType).(.Value, )
default:
panic(fmt.Sprintf("%v cannot have children", ))
}
return
}
}
func ( formatOptions) ( []reportRecord, reflect.Kind, *pointerReferences) textNode {
var string
var func(reflect.Value) string
switch {
case reflect.Struct:
= "field"
= .WithTypeMode(autoType)
= func( reflect.Value) string { return .String() }
case reflect.Slice, reflect.Array:
= "element"
= .WithTypeMode(elideType)
= func(reflect.Value) string { return "" }
case reflect.Map:
= "entry"
= .WithTypeMode(elideType)
= func( reflect.Value) string { return formatMapKey(, false, ) }
}
:= -1
if .LimitVerbosity {
if .DiffMode == diffIdentical {
= ((1 << .verbosity()) >> 1) << 2
} else {
= (1 << .verbosity()) << 1
}
.VerbosityLevel--
}
switch .DiffMode {
case diffIdentical, diffRemoved, diffInserted:
var textList
var bool
for , := range {
if len() == {
= true
break
}
if == reflect.Struct {
var bool
switch .DiffMode {
case diffIdentical:
= .Value.ValueX.IsZero() || .Value.ValueY.IsZero()
case diffRemoved:
= .Value.ValueX.IsZero()
case diffInserted:
= .Value.ValueY.IsZero()
}
if {
continue
}
}
if .Value.NumIgnored > 0 && .Value.NumSame+.Value.NumDiff == 0 {
= !( == reflect.Slice || == reflect.Array)
if ! {
.AppendEllipsis(diffStats{})
}
continue
}
if := .FormatDiff(.Value, ); != nil {
= append(, textRecord{Key: (.Key), Value: })
}
}
if {
.AppendEllipsis(diffStats{})
}
return &textWrap{Prefix: "{", Value: , Suffix: "}"}
case diffUnknown:
default:
panic("invalid diff mode")
}
var int
var textList
var []reflect.Value
:= coalesceAdjacentRecords(, )
:= diffStats{Name: }
for , := range {
if >= 0 && >= {
= .Append()
continue
}
if .NumDiff() == 0 {
var , int
:= .NumIgnored + .NumIdentical
for < numContextRecords && + < && != 0 {
if := [].Value; .NumIgnored > 0 && .NumSame+.NumDiff == 0 {
break
}
++
}
for < numContextRecords && + < && != len()-1 {
if := [--1].Value; .NumIgnored > 0 && .NumSame+.NumDiff == 0 {
break
}
++
}
if -(+) == 1 && .NumIgnored == 0 {
++
}
for , := range [:] {
:= .WithDiffMode(diffIdentical).FormatDiff(.Value, )
= append(, textRecord{Key: (.Key), Value: })
= append(, .Key)
}
if > + {
.NumIdentical -= +
.AppendEllipsis()
for len() < len() {
= append(, reflect.Value{})
}
}
for , := range [- : ] {
:= .WithDiffMode(diffIdentical).FormatDiff(.Value, )
= append(, textRecord{Key: (.Key), Value: })
= append(, .Key)
}
= [:]
continue
}
for , := range [:.NumDiff()] {
switch {
case .CanFormatDiffSlice(.Value):
:= .FormatDiffSlice(.Value)
= append(, textRecord{Key: (.Key), Value: })
= append(, .Key)
case .Value.NumChildren == .Value.MaxDepth:
:= .WithDiffMode(diffRemoved).FormatDiff(.Value, )
:= .WithDiffMode(diffInserted).FormatDiff(.Value, )
for := 0; <= maxVerbosityPreset && != nil && != nil && .Equal(); ++ {
:= verbosityPreset(, )
= .WithDiffMode(diffRemoved).FormatDiff(.Value, )
= .WithDiffMode(diffInserted).FormatDiff(.Value, )
}
if != nil {
= append(, textRecord{Diff: diffRemoved, Key: (.Key), Value: })
= append(, .Key)
}
if != nil {
= append(, textRecord{Diff: diffInserted, Key: (.Key), Value: })
= append(, .Key)
}
default:
:= .FormatDiff(.Value, )
= append(, textRecord{Key: (.Key), Value: })
= append(, .Key)
}
}
= [.NumDiff():]
+= .NumDiff()
}
if .IsZero() {
assert(len() == 0)
} else {
.AppendEllipsis()
for len() < len() {
= append(, reflect.Value{})
}
}
assert(len() == len())
if == reflect.Map {
var bool
:= map[string]reflect.Value{}
for , := range {
if .IsValid() {
:= [].Key
, := []
if && .CanInterface() && .CanInterface() {
= .Interface() != .Interface()
if {
break
}
}
[] =
}
}
if {
for , := range {
if .IsValid() {
[].Key = formatMapKey(, true, )
}
}
}
}
return &textWrap{Prefix: "{", Value: , Suffix: "}"}
}
func ( string, []reportRecord) ( []diffStats) {
var int
:= func( int) *diffStats {
if != {
= append(, diffStats{Name: })
=
}
return &[len()-1]
}
for , := range {
switch := .Value; {
case .NumIgnored > 0 && .NumSame+.NumDiff == 0:
(1).NumIgnored++
case .NumDiff == 0:
(1).NumIdentical++
case .NumDiff > 0 && !.ValueY.IsValid():
(2).NumRemoved++
case .NumDiff > 0 && !.ValueX.IsValid():
(2).NumInserted++
default:
(2).NumModified++
}
}
return
}