forked from ztrue/tracerr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
print.go
182 lines (166 loc) · 4.16 KB
/
print.go
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package tracerr
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
)
// DefaultLinesAfter is number of source lines after traced line to display.
var DefaultLinesAfter = 2
// DefaultLinesBefore is number of source lines before traced line to display.
var DefaultLinesBefore = 3
var cache = map[string][]string{}
var mutex sync.RWMutex
// Print prints error message with stack trace.
func Print(err error) {
fmt.Println(Sprint(err))
}
// PrintSource prints error message with stack trace and source fragments.
//
// By default, 6 lines of source code will be printed,
// see DefaultLinesAfter and DefaultLinesBefore.
//
// Pass a single number to specify a total number of source lines.
//
// Pass two numbers to specify exactly how many lines should be shown
// before and after traced line.
func PrintSource(err error, nums ...int) {
fmt.Println(SprintSource(err, nums...))
}
// PrintSourceColor prints error message with stack trace and source fragments,
// which are in color.
// Output rules are the same as in PrintSource.
func PrintSourceColor(err error, nums ...int) {
fmt.Println(SprintSourceColor(err, nums...))
}
// Sprint returns error output by the same rules as Print.
func Sprint(err error) string {
return sprint(err, []int{0}, false)
}
// SprintSource returns error output by the same rules as PrintSource.
func SprintSource(err error, nums ...int) string {
return sprint(err, nums, false)
}
// SprintSourceColor returns error output by the same rules as PrintSourceColor.
func SprintSourceColor(err error, nums ...int) string {
return sprint(err, nums, true)
}
func calcRows(nums []int) (before, after int, withSource bool) {
before = DefaultLinesBefore
after = DefaultLinesAfter
withSource = true
if len(nums) > 1 {
before = nums[0]
after = nums[1]
withSource = true
} else if len(nums) == 1 {
if nums[0] > 0 {
// Extra line goes to "before" rather than "after".
after = (nums[0] - 1) / 2
before = nums[0] - after - 1
} else {
after = 0
before = 0
withSource = false
}
}
if before < 0 {
before = 0
}
if after < 0 {
after = 0
}
return before, after, withSource
}
func readLines(path string) ([]string, error) {
mutex.RLock()
lines, ok := cache[path]
mutex.RUnlock()
if ok {
return lines, nil
}
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("tracerr: file %s not found", path)
}
lines = strings.Split(string(b), "\n")
mutex.Lock()
defer mutex.Unlock()
cache[path] = lines
return lines, nil
}
func sourceRows(rows []string, frame Frame, before, after int, colorized bool) []string {
lines, err := readLines(frame.Path)
if err != nil {
message := err.Error()
if colorized {
message = yellow(message)
}
return append(rows, message, "")
}
if len(lines) < frame.Line {
message := fmt.Sprintf(
"tracerr: too few lines, got %d, want %d",
len(lines), frame.Line,
)
if colorized {
message = yellow(message)
}
return append(rows, message, "")
}
current := frame.Line - 1
start := current - before
end := current + after
for i := start; i <= end; i++ {
if i < 0 || i >= len(lines) {
continue
}
line := lines[i]
var message string
// TODO Pad to the same length.
if i == frame.Line-1 {
message = fmt.Sprintf("%d\t%s", i+1, line)
if colorized {
message = red(message)
}
} else if colorized {
message = fmt.Sprintf("%s\t%s", black(strconv.Itoa(i+1)), line)
} else {
message = fmt.Sprintf("%d\t%s", i+1, line)
}
rows = append(rows, message)
}
return append(rows, "")
}
func sprint(err error, nums []int, colorized bool) string {
if err == nil {
return ""
}
e, ok := err.(Error)
if !ok {
return err.Error()
}
before, after, withSource := calcRows(nums)
frames := e.StackTrace()
expectedRows := len(frames) + 1
if withSource {
expectedRows = (before+after+3)*len(frames) + 2
}
rows := make([]string, 0, expectedRows)
rows = append(rows, e.Error())
if withSource {
rows = append(rows, "")
}
for _, frame := range frames {
message := frame.String()
if colorized {
message = bold(message)
}
rows = append(rows, message)
if withSource {
rows = sourceRows(rows, frame, before, after, colorized)
}
}
return strings.Join(rows, "\n")
}