-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathformat.go
More file actions
128 lines (120 loc) · 3.09 KB
/
format.go
File metadata and controls
128 lines (120 loc) · 3.09 KB
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package xerrors
import (
"fmt"
"io"
"os"
"strconv"
"strings"
)
var errWriter io.Writer = os.Stderr
// Print writes a formatted error to stderr.
//
// If the error implements the [DetailedError] interface and returns
// a non-empty string, the returned details are added to each error
// in the chain.
//
// The formatted error can span multiple lines and always ends with
// a newline.
func Print(err error) {
buf := &strings.Builder{}
writeErr(buf, err)
errWriter.Write([]byte(buf.String()))
}
// Sprint returns a formatted error as a string.
//
// If the error implements the [DetailedError] interface and returns
// a non-empty string, the returned details are added to each error
// in the chain.
//
// The formatted error can span multiple lines and always ends with
// a newline.
func Sprint(err error) string {
buf := &strings.Builder{}
writeErr(buf, err)
return buf.String()
}
// Fprint writes a formatted error to the provided [io.Writer].
//
// If the error implements the [DetailedError] interface and returns
// a non-empty string, the returned details are added to each error
// in the chain.
//
// The formatted error can span multiple lines and always ends with
// a newline.
func Fprint(w io.Writer, err error) (int, error) {
buf := &strings.Builder{}
writeErr(buf, err)
return w.Write([]byte(buf.String()))
}
// writeErr writes a formatted error to the provided strings.Builder.
func writeErr(buf *strings.Builder, err error) {
const firstErrorPrefix = "Error: "
const previousErrorPrefix = "Previous error: "
first := true
for err != nil {
errMsg := err.Error()
errDetails := ""
if dErr, ok := err.(DetailedError); ok {
errDetails = dErr.ErrorDetails()
}
if errDetails != "" {
if first {
buf.WriteString(firstErrorPrefix)
} else {
buf.WriteString(previousErrorPrefix)
}
buf.WriteString(errMsg)
buf.WriteString("\n\t")
buf.WriteString(indent(errDetails))
if !strings.HasSuffix(errDetails, "\n") {
buf.WriteByte('\n')
}
} else {
// If an error does not contain any details, do not print
// it, except for the first one. This is to avoid printing
// every wrapped error on a single line.
if first {
buf.WriteString(firstErrorPrefix)
buf.WriteString(errMsg)
buf.WriteByte('\n')
}
}
first = false
if wErr, ok := err.(interface{ Unwrap() error }); ok {
err = wErr.Unwrap()
continue
}
break
}
}
// format is a helper function that formats a value according to the provided
// format state and verb.
func format(s fmt.State, verb rune, v any) {
f := []rune{'%'}
for _, c := range []int{'-', '+', '#', ' ', '0'} {
if s.Flag(c) {
f = append(f, rune(c))
}
}
if w, ok := s.Width(); ok {
f = append(f, []rune(strconv.Itoa(w))...)
}
if p, ok := s.Precision(); ok {
f = append(f, '.')
f = append(f, []rune(strconv.Itoa(p))...)
}
f = append(f, verb)
fmt.Fprintf(s, string(f), v)
}
// indent indents every line, except the first one, with a tab.
func indent(s string) string {
nl := strings.HasSuffix(s, "\n")
if nl {
s = s[:len(s)-1]
}
s = strings.ReplaceAll(s, "\n", "\n\t")
if nl {
s += "\n"
}
return s
}