record.gno
1.65 Kb · 70 lines
1package gnotif
2
3import (
4 "strconv"
5 "strings"
6)
7
8const recordFields = 8
9
10// record is a trigger, its target and its name as the registry stores them:
11// one string per trigger, because a tree holds a string inline and a struct
12// is one more stored object.
13type record struct {
14 Target string
15 Name string
16 T Trigger
17}
18
19func (r record) encode() string {
20 return packFields(r.Target, r.Name, r.T.Event, r.T.Filter, r.T.Param, r.T.Title, r.T.Body, r.T.Link)
21}
22
23// decodeRecord reads a string made by encode and panics on any other.
24func decodeRecord(v any) record {
25 f := unpackFields(v.(string), recordFields)
26 return record{
27 Target: f[0],
28 Name: f[1],
29 T: Trigger{Event: f[2], Filter: f[3], Param: f[4], Title: f[5], Body: f[6], Link: f[7]},
30 }
31}
32
33// packFields writes each field as its byte length, a colon and the field, so
34// a field may hold colons and digits.
35func packFields(fields ...string) string {
36 var b strings.Builder
37 for _, f := range fields {
38 b.WriteString(strconv.Itoa(len(f)) + ":" + f)
39 }
40 return b.String()
41}
42
43// unpackFields reads exactly n fields made by packFields and panics on any
44// other string.
45func unpackFields(s string, n int) []string {
46 fields := make([]string, n)
47 for i := range fields {
48 field, rest, ok := cutField(s)
49 if !ok {
50 panic("corrupt record")
51 }
52 fields[i], s = field, rest
53 }
54 if s != "" {
55 panic("corrupt record")
56 }
57 return fields
58}
59
60func cutField(s string) (field, rest string, ok bool) {
61 length, body, found := strings.Cut(s, ":")
62 if !found {
63 return "", "", false
64 }
65 n, err := strconv.Atoi(length)
66 if err != nil || n < 0 || n > len(body) {
67 return "", "", false
68 }
69 return body[:n], body[n:], true
70}