mirror of
https://github.com/make-42/xyosc
synced 2025-01-18 18:57:10 +01:00
183 lines
4.2 KiB
Go
183 lines
4.2 KiB
Go
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")
|
|
}
|