-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencoding.go
More file actions
400 lines (365 loc) · 11.1 KB
/
Copy pathencoding.go
File metadata and controls
400 lines (365 loc) · 11.1 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
package timex
import (
"database/sql/driver"
"encoding"
"encoding/binary"
"fmt"
"strings"
"time"
"go.gh.ink/toolbox/expr"
)
// timex types serialize through the encoding.TextMarshaler / TextUnmarshaler
// pair rather than format-specific interfaces. A single text representation is
// automatically reused by encoding/json, encoding/xml and TOML (BurntSushi/toml,
// pelletier/go-toml) — for both values and map keys — so one implementation
// covers those data-interchange languages. (YAML's gopkg.in/yaml.v3 uses its own
// Marshaler interface and does not consult TextMarshaler; wrap these if needed.)
var (
_ encoding.TextMarshaler = Time{}
_ encoding.TextUnmarshaler = (*Time)(nil)
_ encoding.TextMarshaler = Duration{}
_ encoding.TextUnmarshaler = (*Duration)(nil)
_ encoding.TextMarshaler = Interval{}
_ encoding.TextUnmarshaler = (*Interval)(nil)
)
// MarshalText implements encoding.TextMarshaler. Finite times use the standard
// RFC 3339 text form (delegated to time.Time); infinite times use the
// PosInfTimeStr / NegInfTimeStr sentinels.
func (t Time) MarshalText() ([]byte, error) {
switch {
case t.inf > 0:
return []byte(PosInfTimeStr), nil
case t.inf < 0:
return []byte(NegInfTimeStr), nil
default:
return t.std.MarshalText()
}
}
// UnmarshalText implements encoding.TextUnmarshaler, the inverse of MarshalText.
func (t *Time) UnmarshalText(data []byte) error {
switch string(data) {
case PosInfTimeStr:
*t = NewPosInfTime()
return nil
case NegInfTimeStr:
*t = NewNegInfTime()
return nil
default:
var std time.Time
if err := std.UnmarshalText(data); err != nil {
return err
}
*t = FromStdTime(std)
return nil
}
}
// MarshalText implements encoding.TextMarshaler. Finite durations use the
// standard "2h0m0s" text form (delegated to time.Duration); infinite durations
// use the PosInfTimeStr / NegInfTimeStr sentinels.
func (d Duration) MarshalText() ([]byte, error) {
switch {
case d.inf > 0:
return []byte(PosInfTimeStr), nil
case d.inf < 0:
return []byte(NegInfTimeStr), nil
default:
return []byte(d.std.String()), nil
}
}
// UnmarshalText implements encoding.TextUnmarshaler, the inverse of MarshalText.
func (d *Duration) UnmarshalText(data []byte) error {
switch string(data) {
case PosInfTimeStr:
*d = NewPosInfDuration()
return nil
case NegInfTimeStr:
*d = NewNegInfDuration()
return nil
default:
std, err := time.ParseDuration(string(data))
if err != nil {
return err
}
*d = FromStdDuration(std)
return nil
}
}
// MarshalText implements encoding.TextMarshaler using mathematical interval
// notation: a "[" / "(" bound, the start, a comma, the end, and a "]" / ")"
// bound — e.g. "[2024-01-01T00:00:00Z,2024-04-01T00:00:00Z)". Brackets encode
// inclusivity ("[" / "]" included, "(" / ")" excluded) and each endpoint reuses
// Time's own text form, so infinite endpoints render as their sentinels.
func (i Interval) MarshalText() ([]byte, error) {
start, err := i.start.MarshalText()
if err != nil {
return nil, err
}
end, err := i.end.MarshalText()
if err != nil {
return nil, err
}
var b strings.Builder
b.WriteString(expr.Ternary(i.startIncluded, "[", "("))
b.Write(start)
b.WriteByte(',')
b.Write(end)
b.WriteString(expr.Ternary(i.endIncluded, "]", ")"))
return []byte(b.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler, the inverse of MarshalText.
// Whitespace around the endpoints is tolerated. Neither RFC 3339 nor the
// infinity sentinels contain a comma, so the first comma always separates the
// two endpoints.
func (i *Interval) UnmarshalText(data []byte) error {
s := strings.TrimSpace(string(data))
if len(s) < 2 {
return ErrInvalidInterval
}
var startIncluded, endIncluded bool
switch s[0] {
case '[':
startIncluded = true
case '(':
startIncluded = false
default:
return ErrInvalidInterval
}
switch s[len(s)-1] {
case ']':
endIncluded = true
case ')':
endIncluded = false
default:
return ErrInvalidInterval
}
body := s[1 : len(s)-1]
comma := strings.IndexByte(body, ',')
if comma < 0 {
return ErrInvalidInterval
}
var start, end Time
if err := start.UnmarshalText([]byte(strings.TrimSpace(body[:comma]))); err != nil {
return err
}
if err := end.UnmarshalText([]byte(strings.TrimSpace(body[comma+1:]))); err != nil {
return err
}
*i = Interval{
start: start,
startIncluded: startIncluded,
end: end,
endIncluded: endIncluded,
}
return nil
}
// timex types also implement encoding.BinaryMarshaler / BinaryUnmarshaler. This
// single pair is reused by encoding/gob (which prefers it over reflection when
// present) and any other codec built on those interfaces, giving a compact,
// exact, non-textual round-trip. Every layout begins with the InfFlag byte, so
// infinite values encode in a single byte.
var (
_ encoding.BinaryMarshaler = Time{}
_ encoding.BinaryUnmarshaler = (*Time)(nil)
_ encoding.BinaryMarshaler = Duration{}
_ encoding.BinaryUnmarshaler = (*Duration)(nil)
_ encoding.BinaryMarshaler = Interval{}
_ encoding.BinaryUnmarshaler = (*Interval)(nil)
)
// MarshalBinary implements encoding.BinaryMarshaler. The first byte holds the
// InfFlag; a finite time appends time.Time's own binary form, while an infinite
// time needs no further bytes.
func (t Time) MarshalBinary() ([]byte, error) {
if t.inf != FiniteTime {
return []byte{byte(t.inf)}, nil
}
std, err := t.std.MarshalBinary()
if err != nil {
return nil, err
}
return append([]byte{byte(t.inf)}, std...), nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler, the inverse of MarshalBinary.
func (t *Time) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return ErrInvalidBinary
}
switch InfFlag(int8(data[0])) {
case PosInfTime:
*t = NewPosInfTime()
return nil
case NegInfTime:
*t = NewNegInfTime()
return nil
case FiniteTime:
var std time.Time
if err := std.UnmarshalBinary(data[1:]); err != nil {
return err
}
*t = FromStdTime(std)
return nil
default:
return ErrInvalidBinary
}
}
// MarshalBinary implements encoding.BinaryMarshaler. The first byte holds the
// InfFlag; a finite duration appends its int64 nanoseconds (big-endian), while
// an infinite duration needs no further bytes.
func (d Duration) MarshalBinary() ([]byte, error) {
if d.inf != FiniteTime {
return []byte{byte(d.inf)}, nil
}
buf := make([]byte, 1+8)
buf[0] = byte(d.inf)
binary.BigEndian.PutUint64(buf[1:], uint64(d.std))
return buf, nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler, the inverse of MarshalBinary.
func (d *Duration) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return ErrInvalidBinary
}
switch InfFlag(int8(data[0])) {
case PosInfTime:
*d = NewPosInfDuration()
return nil
case NegInfTime:
*d = NewNegInfDuration()
return nil
case FiniteTime:
if len(data) != 1+8 {
return ErrInvalidBinary
}
*d = FromStdDuration(time.Duration(binary.BigEndian.Uint64(data[1:])))
return nil
default:
return ErrInvalidBinary
}
}
// MarshalBinary implements encoding.BinaryMarshaler. Layout: a flags byte (bit 0
// startIncluded, bit 1 endIncluded), the uvarint length of the start endpoint's
// binary form, that start form, then the end endpoint's binary form (the
// remaining bytes). Both endpoints reuse Time's own binary encoding.
func (i Interval) MarshalBinary() ([]byte, error) {
start, err := i.start.MarshalBinary()
if err != nil {
return nil, err
}
end, err := i.end.MarshalBinary()
if err != nil {
return nil, err
}
var flags byte
if i.startIncluded {
flags |= 1
}
if i.endIncluded {
flags |= 2
}
buf := make([]byte, 0, 1+binary.MaxVarintLen64+len(start)+len(end))
buf = append(buf, flags)
buf = binary.AppendUvarint(buf, uint64(len(start)))
buf = append(buf, start...)
buf = append(buf, end...)
return buf, nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler, the inverse of MarshalBinary.
func (i *Interval) UnmarshalBinary(data []byte) error {
if len(data) < 1 {
return ErrInvalidBinary
}
flags := data[0]
rest := data[1:]
n, read := binary.Uvarint(rest)
if read <= 0 {
return ErrInvalidBinary
}
rest = rest[read:]
if uint64(len(rest)) < n {
return ErrInvalidBinary
}
startBin, endBin := rest[:n], rest[n:]
var start, end Time
if err := start.UnmarshalBinary(startBin); err != nil {
return err
}
if err := end.UnmarshalBinary(endBin); err != nil {
return err
}
*i = Interval{
start: start,
startIncluded: flags&1 != 0,
end: end,
endIncluded: flags&2 != 0,
}
return nil
}
// timex types implement database/sql/driver.Valuer and database/sql.Scanner,
// round-tripping through their text form, so they work as column values with
// database/sql and ORMs built on it (xorm, gorm, sqlx, ...) without an
// app-level adapter. A text/varchar column preserves everything — infinite
// bounds and interval inclusivity included. Scan accepts the string or []byte a
// driver yields for a text column and maps SQL NULL to the zero value.
//
// The Scanner side is asserted against an inline interface so importing timex
// does not pull all of database/sql into binaries that never touch a database;
// only the lightweight database/sql/driver package is required (for Value).
var (
_ driver.Valuer = Time{}
_ driver.Valuer = Duration{}
_ driver.Valuer = Interval{}
_ interface{ Scan(any) error } = (*Time)(nil)
_ interface{ Scan(any) error } = (*Duration)(nil)
_ interface{ Scan(any) error } = (*Interval)(nil)
)
// textValue is the shared driver.Valuer body: marshal to the canonical text form
// and hand the driver a string, i.e. a text/varchar column value.
func textValue(m encoding.TextMarshaler) (driver.Value, error) {
b, err := m.MarshalText()
if err != nil {
return nil, err
}
return string(b), nil
}
// scanText is the shared sql.Scanner body for a non-NULL src: it forwards the
// string / []byte a driver returns for a text column to u.UnmarshalText. NULL is
// handled by each Scan method (reset to the zero value) before calling this.
func scanText(u encoding.TextUnmarshaler, src any) error {
switch v := src.(type) {
case string:
return u.UnmarshalText([]byte(v))
case []byte:
return u.UnmarshalText(v)
default:
return fmt.Errorf("timex: cannot scan %T into %T", src, u)
}
}
// Value implements database/sql/driver.Valuer.
func (t Time) Value() (driver.Value, error) { return textValue(t) }
// Scan implements database/sql.Scanner. A NULL column yields the zero Time.
func (t *Time) Scan(src any) error {
if src == nil {
*t = Time{}
return nil
}
return scanText(t, src)
}
// Value implements database/sql/driver.Valuer.
func (d Duration) Value() (driver.Value, error) { return textValue(d) }
// Scan implements database/sql.Scanner. A NULL column yields the zero Duration.
func (d *Duration) Scan(src any) error {
if src == nil {
*d = Duration{}
return nil
}
return scanText(d, src)
}
// Value implements database/sql/driver.Valuer.
func (i Interval) Value() (driver.Value, error) { return textValue(i) }
// Scan implements database/sql.Scanner. A NULL column yields the zero Interval.
func (i *Interval) Scan(src any) error {
if src == nil {
*i = Interval{}
return nil
}
return scanText(i, src)
}