blob: fbe8579a39efd51a24480c85b0e7636041aa9a92 [file] [log] [blame]
// 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 fmt
import (
"golang.org/x/exp/errors"
)
// fmtError formats err according to verb, writing to p.
// If it cannot handle the error, it does no formatting
// and returns false.
func fmtError(p *pp, verb rune, err error) (handled bool) {
var (
sep = ": " // separator before next error
w = p // print buffer where error text is written
)
switch {
// Note that this switch must match the preference order
// for ordinary string printing (%#v before %+v, and so on).
case p.fmt.sharpV:
if stringer, ok := p.arg.(GoStringer); ok {
// Print the result of GoString unadorned.
p.fmt.fmtS(stringer.GoString())
return true
}
return false
case p.fmt.plusV:
sep = "\n--- "
w.fmt.fmtFlags = fmtFlags{plusV: p.fmt.plusV} // only keep detail flag
// The width or precision of a detailed view could be the number of
// errors to print from a list.
default:
// Use an intermediate buffer in the rare cases that precision,
// truncation, or one of the alternative verbs (q, x, and X) are
// specified.
switch verb {
case 's', 'v':
if (!w.fmt.widPresent || w.fmt.wid == 0) && !w.fmt.precPresent {
break
}
fallthrough
case 'q', 'x', 'X':
w = newPrinter()
defer w.free()
default:
w.badVerb(verb)
return true
}
}
loop:
for {
w.fmt.inDetail = false
switch v := err.(type) {
case errors.Formatter:
err = v.Format((*errPP)(w))
// TODO: This case is for supporting old error implementations.
// It may eventually disappear.
case interface{ FormatError(errors.Printer) error }:
err = v.FormatError((*errPP)(w))
case Formatter:
// Discard verb, but keep the flags. Discarding the verb prevents
// nested quoting and other unwanted behavior. Preserving flags
// recursively signals a request for detail, if interpreted as %+v.
w.fmt.fmtFlags = p.fmt.fmtFlags
if w.fmt.plusV {
v.Format((*errPPState)(w), 'v') // indent new lines
} else {
v.Format(w, 'v') // do not indent new lines
}
break loop
default:
w.fmtString(v.Error(), 's')
break loop
}
if err == nil {
break
}
w.buf.WriteString(sep)
}
if w != p {
p.fmtString(string(w.buf), verb)
}
return true
}
// errPPState wraps a pp to implement State with indentation. It is used
// for errors implementing fmt.Formatter.
type errPPState pp
func (p *errPPState) Width() (wid int, ok bool) { return (*pp)(p).Width() }
func (p *errPPState) Precision() (prec int, ok bool) { return (*pp)(p).Precision() }
func (p *errPPState) Flag(c int) bool { return (*pp)(p).Flag(c) }
func (p *errPPState) Write(b []byte) (n int, err error) {
if !p.fmt.inDetail || p.fmt.plusV {
k := 0
if p.fmt.indent {
for i, c := range b {
if c == '\n' {
p.buf.Write(b[k:i])
p.buf.Write([]byte("\n "))
k = i + 1
}
}
}
p.buf.Write(b[k:])
}
return len(b), nil
}
// errPP wraps a pp to implement an errors.Printer.
type errPP pp
func (p *errPP) Print(args ...interface{}) {
if !p.fmt.inDetail || p.fmt.plusV {
if p.fmt.indent {
Fprint((*errPPState)(p), args...)
} else {
(*pp)(p).doPrint(args)
}
}
}
func (p *errPP) Printf(format string, args ...interface{}) {
if !p.fmt.inDetail || p.fmt.plusV {
if p.fmt.indent {
Fprintf((*errPPState)(p), format, args...)
} else {
(*pp)(p).doPrintf(format, args)
}
}
}
func (p *errPP) Detail() bool {
p.fmt.indent = p.fmt.plusV
p.fmt.inDetail = true
(*errPPState)(p).Write([]byte("\n"))
return p.fmt.plusV
}