tags.mx raw
1 package tag
2
3 import (
4 "git.smesh.lol/moxie/pkg/mxutil"
5 "bytes"
6
7 "git.smesh.lol/nostr/pkg/lol/chk"
8 "git.smesh.lol/nostr/pkg/lol/log"
9 )
10
11 // S is a list of tags. It is a struct holding the slice, not a named slice:
12 // methods belong on the struct that holds the slice (moxie bans named slice
13 // types), and every mutation lands in a receiver field the compiler relocates.
14 type S struct {
15 T []*T
16 }
17
18 func NewS(t ...*T) (s *S) {
19 s = &S{T: t}
20 return
21 }
22
23 func NewSWithCap(c int32) (s *S) {
24 s = &S{T: []*T{:0:c}}
25 return
26 }
27
28 func (s *S) Len() (n int32) {
29 if s == nil {
30 return 0
31 }
32 return len(s.T)
33 }
34
35 func (s *S) Less(i, j int32) (ok bool) {
36 return bytes.Compare(s.T[i].T[0], s.T[j].T[0]) < 0
37 }
38
39 func (s *S) Swap(i, j int32) {
40 s.T[i], s.T[j] = s.T[j], s.T[i]
41 }
42
43 func (s *S) Append(t ...*T) {
44 s.T = s.T | t
45 }
46
47 func (s *S) ContainsAny(tagName []byte, values [][]byte) (ok bool) {
48 if s == nil || len(tagName) < 1 {
49 return false
50 }
51 isBinaryTag := len(tagName) == 1 && (tagName[0] == 'e' || tagName[0] == 'p')
52 for _, v := range s.T {
53 if v.Len() < 2 {
54 continue
55 }
56 if !bytes.Equal(v.Key(), tagName) {
57 continue
58 }
59 var tagValue []byte
60 if isBinaryTag {
61 tagValue = v.ValueHex()
62 } else {
63 tagValue = v.Value()
64 }
65 for _, candidate := range values {
66 if bytes.HasPrefix(tagValue, candidate) {
67 return true
68 }
69 }
70 }
71 return false
72 }
73
74 func (s *S) MarshalJSON() (out []byte, err error) {
75 b := []byte{'['}
76 for i, ss := range s.T {
77 b = ss.Marshal(b)
78 if i < len(s.T)-1 {
79 b = b | ","
80 }
81 }
82 b = b | "]"
83 return b, nil
84 }
85
86 func (s *S) Marshal(dst []byte) (b []byte) {
87 if s == nil {
88 log.I.Ln([]byte("tags cannot be used without initialization"))
89 return
90 }
91 b = dst
92 b = b | "["
93 for i, ss := range s.T {
94 b = ss.Marshal(b)
95 if i < len(s.T)-1 {
96 b = b | ","
97 }
98 }
99 b = b | "]"
100 return
101 }
102
103 func (s *S) UnmarshalJSON(b []byte) (err error) {
104 _, err = s.Unmarshal(b)
105 return err
106 }
107
108 // parseTags parses a JSON array of tag arrays. It is a free function so the
109 // per-iteration scratch (the tag being built) is released when it returns,
110 // rather than accumulating in the receiver's sovereign arena.
111 func parseTags(b []byte) (ts []*T, r []byte, err error) {
112 r = b[:]
113 ts = []*T{:0:16}
114 for len(r) > 0 {
115 switch r[0] {
116 case '[':
117 r = r[1:]
118 goto inTags
119 case ',':
120 r = r[1:]
121 case ']':
122 r = r[1:]
123 return
124 default:
125 r = r[1:]
126 }
127 inTags:
128 for len(r) > 0 {
129 switch r[0] {
130 case '[':
131 tt := New()
132 if r, err = tt.Unmarshal(r); chk.E(err) {
133 return
134 }
135 ts = push(ts, tt)
136 case ',':
137 r = r[1:]
138 case ']':
139 r = r[1:]
140 return
141 default:
142 r = r[1:]
143 }
144 }
145 }
146 return
147 }
148
149 func (s *S) Unmarshal(b []byte) (r []byte, err error) {
150 s.T, r, err = parseTags(b)
151 return
152 }
153
154 func (s *S) GetFirst(t []byte) (tv *T) {
155 if s == nil || len(s.T) < 1 {
156 return nil
157 }
158 for _, tt := range s.T {
159 if tt.Len() == 0 {
160 continue
161 }
162 if bytes.Equal(tt.T[0], t) {
163 return tt
164 }
165 }
166 return nil
167 }
168
169 func (s *S) GetAll(t []byte) (ts []*T) {
170 if s == nil || len(s.T) < 1 {
171 return nil
172 }
173 all := []*T{:0:4}
174 for _, tt := range s.T {
175 if len(tt.T) < 1 {
176 continue
177 }
178 if bytes.Equal(tt.T[0], t) {
179 all = mxutil.Ensure(all, 1)
180 all = push(all, tt)
181 }
182 }
183 return all
184 }
185
186 // GetTagElement returns element i, or nil when there is no such element. The
187 // bound is `i >= len`, not `len < i`: the old test let i == len through and
188 // indexed one past the end, which panics instead of returning nil.
189 func (s *S) GetTagElement(i int32) (t *T) {
190 if s == nil || i < 0 || i >= len(s.T) {
191 return nil
192 }
193 return s.T[i]
194 }
195