-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
179 lines (154 loc) · 5.53 KB
/
Copy patherrors.go
File metadata and controls
179 lines (154 loc) · 5.53 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
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
// Copyright (c) 2026 Z5Labs and Contributors
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package dfcad
import (
"errors"
"fmt"
"io/fs"
"os"
sexpr "github.com/z5labs/sexpr-go"
)
// EncodingError reports a file that is not valid UTF-8.
//
// The position is the first offending byte rather than the file as a whole,
// because "this file is not UTF-8" is not something anybody can act on and
// "byte 0xff at line 12, column 3" is.
type EncodingError struct {
// Position is the first byte that is not part of a valid encoding.
Position Position
// Byte is that byte.
Byte byte
}
// Error implements the [error] interface.
func (e EncodingError) Error() string {
return fmt.Sprintf("%s: %s", e.Position, e.message())
}
// message is the failure without the position in front of it, which is what a
// diagnostic carries: a diagnostic holds its span as a field and renders the
// position itself, so a message repeating it would print it twice.
func (e EncodingError) message() string {
return fmt.Sprintf("invalid UTF-8: byte %#02x begins no valid encoding", e.Byte)
}
// ByteOrderMarkError reports a file beginning with a UTF-8 byte order mark.
//
// A mark is a load error rather than something to skip. UTF-8 needs none, it
// is invisible in every editor that would have to remove it, and accepting one
// puts every byte offset in the file three bytes out for anything reading it
// outside the loader.
type ByteOrderMarkError struct {
// Position is the first byte of the mark, which is the first byte of the
// file.
Position Position
}
// Error implements the [error] interface.
func (e ByteOrderMarkError) Error() string {
return fmt.Sprintf("%s: %s", e.Position, e.message())
}
// message is the failure without the position in front of it.
func (e ByteOrderMarkError) message() string {
return "file begins with a UTF-8 byte order mark, which must be removed"
}
// ParseError reports a failure from the underlying S-expression tokenizer or
// parser, placed in a file.
//
// The underlying error already knows the line and the column; what it cannot
// know is which file it was reading or where that lands in the bytes. Err is
// kept rather than flattened into a message so that errors.As still reaches
// the tokenizer's and the parser's own types.
type ParseError struct {
// Position is where the failure was reported.
Position Position
// Err is the failure the tokenizer or the parser reported.
Err error
}
// Error implements the [error] interface.
func (e ParseError) Error() string {
return fmt.Sprintf("%s: %s", e.Position, e.message())
}
// message is the failure without the position in front of it.
func (e ParseError) message() string {
return e.Err.Error()
}
// Unwrap returns the underlying failure.
func (e ParseError) Unwrap() error {
return e.Err
}
// WriteError reports a file that could not be replaced.
//
// The path is carried because the failure the operating system reports names
// the temporary file being written rather than the file being replaced, and
// the temporary one is not a name anybody asked for or can act on.
type WriteError struct {
// Path is the file that was to be replaced.
Path string
// Err is the failure that stopped it.
Err error
}
// Error implements the [error] interface.
func (e WriteError) Error() string {
return fmt.Sprintf("%s: %s", e.Path, cause(e.Err))
}
// cause is a file system failure with the name of the file it names taken out
// of it.
//
// The operating system reports a failed replacement against whichever file the
// call it refused named, which for every step of a replacement but the last is
// a temporary one — a name nobody asked for and nobody can act on. An error
// which carries the path it is about and then prints that name beside it puts
// two paths in one message, one of which is noise.
//
// What is worth keeping is the operation and the reason, because which step
// failed says whether the trouble is with the directory, the device or the
// target: "open: permission denied" and "rename: invalid cross-device link" are
// different problems.
func cause(err error) string {
var path *fs.PathError
if errors.As(err, &path) {
return fmt.Sprintf("%s: %v", path.Op, path.Err)
}
// Renaming names two files, so it reports its own type rather than a
// PathError.
var link *os.LinkError
if errors.As(err, &link) {
return fmt.Sprintf("%s: %v", link.Op, link.Err)
}
return err.Error()
}
// Unwrap returns the underlying failure.
func (e WriteError) Unwrap() error {
return e.Err
}
// parseError places an error from the underlying tokenizer or parser in a file.
//
// Every failure either of them reports carries a position, and the switch below
// is how each spells it. A failure that somehow carries none is reported at the
// start of the file: a position that is merely coarse is still a position, and
// one that is absent is a diagnostic nobody can follow.
func parseError(lines lineIndex, err error) ParseError {
var pos sexpr.Pos
switch err := err.(type) {
case sexpr.InvalidEscapeError:
pos = err.Pos
case sexpr.InvalidNumberError:
pos = err.Pos
case sexpr.MaxDepthExceededError:
pos = err.Pos
case sexpr.NumberRangeError:
pos = err.Pos
case sexpr.UnexpectedCharacterError:
pos = err.Pos
case sexpr.UnexpectedEndOfTokensError:
pos = err.Pos
case sexpr.UnexpectedTokenError:
pos = err.Actual.Pos
case sexpr.UnterminatedCommentError:
pos = err.Pos
case sexpr.UnterminatedStringError:
pos = err.Pos
default:
pos = sexpr.Pos{Line: 1, Column: 1}
}
return ParseError{Position: lines.position(pos), Err: err}
}