1*88d15eacSSasha Smundak// Copyright 2019, The Go Authors. All rights reserved. 2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style 3*88d15eacSSasha Smundak// license that can be found in the LICENSE file. 4*88d15eacSSasha Smundak 5*88d15eacSSasha Smundakpackage cmp 6*88d15eacSSasha Smundak 7*88d15eacSSasha Smundakimport ( 8*88d15eacSSasha Smundak "bytes" 9*88d15eacSSasha Smundak "fmt" 10*88d15eacSSasha Smundak "reflect" 11*88d15eacSSasha Smundak "strconv" 12*88d15eacSSasha Smundak "strings" 13*88d15eacSSasha Smundak "unicode" 14*88d15eacSSasha Smundak "unicode/utf8" 15*88d15eacSSasha Smundak 16*88d15eacSSasha Smundak "github.com/google/go-cmp/cmp/internal/value" 17*88d15eacSSasha Smundak) 18*88d15eacSSasha Smundak 19*88d15eacSSasha Smundakvar ( 20*88d15eacSSasha Smundak anyType = reflect.TypeOf((*interface{})(nil)).Elem() 21*88d15eacSSasha Smundak stringType = reflect.TypeOf((*string)(nil)).Elem() 22*88d15eacSSasha Smundak bytesType = reflect.TypeOf((*[]byte)(nil)).Elem() 23*88d15eacSSasha Smundak byteType = reflect.TypeOf((*byte)(nil)).Elem() 24*88d15eacSSasha Smundak) 25*88d15eacSSasha Smundak 26*88d15eacSSasha Smundaktype formatValueOptions struct { 27*88d15eacSSasha Smundak // AvoidStringer controls whether to avoid calling custom stringer 28*88d15eacSSasha Smundak // methods like error.Error or fmt.Stringer.String. 29*88d15eacSSasha Smundak AvoidStringer bool 30*88d15eacSSasha Smundak 31*88d15eacSSasha Smundak // PrintAddresses controls whether to print the address of all pointers, 32*88d15eacSSasha Smundak // slice elements, and maps. 33*88d15eacSSasha Smundak PrintAddresses bool 34*88d15eacSSasha Smundak 35*88d15eacSSasha Smundak // QualifiedNames controls whether FormatType uses the fully qualified name 36*88d15eacSSasha Smundak // (including the full package path as opposed to just the package name). 37*88d15eacSSasha Smundak QualifiedNames bool 38*88d15eacSSasha Smundak 39*88d15eacSSasha Smundak // VerbosityLevel controls the amount of output to produce. 40*88d15eacSSasha Smundak // A higher value produces more output. A value of zero or lower produces 41*88d15eacSSasha Smundak // no output (represented using an ellipsis). 42*88d15eacSSasha Smundak // If LimitVerbosity is false, then the level is treated as infinite. 43*88d15eacSSasha Smundak VerbosityLevel int 44*88d15eacSSasha Smundak 45*88d15eacSSasha Smundak // LimitVerbosity specifies that formatting should respect VerbosityLevel. 46*88d15eacSSasha Smundak LimitVerbosity bool 47*88d15eacSSasha Smundak} 48*88d15eacSSasha Smundak 49*88d15eacSSasha Smundak// FormatType prints the type as if it were wrapping s. 50*88d15eacSSasha Smundak// This may return s as-is depending on the current type and TypeMode mode. 51*88d15eacSSasha Smundakfunc (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode { 52*88d15eacSSasha Smundak // Check whether to emit the type or not. 53*88d15eacSSasha Smundak switch opts.TypeMode { 54*88d15eacSSasha Smundak case autoType: 55*88d15eacSSasha Smundak switch t.Kind() { 56*88d15eacSSasha Smundak case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: 57*88d15eacSSasha Smundak if s.Equal(textNil) { 58*88d15eacSSasha Smundak return s 59*88d15eacSSasha Smundak } 60*88d15eacSSasha Smundak default: 61*88d15eacSSasha Smundak return s 62*88d15eacSSasha Smundak } 63*88d15eacSSasha Smundak if opts.DiffMode == diffIdentical { 64*88d15eacSSasha Smundak return s // elide type for identical nodes 65*88d15eacSSasha Smundak } 66*88d15eacSSasha Smundak case elideType: 67*88d15eacSSasha Smundak return s 68*88d15eacSSasha Smundak } 69*88d15eacSSasha Smundak 70*88d15eacSSasha Smundak // Determine the type label, applying special handling for unnamed types. 71*88d15eacSSasha Smundak typeName := value.TypeString(t, opts.QualifiedNames) 72*88d15eacSSasha Smundak if t.Name() == "" { 73*88d15eacSSasha Smundak // According to Go grammar, certain type literals contain symbols that 74*88d15eacSSasha Smundak // do not strongly bind to the next lexicographical token (e.g., *T). 75*88d15eacSSasha Smundak switch t.Kind() { 76*88d15eacSSasha Smundak case reflect.Chan, reflect.Func, reflect.Ptr: 77*88d15eacSSasha Smundak typeName = "(" + typeName + ")" 78*88d15eacSSasha Smundak } 79*88d15eacSSasha Smundak } 80*88d15eacSSasha Smundak return &textWrap{Prefix: typeName, Value: wrapParens(s)} 81*88d15eacSSasha Smundak} 82*88d15eacSSasha Smundak 83*88d15eacSSasha Smundak// wrapParens wraps s with a set of parenthesis, but avoids it if the 84*88d15eacSSasha Smundak// wrapped node itself is already surrounded by a pair of parenthesis or braces. 85*88d15eacSSasha Smundak// It handles unwrapping one level of pointer-reference nodes. 86*88d15eacSSasha Smundakfunc wrapParens(s textNode) textNode { 87*88d15eacSSasha Smundak var refNode *textWrap 88*88d15eacSSasha Smundak if s2, ok := s.(*textWrap); ok { 89*88d15eacSSasha Smundak // Unwrap a single pointer reference node. 90*88d15eacSSasha Smundak switch s2.Metadata.(type) { 91*88d15eacSSasha Smundak case leafReference, trunkReference, trunkReferences: 92*88d15eacSSasha Smundak refNode = s2 93*88d15eacSSasha Smundak if s3, ok := refNode.Value.(*textWrap); ok { 94*88d15eacSSasha Smundak s2 = s3 95*88d15eacSSasha Smundak } 96*88d15eacSSasha Smundak } 97*88d15eacSSasha Smundak 98*88d15eacSSasha Smundak // Already has delimiters that make parenthesis unnecessary. 99*88d15eacSSasha Smundak hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")") 100*88d15eacSSasha Smundak hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}") 101*88d15eacSSasha Smundak if hasParens || hasBraces { 102*88d15eacSSasha Smundak return s 103*88d15eacSSasha Smundak } 104*88d15eacSSasha Smundak } 105*88d15eacSSasha Smundak if refNode != nil { 106*88d15eacSSasha Smundak refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"} 107*88d15eacSSasha Smundak return s 108*88d15eacSSasha Smundak } 109*88d15eacSSasha Smundak return &textWrap{Prefix: "(", Value: s, Suffix: ")"} 110*88d15eacSSasha Smundak} 111*88d15eacSSasha Smundak 112*88d15eacSSasha Smundak// FormatValue prints the reflect.Value, taking extra care to avoid descending 113*88d15eacSSasha Smundak// into pointers already in ptrs. As pointers are visited, ptrs is also updated. 114*88d15eacSSasha Smundakfunc (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) { 115*88d15eacSSasha Smundak if !v.IsValid() { 116*88d15eacSSasha Smundak return nil 117*88d15eacSSasha Smundak } 118*88d15eacSSasha Smundak t := v.Type() 119*88d15eacSSasha Smundak 120*88d15eacSSasha Smundak // Check slice element for cycles. 121*88d15eacSSasha Smundak if parentKind == reflect.Slice { 122*88d15eacSSasha Smundak ptrRef, visited := ptrs.Push(v.Addr()) 123*88d15eacSSasha Smundak if visited { 124*88d15eacSSasha Smundak return makeLeafReference(ptrRef, false) 125*88d15eacSSasha Smundak } 126*88d15eacSSasha Smundak defer ptrs.Pop() 127*88d15eacSSasha Smundak defer func() { out = wrapTrunkReference(ptrRef, false, out) }() 128*88d15eacSSasha Smundak } 129*88d15eacSSasha Smundak 130*88d15eacSSasha Smundak // Check whether there is an Error or String method to call. 131*88d15eacSSasha Smundak if !opts.AvoidStringer && v.CanInterface() { 132*88d15eacSSasha Smundak // Avoid calling Error or String methods on nil receivers since many 133*88d15eacSSasha Smundak // implementations crash when doing so. 134*88d15eacSSasha Smundak if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() { 135*88d15eacSSasha Smundak var prefix, strVal string 136*88d15eacSSasha Smundak func() { 137*88d15eacSSasha Smundak // Swallow and ignore any panics from String or Error. 138*88d15eacSSasha Smundak defer func() { recover() }() 139*88d15eacSSasha Smundak switch v := v.Interface().(type) { 140*88d15eacSSasha Smundak case error: 141*88d15eacSSasha Smundak strVal = v.Error() 142*88d15eacSSasha Smundak prefix = "e" 143*88d15eacSSasha Smundak case fmt.Stringer: 144*88d15eacSSasha Smundak strVal = v.String() 145*88d15eacSSasha Smundak prefix = "s" 146*88d15eacSSasha Smundak } 147*88d15eacSSasha Smundak }() 148*88d15eacSSasha Smundak if prefix != "" { 149*88d15eacSSasha Smundak return opts.formatString(prefix, strVal) 150*88d15eacSSasha Smundak } 151*88d15eacSSasha Smundak } 152*88d15eacSSasha Smundak } 153*88d15eacSSasha Smundak 154*88d15eacSSasha Smundak // Check whether to explicitly wrap the result with the type. 155*88d15eacSSasha Smundak var skipType bool 156*88d15eacSSasha Smundak defer func() { 157*88d15eacSSasha Smundak if !skipType { 158*88d15eacSSasha Smundak out = opts.FormatType(t, out) 159*88d15eacSSasha Smundak } 160*88d15eacSSasha Smundak }() 161*88d15eacSSasha Smundak 162*88d15eacSSasha Smundak switch t.Kind() { 163*88d15eacSSasha Smundak case reflect.Bool: 164*88d15eacSSasha Smundak return textLine(fmt.Sprint(v.Bool())) 165*88d15eacSSasha Smundak case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 166*88d15eacSSasha Smundak return textLine(fmt.Sprint(v.Int())) 167*88d15eacSSasha Smundak case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: 168*88d15eacSSasha Smundak return textLine(fmt.Sprint(v.Uint())) 169*88d15eacSSasha Smundak case reflect.Uint8: 170*88d15eacSSasha Smundak if parentKind == reflect.Slice || parentKind == reflect.Array { 171*88d15eacSSasha Smundak return textLine(formatHex(v.Uint())) 172*88d15eacSSasha Smundak } 173*88d15eacSSasha Smundak return textLine(fmt.Sprint(v.Uint())) 174*88d15eacSSasha Smundak case reflect.Uintptr: 175*88d15eacSSasha Smundak return textLine(formatHex(v.Uint())) 176*88d15eacSSasha Smundak case reflect.Float32, reflect.Float64: 177*88d15eacSSasha Smundak return textLine(fmt.Sprint(v.Float())) 178*88d15eacSSasha Smundak case reflect.Complex64, reflect.Complex128: 179*88d15eacSSasha Smundak return textLine(fmt.Sprint(v.Complex())) 180*88d15eacSSasha Smundak case reflect.String: 181*88d15eacSSasha Smundak return opts.formatString("", v.String()) 182*88d15eacSSasha Smundak case reflect.UnsafePointer, reflect.Chan, reflect.Func: 183*88d15eacSSasha Smundak return textLine(formatPointer(value.PointerOf(v), true)) 184*88d15eacSSasha Smundak case reflect.Struct: 185*88d15eacSSasha Smundak var list textList 186*88d15eacSSasha Smundak v := makeAddressable(v) // needed for retrieveUnexportedField 187*88d15eacSSasha Smundak maxLen := v.NumField() 188*88d15eacSSasha Smundak if opts.LimitVerbosity { 189*88d15eacSSasha Smundak maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... 190*88d15eacSSasha Smundak opts.VerbosityLevel-- 191*88d15eacSSasha Smundak } 192*88d15eacSSasha Smundak for i := 0; i < v.NumField(); i++ { 193*88d15eacSSasha Smundak vv := v.Field(i) 194*88d15eacSSasha Smundak if vv.IsZero() { 195*88d15eacSSasha Smundak continue // Elide fields with zero values 196*88d15eacSSasha Smundak } 197*88d15eacSSasha Smundak if len(list) == maxLen { 198*88d15eacSSasha Smundak list.AppendEllipsis(diffStats{}) 199*88d15eacSSasha Smundak break 200*88d15eacSSasha Smundak } 201*88d15eacSSasha Smundak sf := t.Field(i) 202*88d15eacSSasha Smundak if supportExporters && !isExported(sf.Name) { 203*88d15eacSSasha Smundak vv = retrieveUnexportedField(v, sf, true) 204*88d15eacSSasha Smundak } 205*88d15eacSSasha Smundak s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs) 206*88d15eacSSasha Smundak list = append(list, textRecord{Key: sf.Name, Value: s}) 207*88d15eacSSasha Smundak } 208*88d15eacSSasha Smundak return &textWrap{Prefix: "{", Value: list, Suffix: "}"} 209*88d15eacSSasha Smundak case reflect.Slice: 210*88d15eacSSasha Smundak if v.IsNil() { 211*88d15eacSSasha Smundak return textNil 212*88d15eacSSasha Smundak } 213*88d15eacSSasha Smundak 214*88d15eacSSasha Smundak // Check whether this is a []byte of text data. 215*88d15eacSSasha Smundak if t.Elem() == byteType { 216*88d15eacSSasha Smundak b := v.Bytes() 217*88d15eacSSasha Smundak isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) } 218*88d15eacSSasha Smundak if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { 219*88d15eacSSasha Smundak out = opts.formatString("", string(b)) 220*88d15eacSSasha Smundak skipType = true 221*88d15eacSSasha Smundak return opts.FormatType(t, out) 222*88d15eacSSasha Smundak } 223*88d15eacSSasha Smundak } 224*88d15eacSSasha Smundak 225*88d15eacSSasha Smundak fallthrough 226*88d15eacSSasha Smundak case reflect.Array: 227*88d15eacSSasha Smundak maxLen := v.Len() 228*88d15eacSSasha Smundak if opts.LimitVerbosity { 229*88d15eacSSasha Smundak maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... 230*88d15eacSSasha Smundak opts.VerbosityLevel-- 231*88d15eacSSasha Smundak } 232*88d15eacSSasha Smundak var list textList 233*88d15eacSSasha Smundak for i := 0; i < v.Len(); i++ { 234*88d15eacSSasha Smundak if len(list) == maxLen { 235*88d15eacSSasha Smundak list.AppendEllipsis(diffStats{}) 236*88d15eacSSasha Smundak break 237*88d15eacSSasha Smundak } 238*88d15eacSSasha Smundak s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs) 239*88d15eacSSasha Smundak list = append(list, textRecord{Value: s}) 240*88d15eacSSasha Smundak } 241*88d15eacSSasha Smundak 242*88d15eacSSasha Smundak out = &textWrap{Prefix: "{", Value: list, Suffix: "}"} 243*88d15eacSSasha Smundak if t.Kind() == reflect.Slice && opts.PrintAddresses { 244*88d15eacSSasha Smundak header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap()) 245*88d15eacSSasha Smundak out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out} 246*88d15eacSSasha Smundak } 247*88d15eacSSasha Smundak return out 248*88d15eacSSasha Smundak case reflect.Map: 249*88d15eacSSasha Smundak if v.IsNil() { 250*88d15eacSSasha Smundak return textNil 251*88d15eacSSasha Smundak } 252*88d15eacSSasha Smundak 253*88d15eacSSasha Smundak // Check pointer for cycles. 254*88d15eacSSasha Smundak ptrRef, visited := ptrs.Push(v) 255*88d15eacSSasha Smundak if visited { 256*88d15eacSSasha Smundak return makeLeafReference(ptrRef, opts.PrintAddresses) 257*88d15eacSSasha Smundak } 258*88d15eacSSasha Smundak defer ptrs.Pop() 259*88d15eacSSasha Smundak 260*88d15eacSSasha Smundak maxLen := v.Len() 261*88d15eacSSasha Smundak if opts.LimitVerbosity { 262*88d15eacSSasha Smundak maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... 263*88d15eacSSasha Smundak opts.VerbosityLevel-- 264*88d15eacSSasha Smundak } 265*88d15eacSSasha Smundak var list textList 266*88d15eacSSasha Smundak for _, k := range value.SortKeys(v.MapKeys()) { 267*88d15eacSSasha Smundak if len(list) == maxLen { 268*88d15eacSSasha Smundak list.AppendEllipsis(diffStats{}) 269*88d15eacSSasha Smundak break 270*88d15eacSSasha Smundak } 271*88d15eacSSasha Smundak sk := formatMapKey(k, false, ptrs) 272*88d15eacSSasha Smundak sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs) 273*88d15eacSSasha Smundak list = append(list, textRecord{Key: sk, Value: sv}) 274*88d15eacSSasha Smundak } 275*88d15eacSSasha Smundak 276*88d15eacSSasha Smundak out = &textWrap{Prefix: "{", Value: list, Suffix: "}"} 277*88d15eacSSasha Smundak out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) 278*88d15eacSSasha Smundak return out 279*88d15eacSSasha Smundak case reflect.Ptr: 280*88d15eacSSasha Smundak if v.IsNil() { 281*88d15eacSSasha Smundak return textNil 282*88d15eacSSasha Smundak } 283*88d15eacSSasha Smundak 284*88d15eacSSasha Smundak // Check pointer for cycles. 285*88d15eacSSasha Smundak ptrRef, visited := ptrs.Push(v) 286*88d15eacSSasha Smundak if visited { 287*88d15eacSSasha Smundak out = makeLeafReference(ptrRef, opts.PrintAddresses) 288*88d15eacSSasha Smundak return &textWrap{Prefix: "&", Value: out} 289*88d15eacSSasha Smundak } 290*88d15eacSSasha Smundak defer ptrs.Pop() 291*88d15eacSSasha Smundak 292*88d15eacSSasha Smundak // Skip the name only if this is an unnamed pointer type. 293*88d15eacSSasha Smundak // Otherwise taking the address of a value does not reproduce 294*88d15eacSSasha Smundak // the named pointer type. 295*88d15eacSSasha Smundak if v.Type().Name() == "" { 296*88d15eacSSasha Smundak skipType = true // Let the underlying value print the type instead 297*88d15eacSSasha Smundak } 298*88d15eacSSasha Smundak out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) 299*88d15eacSSasha Smundak out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) 300*88d15eacSSasha Smundak out = &textWrap{Prefix: "&", Value: out} 301*88d15eacSSasha Smundak return out 302*88d15eacSSasha Smundak case reflect.Interface: 303*88d15eacSSasha Smundak if v.IsNil() { 304*88d15eacSSasha Smundak return textNil 305*88d15eacSSasha Smundak } 306*88d15eacSSasha Smundak // Interfaces accept different concrete types, 307*88d15eacSSasha Smundak // so configure the underlying value to explicitly print the type. 308*88d15eacSSasha Smundak return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs) 309*88d15eacSSasha Smundak default: 310*88d15eacSSasha Smundak panic(fmt.Sprintf("%v kind not handled", v.Kind())) 311*88d15eacSSasha Smundak } 312*88d15eacSSasha Smundak} 313*88d15eacSSasha Smundak 314*88d15eacSSasha Smundakfunc (opts formatOptions) formatString(prefix, s string) textNode { 315*88d15eacSSasha Smundak maxLen := len(s) 316*88d15eacSSasha Smundak maxLines := strings.Count(s, "\n") + 1 317*88d15eacSSasha Smundak if opts.LimitVerbosity { 318*88d15eacSSasha Smundak maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... 319*88d15eacSSasha Smundak maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc... 320*88d15eacSSasha Smundak } 321*88d15eacSSasha Smundak 322*88d15eacSSasha Smundak // For multiline strings, use the triple-quote syntax, 323*88d15eacSSasha Smundak // but only use it when printing removed or inserted nodes since 324*88d15eacSSasha Smundak // we only want the extra verbosity for those cases. 325*88d15eacSSasha Smundak lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n") 326*88d15eacSSasha Smundak isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+') 327*88d15eacSSasha Smundak for i := 0; i < len(lines) && isTripleQuoted; i++ { 328*88d15eacSSasha Smundak lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support 329*88d15eacSSasha Smundak isPrintable := func(r rune) bool { 330*88d15eacSSasha Smundak return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable 331*88d15eacSSasha Smundak } 332*88d15eacSSasha Smundak line := lines[i] 333*88d15eacSSasha Smundak isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen 334*88d15eacSSasha Smundak } 335*88d15eacSSasha Smundak if isTripleQuoted { 336*88d15eacSSasha Smundak var list textList 337*88d15eacSSasha Smundak list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true}) 338*88d15eacSSasha Smundak for i, line := range lines { 339*88d15eacSSasha Smundak if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 { 340*88d15eacSSasha Smundak comment := commentString(fmt.Sprintf("%d elided lines", numElided)) 341*88d15eacSSasha Smundak list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment}) 342*88d15eacSSasha Smundak break 343*88d15eacSSasha Smundak } 344*88d15eacSSasha Smundak list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true}) 345*88d15eacSSasha Smundak } 346*88d15eacSSasha Smundak list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true}) 347*88d15eacSSasha Smundak return &textWrap{Prefix: "(", Value: list, Suffix: ")"} 348*88d15eacSSasha Smundak } 349*88d15eacSSasha Smundak 350*88d15eacSSasha Smundak // Format the string as a single-line quoted string. 351*88d15eacSSasha Smundak if len(s) > maxLen+len(textEllipsis) { 352*88d15eacSSasha Smundak return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis)) 353*88d15eacSSasha Smundak } 354*88d15eacSSasha Smundak return textLine(prefix + formatString(s)) 355*88d15eacSSasha Smundak} 356*88d15eacSSasha Smundak 357*88d15eacSSasha Smundak// formatMapKey formats v as if it were a map key. 358*88d15eacSSasha Smundak// The result is guaranteed to be a single line. 359*88d15eacSSasha Smundakfunc formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string { 360*88d15eacSSasha Smundak var opts formatOptions 361*88d15eacSSasha Smundak opts.DiffMode = diffIdentical 362*88d15eacSSasha Smundak opts.TypeMode = elideType 363*88d15eacSSasha Smundak opts.PrintAddresses = disambiguate 364*88d15eacSSasha Smundak opts.AvoidStringer = disambiguate 365*88d15eacSSasha Smundak opts.QualifiedNames = disambiguate 366*88d15eacSSasha Smundak opts.VerbosityLevel = maxVerbosityPreset 367*88d15eacSSasha Smundak opts.LimitVerbosity = true 368*88d15eacSSasha Smundak s := opts.FormatValue(v, reflect.Map, ptrs).String() 369*88d15eacSSasha Smundak return strings.TrimSpace(s) 370*88d15eacSSasha Smundak} 371*88d15eacSSasha Smundak 372*88d15eacSSasha Smundak// formatString prints s as a double-quoted or backtick-quoted string. 373*88d15eacSSasha Smundakfunc formatString(s string) string { 374*88d15eacSSasha Smundak // Use quoted string if it the same length as a raw string literal. 375*88d15eacSSasha Smundak // Otherwise, attempt to use the raw string form. 376*88d15eacSSasha Smundak qs := strconv.Quote(s) 377*88d15eacSSasha Smundak if len(qs) == 1+len(s)+1 { 378*88d15eacSSasha Smundak return qs 379*88d15eacSSasha Smundak } 380*88d15eacSSasha Smundak 381*88d15eacSSasha Smundak // Disallow newlines to ensure output is a single line. 382*88d15eacSSasha Smundak // Only allow printable runes for readability purposes. 383*88d15eacSSasha Smundak rawInvalid := func(r rune) bool { 384*88d15eacSSasha Smundak return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t') 385*88d15eacSSasha Smundak } 386*88d15eacSSasha Smundak if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 { 387*88d15eacSSasha Smundak return "`" + s + "`" 388*88d15eacSSasha Smundak } 389*88d15eacSSasha Smundak return qs 390*88d15eacSSasha Smundak} 391*88d15eacSSasha Smundak 392*88d15eacSSasha Smundak// formatHex prints u as a hexadecimal integer in Go notation. 393*88d15eacSSasha Smundakfunc formatHex(u uint64) string { 394*88d15eacSSasha Smundak var f string 395*88d15eacSSasha Smundak switch { 396*88d15eacSSasha Smundak case u <= 0xff: 397*88d15eacSSasha Smundak f = "0x%02x" 398*88d15eacSSasha Smundak case u <= 0xffff: 399*88d15eacSSasha Smundak f = "0x%04x" 400*88d15eacSSasha Smundak case u <= 0xffffff: 401*88d15eacSSasha Smundak f = "0x%06x" 402*88d15eacSSasha Smundak case u <= 0xffffffff: 403*88d15eacSSasha Smundak f = "0x%08x" 404*88d15eacSSasha Smundak case u <= 0xffffffffff: 405*88d15eacSSasha Smundak f = "0x%010x" 406*88d15eacSSasha Smundak case u <= 0xffffffffffff: 407*88d15eacSSasha Smundak f = "0x%012x" 408*88d15eacSSasha Smundak case u <= 0xffffffffffffff: 409*88d15eacSSasha Smundak f = "0x%014x" 410*88d15eacSSasha Smundak case u <= 0xffffffffffffffff: 411*88d15eacSSasha Smundak f = "0x%016x" 412*88d15eacSSasha Smundak } 413*88d15eacSSasha Smundak return fmt.Sprintf(f, u) 414*88d15eacSSasha Smundak} 415