-
-
Notifications
You must be signed in to change notification settings - Fork 143
/
error.go
142 lines (122 loc) · 3.1 KB
/
error.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
package core
import (
"bufio"
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/pterm/pterm"
)
type lineError struct {
content string
line int
span []int
}
type errorCondition func(position int, line, target string) bool
func annotate(file []byte, target string, finder errorCondition) (lineError, error) {
var sb strings.Builder
scanner := bufio.NewScanner(bytes.NewBuffer(file))
context := lineError{span: []int{1, 1}}
idx := 1
for scanner.Scan() {
markup := scanner.Text()
plain := StripANSI(markup)
if idx-context.line > 2 && context.line != 0 { //nolint:gocritic
break
} else if finder(idx, plain, target) && context.line == 0 {
context.line = idx
s := strings.Index(plain, target) + 1
context.span = []int{s, s + len(target)}
sb.WriteString(
fmt.Sprintf("\033[1;32m%4d\033[0m* %s\n", idx, markup))
} else {
sb.WriteString(
fmt.Sprintf("\033[1;32m%4d\033[0m %s\n", idx, markup))
}
idx++
}
if err := scanner.Err(); err != nil {
return lineError{}, err
}
lines := []string{}
for i, l := range strings.Split(sb.String(), "\n") {
if context.line-i < 3 {
lines = append(lines, l)
}
}
context.content = strings.Join(lines, "\n")
return context, nil
}
// NewError creates a colored error from the given information.
//
// # The standard format is
//
// ```
// <code> [<context>] <title>
//
// <msg>
// ```
func NewError(code, title, msg string) error {
return fmt.Errorf(
"%s %s\n\n%s\n\n%s",
pterm.BgRed.Sprintf(code),
title,
msg,
pterm.Fuzzy.Sprint(pterm.Italic.Sprintf("Execution stopped with code 1.")),
)
}
// NewE100 creates a new, formatted "unexpected" error.
//
// Since E100 errors can occur anywhere, we include a "context" that makes it
// clear where exactly the error was generated.
func NewE100(context string, err error) error {
title := fmt.Sprintf("[%s] %s", context, "Runtime error")
return NewError("E100", title, err.Error())
}
// NewE201 creates a formatted user-generated error.
//
// 201 errors involve a specific configuration asset and should contain
// parsable location information on their last line of the form:
//
// <path>:<line>:<start>:<end>
func NewE201(msg, value, path string, finder errorCondition) error {
f, err := os.ReadFile(path)
if err != nil {
return NewE100("NewE201", errors.New(msg))
}
ctx, err := annotate(f, value, finder)
if err != nil {
return NewE100("NewE201/annotate", err)
}
title := fmt.Sprintf(
"Invalid value [%s:%d:%d]:",
filepath.ToSlash(path),
ctx.line,
ctx.span[0])
return NewError(
"E201",
title,
fmt.Sprintf("%s\n%s", ctx.content, msg))
}
// NewE201FromTarget creates a new E201 error from a target string.
func NewE201FromTarget(msg, value, file string) error {
return NewE201(
msg,
value,
file,
func(position int, line, target string) bool {
return strings.Contains(line, target)
})
}
// NewE201FromPosition creates a new E201 error from an in-file location.
func NewE201FromPosition(msg, file string, goal int) error {
return NewE201(
msg,
"",
file,
func(position int, line, target string) bool {
return position == goal
})
}