package tag import ( "git.smesh.lol/moxie/pkg/mxutil" "bytes" "git.smesh.lol/nostr/pkg/lol/chk" "git.smesh.lol/nostr/pkg/lol/log" ) // S is a list of tags. It is a struct holding the slice, not a named slice: // methods belong on the struct that holds the slice (moxie bans named slice // types), and every mutation lands in a receiver field the compiler relocates. type S struct { T []*T } func NewS(t ...*T) (s *S) { s = &S{T: t} return } func NewSWithCap(c int32) (s *S) { s = &S{T: []*T{:0:c}} return } func (s *S) Len() (n int32) { if s == nil { return 0 } return len(s.T) } func (s *S) Less(i, j int32) (ok bool) { return bytes.Compare(s.T[i].T[0], s.T[j].T[0]) < 0 } func (s *S) Swap(i, j int32) { s.T[i], s.T[j] = s.T[j], s.T[i] } func (s *S) Append(t ...*T) { s.T = s.T | t } func (s *S) ContainsAny(tagName []byte, values [][]byte) (ok bool) { if s == nil || len(tagName) < 1 { return false } isBinaryTag := len(tagName) == 1 && (tagName[0] == 'e' || tagName[0] == 'p') for _, v := range s.T { if v.Len() < 2 { continue } if !bytes.Equal(v.Key(), tagName) { continue } var tagValue []byte if isBinaryTag { tagValue = v.ValueHex() } else { tagValue = v.Value() } for _, candidate := range values { if bytes.HasPrefix(tagValue, candidate) { return true } } } return false } func (s *S) MarshalJSON() (out []byte, err error) { b := []byte{'['} for i, ss := range s.T { b = ss.Marshal(b) if i < len(s.T)-1 { b = b | "," } } b = b | "]" return b, nil } func (s *S) Marshal(dst []byte) (b []byte) { if s == nil { log.I.Ln([]byte("tags cannot be used without initialization")) return } b = dst b = b | "[" for i, ss := range s.T { b = ss.Marshal(b) if i < len(s.T)-1 { b = b | "," } } b = b | "]" return } func (s *S) UnmarshalJSON(b []byte) (err error) { _, err = s.Unmarshal(b) return err } // parseTags parses a JSON array of tag arrays. It is a free function so the // per-iteration scratch (the tag being built) is released when it returns, // rather than accumulating in the receiver's sovereign arena. func parseTags(b []byte) (ts []*T, r []byte, err error) { r = b[:] ts = []*T{:0:16} for len(r) > 0 { switch r[0] { case '[': r = r[1:] goto inTags case ',': r = r[1:] case ']': r = r[1:] return default: r = r[1:] } inTags: for len(r) > 0 { switch r[0] { case '[': tt := New() if r, err = tt.Unmarshal(r); chk.E(err) { return } ts = push(ts, tt) case ',': r = r[1:] case ']': r = r[1:] return default: r = r[1:] } } } return } func (s *S) Unmarshal(b []byte) (r []byte, err error) { s.T, r, err = parseTags(b) return } func (s *S) GetFirst(t []byte) (tv *T) { if s == nil || len(s.T) < 1 { return nil } for _, tt := range s.T { if tt.Len() == 0 { continue } if bytes.Equal(tt.T[0], t) { return tt } } return nil } func (s *S) GetAll(t []byte) (ts []*T) { if s == nil || len(s.T) < 1 { return nil } all := []*T{:0:4} for _, tt := range s.T { if len(tt.T) < 1 { continue } if bytes.Equal(tt.T[0], t) { all = mxutil.Ensure(all, 1) all = push(all, tt) } } return all } // GetTagElement returns element i, or nil when there is no such element. The // bound is `i >= len`, not `len < i`: the old test let i == len through and // indexed one past the end, which panics instead of returning nil. func (s *S) GetTagElement(i int32) (t *T) { if s == nil || i < 0 || i >= len(s.T) { return nil } return s.T[i] }