mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-18 08:26:45 +02:00
342 lines
10 KiB
Go
342 lines
10 KiB
Go
|
/*
|
||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
package spew
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||
|
// the technique used in the fmt package.
|
||
|
var (
|
||
|
panicBytes = []byte("(PANIC=")
|
||
|
plusBytes = []byte("+")
|
||
|
iBytes = []byte("i")
|
||
|
trueBytes = []byte("true")
|
||
|
falseBytes = []byte("false")
|
||
|
interfaceBytes = []byte("(interface {})")
|
||
|
commaNewlineBytes = []byte(",\n")
|
||
|
newlineBytes = []byte("\n")
|
||
|
openBraceBytes = []byte("{")
|
||
|
openBraceNewlineBytes = []byte("{\n")
|
||
|
closeBraceBytes = []byte("}")
|
||
|
asteriskBytes = []byte("*")
|
||
|
colonBytes = []byte(":")
|
||
|
colonSpaceBytes = []byte(": ")
|
||
|
openParenBytes = []byte("(")
|
||
|
closeParenBytes = []byte(")")
|
||
|
spaceBytes = []byte(" ")
|
||
|
pointerChainBytes = []byte("->")
|
||
|
nilAngleBytes = []byte("<nil>")
|
||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||
|
maxShortBytes = []byte("<max>")
|
||
|
circularBytes = []byte("<already shown>")
|
||
|
circularShortBytes = []byte("<shown>")
|
||
|
invalidAngleBytes = []byte("<invalid>")
|
||
|
openBracketBytes = []byte("[")
|
||
|
closeBracketBytes = []byte("]")
|
||
|
percentBytes = []byte("%")
|
||
|
precisionBytes = []byte(".")
|
||
|
openAngleBytes = []byte("<")
|
||
|
closeAngleBytes = []byte(">")
|
||
|
openMapBytes = []byte("map[")
|
||
|
closeMapBytes = []byte("]")
|
||
|
lenEqualsBytes = []byte("len=")
|
||
|
capEqualsBytes = []byte("cap=")
|
||
|
)
|
||
|
|
||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||
|
var hexDigits = "0123456789abcdef"
|
||
|
|
||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||
|
// calls.
|
||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||
|
if err := recover(); err != nil {
|
||
|
w.Write(panicBytes)
|
||
|
fmt.Fprintf(w, "%v", err)
|
||
|
w.Write(closeParenBytes)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||
|
//
|
||
|
// It handles panics in any called methods by catching and displaying the error
|
||
|
// as the formatted value.
|
||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||
|
// We need an interface to check if the type implements the error or
|
||
|
// Stringer interface. However, the reflect package won't give us an
|
||
|
// interface on certain things like unexported struct fields in order
|
||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||
|
// to bypass these restrictions since this package does not mutate the
|
||
|
// values.
|
||
|
if !v.CanInterface() {
|
||
|
if UnsafeDisabled {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
v = unsafeReflectValue(v)
|
||
|
}
|
||
|
|
||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||
|
// the base type or a pointer to the base type depending on settings.
|
||
|
// Technically calling one of these methods with a pointer receiver can
|
||
|
// mutate the value, however, types which choose to satisify an error or
|
||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||
|
// state inside these interface methods.
|
||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||
|
v = unsafeReflectValue(v)
|
||
|
}
|
||
|
if v.CanAddr() {
|
||
|
v = v.Addr()
|
||
|
}
|
||
|
|
||
|
// Is it an error or Stringer?
|
||
|
switch iface := v.Interface().(type) {
|
||
|
case error:
|
||
|
defer catchPanic(w, v)
|
||
|
if cs.ContinueOnMethod {
|
||
|
w.Write(openParenBytes)
|
||
|
w.Write([]byte(iface.Error()))
|
||
|
w.Write(closeParenBytes)
|
||
|
w.Write(spaceBytes)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
w.Write([]byte(iface.Error()))
|
||
|
return true
|
||
|
|
||
|
case fmt.Stringer:
|
||
|
defer catchPanic(w, v)
|
||
|
if cs.ContinueOnMethod {
|
||
|
w.Write(openParenBytes)
|
||
|
w.Write([]byte(iface.String()))
|
||
|
w.Write(closeParenBytes)
|
||
|
w.Write(spaceBytes)
|
||
|
return false
|
||
|
}
|
||
|
w.Write([]byte(iface.String()))
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||
|
func printBool(w io.Writer, val bool) {
|
||
|
if val {
|
||
|
w.Write(trueBytes)
|
||
|
} else {
|
||
|
w.Write(falseBytes)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// printInt outputs a signed integer value to Writer w.
|
||
|
func printInt(w io.Writer, val int64, base int) {
|
||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||
|
}
|
||
|
|
||
|
// printUint outputs an unsigned integer value to Writer w.
|
||
|
func printUint(w io.Writer, val uint64, base int) {
|
||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||
|
}
|
||
|
|
||
|
// printFloat outputs a floating point value using the specified precision,
|
||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||
|
}
|
||
|
|
||
|
// printComplex outputs a complex value using the specified float precision
|
||
|
// for the real and imaginary parts to Writer w.
|
||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||
|
r := real(c)
|
||
|
w.Write(openParenBytes)
|
||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||
|
i := imag(c)
|
||
|
if i >= 0 {
|
||
|
w.Write(plusBytes)
|
||
|
}
|
||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||
|
w.Write(iBytes)
|
||
|
w.Write(closeParenBytes)
|
||
|
}
|
||
|
|
||
|
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||
|
// prefix to Writer w.
|
||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||
|
// Null pointer.
|
||
|
num := uint64(p)
|
||
|
if num == 0 {
|
||
|
w.Write(nilAngleBytes)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||
|
buf := make([]byte, 18)
|
||
|
|
||
|
// It's simpler to construct the hex string right to left.
|
||
|
base := uint64(16)
|
||
|
i := len(buf) - 1
|
||
|
for num >= base {
|
||
|
buf[i] = hexDigits[num%base]
|
||
|
num /= base
|
||
|
i--
|
||
|
}
|
||
|
buf[i] = hexDigits[num]
|
||
|
|
||
|
// Add '0x' prefix.
|
||
|
i--
|
||
|
buf[i] = 'x'
|
||
|
i--
|
||
|
buf[i] = '0'
|
||
|
|
||
|
// Strip unused leading bytes.
|
||
|
buf = buf[i:]
|
||
|
w.Write(buf)
|
||
|
}
|
||
|
|
||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||
|
// elements to be sorted.
|
||
|
type valuesSorter struct {
|
||
|
values []reflect.Value
|
||
|
strings []string // either nil or same len and values
|
||
|
cs *ConfigState
|
||
|
}
|
||
|
|
||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||
|
vs := &valuesSorter{values: values, cs: cs}
|
||
|
if canSortSimply(vs.values[0].Kind()) {
|
||
|
return vs
|
||
|
}
|
||
|
if !cs.DisableMethods {
|
||
|
vs.strings = make([]string, len(values))
|
||
|
for i := range vs.values {
|
||
|
b := bytes.Buffer{}
|
||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||
|
vs.strings = nil
|
||
|
break
|
||
|
}
|
||
|
vs.strings[i] = b.String()
|
||
|
}
|
||
|
}
|
||
|
if vs.strings == nil && cs.SpewKeys {
|
||
|
vs.strings = make([]string, len(values))
|
||
|
for i := range vs.values {
|
||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||
|
}
|
||
|
}
|
||
|
return vs
|
||
|
}
|
||
|
|
||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||
|
// (if the ConfigState allows it).
|
||
|
func canSortSimply(kind reflect.Kind) bool {
|
||
|
// This switch parallels valueSortLess, except for the default case.
|
||
|
switch kind {
|
||
|
case reflect.Bool:
|
||
|
return true
|
||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||
|
return true
|
||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||
|
return true
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
return true
|
||
|
case reflect.String:
|
||
|
return true
|
||
|
case reflect.Uintptr:
|
||
|
return true
|
||
|
case reflect.Array:
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Len returns the number of values in the slice. It is part of the
|
||
|
// sort.Interface implementation.
|
||
|
func (s *valuesSorter) Len() int {
|
||
|
return len(s.values)
|
||
|
}
|
||
|
|
||
|
// Swap swaps the values at the passed indices. It is part of the
|
||
|
// sort.Interface implementation.
|
||
|
func (s *valuesSorter) Swap(i, j int) {
|
||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||
|
if s.strings != nil {
|
||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// valueSortLess returns whether the first value should sort before the second
|
||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||
|
// implementation.
|
||
|
func valueSortLess(a, b reflect.Value) bool {
|
||
|
switch a.Kind() {
|
||
|
case reflect.Bool:
|
||
|
return !a.Bool() && b.Bool()
|
||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||
|
return a.Int() < b.Int()
|
||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||
|
return a.Uint() < b.Uint()
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
return a.Float() < b.Float()
|
||
|
case reflect.String:
|
||
|
return a.String() < b.String()
|
||
|
case reflect.Uintptr:
|
||
|
return a.Uint() < b.Uint()
|
||
|
case reflect.Array:
|
||
|
// Compare the contents of both arrays.
|
||
|
l := a.Len()
|
||
|
for i := 0; i < l; i++ {
|
||
|
av := a.Index(i)
|
||
|
bv := b.Index(i)
|
||
|
if av.Interface() == bv.Interface() {
|
||
|
continue
|
||
|
}
|
||
|
return valueSortLess(av, bv)
|
||
|
}
|
||
|
}
|
||
|
return a.String() < b.String()
|
||
|
}
|
||
|
|
||
|
// Less returns whether the value at index i should sort before the
|
||
|
// value at index j. It is part of the sort.Interface implementation.
|
||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||
|
if s.strings == nil {
|
||
|
return valueSortLess(s.values[i], s.values[j])
|
||
|
}
|
||
|
return s.strings[i] < s.strings[j]
|
||
|
}
|
||
|
|
||
|
// sortValues is a sort function that handles both native types and any type that
|
||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||
|
// their Value.String() value to ensure display stability.
|
||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||
|
if len(values) == 0 {
|
||
|
return
|
||
|
}
|
||
|
sort.Sort(newValuesSorter(values, cs))
|
||
|
}
|