1package helpers
2
3import (
4	"bytes"
5	"strings"
6)
7
8// This provides an efficient way to join lots of big string and byte slices
9// together. It avoids the cost of repeatedly reallocating as the buffer grows
10// by measuring exactly how big the buffer should be and then allocating once.
11// This is a measurable speedup.
12type Joiner struct {
13	lastByte byte
14	strings  []joinerString
15	bytes    []joinerBytes
16	length   uint32
17}
18
19type joinerString struct {
20	data   string
21	offset uint32
22}
23
24type joinerBytes struct {
25	data   []byte
26	offset uint32
27}
28
29func (j *Joiner) AddString(data string) {
30	if len(data) > 0 {
31		j.lastByte = data[len(data)-1]
32	}
33	j.strings = append(j.strings, joinerString{data, j.length})
34	j.length += uint32(len(data))
35}
36
37func (j *Joiner) AddBytes(data []byte) {
38	if len(data) > 0 {
39		j.lastByte = data[len(data)-1]
40	}
41	j.bytes = append(j.bytes, joinerBytes{data, j.length})
42	j.length += uint32(len(data))
43}
44
45func (j *Joiner) LastByte() byte {
46	return j.lastByte
47}
48
49func (j *Joiner) Length() uint32 {
50	return j.length
51}
52
53func (j *Joiner) EnsureNewlineAtEnd() {
54	if j.length > 0 && j.lastByte != '\n' {
55		j.AddString("\n")
56	}
57}
58
59func (j *Joiner) Done() []byte {
60	if len(j.strings) == 0 && len(j.bytes) == 1 && j.bytes[0].offset == 0 {
61		// No need to allocate if there was only a single byte array written
62		return j.bytes[0].data
63	}
64	buffer := make([]byte, j.length)
65	for _, item := range j.strings {
66		copy(buffer[item.offset:], item.data)
67	}
68	for _, item := range j.bytes {
69		copy(buffer[item.offset:], item.data)
70	}
71	return buffer
72}
73
74func (j *Joiner) Contains(s string, b []byte) bool {
75	for _, item := range j.strings {
76		if strings.Contains(item.data, s) {
77			return true
78		}
79	}
80	for _, item := range j.bytes {
81		if bytes.Contains(item.data, b) {
82			return true
83		}
84	}
85	return false
86}
87