1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
// Licensed under the MIT license, see LICENSE file for details.
package qt
import (
"fmt"
"reflect"
"strconv"
"strings"
"unicode/utf8"
"github.com/kr/pretty"
)
// Format formats the given value as a string. It is used to print values in
// test failures.
func Format(v any) string {
switch v := v.(type) {
case error:
s, ok := checkStringCall(v, v.Error)
if !ok {
return "e<nil>"
}
if msg := fmt.Sprintf("%+v", v); msg != s {
// The error has formatted itself with additional information.
// Leave that as is.
return msg
}
return "e" + quoteString(s)
case fmt.Stringer:
s, ok := checkStringCall(v, v.String)
if !ok {
return "s<nil>"
}
return "s" + quoteString(s)
case string:
return quoteString(v)
case uintptr, uint, uint8, uint16, uint32, uint64:
// Use decimal base (rather than hexadecimal) for representing uint types.
return fmt.Sprintf("%T(%d)", v, v)
}
if bytes, ok := byteSlice(v); ok && bytes != nil && utf8.Valid(bytes) {
// It's a top level slice of bytes that's also valid UTF-8.
// Ideally, this would happen at deeper levels too,
// but this is sufficient for some significant cases
// (json.RawMessage for example).
return fmt.Sprintf("%T(%s)", v, quoteString(string(bytes)))
}
// The pretty.Sprint equivalent does not quote string values.
return fmt.Sprintf("%# v", pretty.Formatter(v))
}
func byteSlice(x any) ([]byte, bool) {
v := reflect.ValueOf(x)
if !v.IsValid() {
return nil, false
}
t := v.Type()
if t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
return v.Bytes(), true
}
return nil, false
}
func quoteString(s string) string {
// TODO think more about what to do about multi-line strings.
if strings.Contains(s, `"`) && !strings.Contains(s, "\n") && strconv.CanBackquote(s) {
return "`" + s + "`"
}
return strconv.Quote(s)
}
// checkStringCall calls f and returns its result, and reports if the call
// succeeded without panicking due to a nil pointer.
// If f panics and v is a nil pointer, it returns false.
func checkStringCall(v any, f func() string) (s string, ok bool) {
defer func() {
err := recover()
if err == nil {
return
}
if val := reflect.ValueOf(v); val.Kind() == reflect.Ptr && val.IsNil() {
ok = false
return
}
panic(err)
}()
return f(), true
}
|