194 lines
4 KiB
Go
194 lines
4 KiB
Go
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package xerrors
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// FormatError calls the FormatError method of f with an errors.Printer
|
||
|
// configured according to s and verb, and writes the result to s.
|
||
|
func FormatError(f Formatter, s fmt.State, verb rune) {
|
||
|
// Assuming this function is only called from the Format method, and given
|
||
|
// that FormatError takes precedence over Format, it cannot be called from
|
||
|
// any package that supports errors.Formatter. It is therefore safe to
|
||
|
// disregard that State may be a specific printer implementation and use one
|
||
|
// of our choice instead.
|
||
|
|
||
|
// limitations: does not support printing error as Go struct.
|
||
|
|
||
|
var (
|
||
|
sep = " " // separator before next error
|
||
|
p = &state{State: s}
|
||
|
direct = true
|
||
|
)
|
||
|
|
||
|
var err error = f
|
||
|
|
||
|
switch verb {
|
||
|
// Note that this switch must match the preference order
|
||
|
// for ordinary string printing (%#v before %+v, and so on).
|
||
|
|
||
|
case 'v':
|
||
|
if s.Flag('#') {
|
||
|
if stringer, ok := err.(fmt.GoStringer); ok {
|
||
|
io.WriteString(&p.buf, stringer.GoString())
|
||
|
goto exit
|
||
|
}
|
||
|
// proceed as if it were %v
|
||
|
} else if s.Flag('+') {
|
||
|
p.printDetail = true
|
||
|
sep = "\n - "
|
||
|
}
|
||
|
case 's':
|
||
|
case 'q', 'x', 'X':
|
||
|
// Use an intermediate buffer in the rare cases that precision,
|
||
|
// truncation, or one of the alternative verbs (q, x, and X) are
|
||
|
// specified.
|
||
|
direct = false
|
||
|
|
||
|
default:
|
||
|
p.buf.WriteString("%!")
|
||
|
p.buf.WriteRune(verb)
|
||
|
p.buf.WriteByte('(')
|
||
|
switch {
|
||
|
case err != nil:
|
||
|
p.buf.WriteString(reflect.TypeOf(f).String())
|
||
|
default:
|
||
|
p.buf.WriteString("<nil>")
|
||
|
}
|
||
|
p.buf.WriteByte(')')
|
||
|
io.Copy(s, &p.buf)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
loop:
|
||
|
for {
|
||
|
switch v := err.(type) {
|
||
|
case Formatter:
|
||
|
err = v.FormatError((*printer)(p))
|
||
|
case fmt.Formatter:
|
||
|
v.Format(p, 'v')
|
||
|
break loop
|
||
|
default:
|
||
|
io.WriteString(&p.buf, v.Error())
|
||
|
break loop
|
||
|
}
|
||
|
if err == nil {
|
||
|
break
|
||
|
}
|
||
|
if p.needColon || !p.printDetail {
|
||
|
p.buf.WriteByte(':')
|
||
|
p.needColon = false
|
||
|
}
|
||
|
p.buf.WriteString(sep)
|
||
|
p.inDetail = false
|
||
|
p.needNewline = false
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
width, okW := s.Width()
|
||
|
prec, okP := s.Precision()
|
||
|
|
||
|
if !direct || (okW && width > 0) || okP {
|
||
|
// Construct format string from State s.
|
||
|
format := []byte{'%'}
|
||
|
if s.Flag('-') {
|
||
|
format = append(format, '-')
|
||
|
}
|
||
|
if s.Flag('+') {
|
||
|
format = append(format, '+')
|
||
|
}
|
||
|
if s.Flag(' ') {
|
||
|
format = append(format, ' ')
|
||
|
}
|
||
|
if okW {
|
||
|
format = strconv.AppendInt(format, int64(width), 10)
|
||
|
}
|
||
|
if okP {
|
||
|
format = append(format, '.')
|
||
|
format = strconv.AppendInt(format, int64(prec), 10)
|
||
|
}
|
||
|
format = append(format, string(verb)...)
|
||
|
fmt.Fprintf(s, string(format), p.buf.String())
|
||
|
} else {
|
||
|
io.Copy(s, &p.buf)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var detailSep = []byte("\n ")
|
||
|
|
||
|
// state tracks error printing state. It implements fmt.State.
|
||
|
type state struct {
|
||
|
fmt.State
|
||
|
buf bytes.Buffer
|
||
|
|
||
|
printDetail bool
|
||
|
inDetail bool
|
||
|
needColon bool
|
||
|
needNewline bool
|
||
|
}
|
||
|
|
||
|
func (s *state) Write(b []byte) (n int, err error) {
|
||
|
if s.printDetail {
|
||
|
if len(b) == 0 {
|
||
|
return 0, nil
|
||
|
}
|
||
|
if s.inDetail && s.needColon {
|
||
|
s.needNewline = true
|
||
|
if b[0] == '\n' {
|
||
|
b = b[1:]
|
||
|
}
|
||
|
}
|
||
|
k := 0
|
||
|
for i, c := range b {
|
||
|
if s.needNewline {
|
||
|
if s.inDetail && s.needColon {
|
||
|
s.buf.WriteByte(':')
|
||
|
s.needColon = false
|
||
|
}
|
||
|
s.buf.Write(detailSep)
|
||
|
s.needNewline = false
|
||
|
}
|
||
|
if c == '\n' {
|
||
|
s.buf.Write(b[k:i])
|
||
|
k = i + 1
|
||
|
s.needNewline = true
|
||
|
}
|
||
|
}
|
||
|
s.buf.Write(b[k:])
|
||
|
if !s.inDetail {
|
||
|
s.needColon = true
|
||
|
}
|
||
|
} else if !s.inDetail {
|
||
|
s.buf.Write(b)
|
||
|
}
|
||
|
return len(b), nil
|
||
|
}
|
||
|
|
||
|
// printer wraps a state to implement an xerrors.Printer.
|
||
|
type printer state
|
||
|
|
||
|
func (s *printer) Print(args ...interface{}) {
|
||
|
if !s.inDetail || s.printDetail {
|
||
|
fmt.Fprint((*state)(s), args...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *printer) Printf(format string, args ...interface{}) {
|
||
|
if !s.inDetail || s.printDetail {
|
||
|
fmt.Fprintf((*state)(s), format, args...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *printer) Detail() bool {
|
||
|
s.inDetail = true
|
||
|
return s.printDetail
|
||
|
}
|