-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
strings.go
313 lines (252 loc) · 8.69 KB
/
strings.go
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
package utils
import (
crand "crypto/rand"
"fmt"
"math/rand"
"net/url"
"strconv"
"strings"
"time"
"unicode"
"github.com/valyala/fasthttp"
)
// IsStringAbsURL checks a string can be parsed as a URL and that is IsAbs and if it can't it returns an error
// describing why.
func IsStringAbsURL(input string) (err error) {
parsedURL, err := url.Parse(input)
if err != nil {
return fmt.Errorf("could not parse '%s' as a URL", input)
}
if !parsedURL.IsAbs() {
return fmt.Errorf("the url '%s' is not absolute because it doesn't start with a scheme like 'http://' or 'https://'", input)
}
return nil
}
// IsStringAlphaNumeric returns false if any rune in the string is not alpha-numeric.
func IsStringAlphaNumeric(input string) bool {
for _, r := range input {
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
return false
}
}
return true
}
// IsStringInSlice checks if a single string is in a slice of strings.
func IsStringInSlice(needle string, haystack []string) (inSlice bool) {
for _, b := range haystack {
if b == needle {
return true
}
}
return false
}
// IsStringInSliceSuffix checks if the needle string has one of the suffixes in the haystack.
func IsStringInSliceSuffix(needle string, haystack []string) (hasSuffix bool) {
for _, straw := range haystack {
if strings.HasSuffix(needle, straw) {
return true
}
}
return false
}
// IsStringInSliceFold checks if a single string is in a slice of strings but uses strings.EqualFold to compare them.
func IsStringInSliceFold(needle string, haystack []string) (inSlice bool) {
for _, b := range haystack {
if strings.EqualFold(b, needle) {
return true
}
}
return false
}
// IsStringInSliceContains checks if a single string is in an array of strings.
func IsStringInSliceContains(needle string, haystack []string) (inSlice bool) {
for _, b := range haystack {
if strings.Contains(needle, b) {
return true
}
}
return false
}
// IsStringSliceContainsAll checks if the haystack contains all strings in the needles.
func IsStringSliceContainsAll(needles []string, haystack []string) (inSlice bool) {
for _, n := range needles {
if !IsStringInSlice(n, haystack) {
return false
}
}
return true
}
// IsStringSliceContainsAny checks if the haystack contains any of the strings in the needles.
func IsStringSliceContainsAny(needles []string, haystack []string) (inSlice bool) {
for _, n := range needles {
if IsStringInSlice(n, haystack) {
return true
}
}
return false
}
// SliceString splits a string s into an array with each item being a max of int d
// d = denominator, n = numerator, q = quotient, r = remainder.
func SliceString(s string, d int) (array []string) {
n := len(s)
q := n / d
r := n % d
for i := 0; i < q; i++ {
array = append(array, s[i*d:i*d+d])
if i+1 == q && r != 0 {
array = append(array, s[i*d+d:])
}
}
return
}
func isStringSlicesDifferent(a, b []string, method func(s string, b []string) bool) (different bool) {
if len(a) != len(b) {
return true
}
for _, s := range a {
if !method(s, b) {
return true
}
}
return false
}
// IsStringSlicesDifferent checks two slices of strings and on the first occurrence of a string item not existing in the
// other slice returns true, otherwise returns false.
func IsStringSlicesDifferent(a, b []string) (different bool) {
return isStringSlicesDifferent(a, b, IsStringInSlice)
}
// IsStringSlicesDifferentFold checks two slices of strings and on the first occurrence of a string item not existing in
// the other slice (case insensitive) returns true, otherwise returns false.
func IsStringSlicesDifferentFold(a, b []string) (different bool) {
return isStringSlicesDifferent(a, b, IsStringInSliceFold)
}
// IsURLInSlice returns true if the needle url.URL is in the []url.URL haystack.
func IsURLInSlice(needle url.URL, haystack []url.URL) (has bool) {
for i := 0; i < len(haystack); i++ {
if strings.EqualFold(needle.String(), haystack[i].String()) {
return true
}
}
return false
}
// StringSliceFromURLs returns a []string from a []url.URL.
func StringSliceFromURLs(urls []url.URL) []string {
result := make([]string, len(urls))
for i := 0; i < len(urls); i++ {
result[i] = urls[i].String()
}
return result
}
// URLsFromStringSlice returns a []url.URL from a []string.
func URLsFromStringSlice(urls []string) []url.URL {
var result []url.URL
for i := 0; i < len(urls); i++ {
u, err := url.Parse(urls[i])
if err != nil {
continue
}
result = append(result, *u)
}
return result
}
// OriginFromURL returns an origin url.URL given another url.URL.
func OriginFromURL(u url.URL) (origin url.URL) {
return url.URL{
Scheme: u.Scheme,
Host: u.Host,
}
}
// StringSlicesDelta takes a before and after []string and compares them returning a added and removed []string.
func StringSlicesDelta(before, after []string) (added, removed []string) {
for _, s := range before {
if !IsStringInSlice(s, after) {
removed = append(removed, s)
}
}
for _, s := range after {
if !IsStringInSlice(s, before) {
added = append(added, s)
}
}
return added, removed
}
// RandomString returns a random string with a given length with values from the provided characters. When crypto is set
// to false we use math/rand and when it's set to true we use crypto/rand. The crypto option should always be set to true
// excluding when the task is time sensitive and would not benefit from extra randomness.
func RandomString(n int, characters string, crypto bool) (randomString string) {
return string(RandomBytes(n, characters, crypto))
}
// RandomBytes returns a random []byte with a given length with values from the provided characters. When crypto is set
// to false we use math/rand and when it's set to true we use crypto/rand. The crypto option should always be set to true
// excluding when the task is time sensitive and would not benefit from extra randomness.
func RandomBytes(n int, characters string, crypto bool) (bytes []byte) {
bytes = make([]byte, n)
if crypto {
_, _ = crand.Read(bytes)
} else {
_, _ = rand.Read(bytes) //nolint:gosec // As this is an option when using this function it's not necessary to be concerned about this.
}
for i, b := range bytes {
bytes[i] = characters[b%byte(len(characters))]
}
return bytes
}
// StringHTMLEscape escapes chars for a HTML body.
func StringHTMLEscape(input string) (output string) {
return htmlEscaper.Replace(input)
}
// StringJoinDelimitedEscaped joins a string with a specified rune delimiter after escaping any instance of that string
// in the string slice. Used with StringSplitDelimitedEscaped.
func StringJoinDelimitedEscaped(value []string, delimiter rune) string {
escaped := make([]string, len(value))
for k, v := range value {
escaped[k] = strings.ReplaceAll(v, string(delimiter), "\\"+string(delimiter))
}
return strings.Join(escaped, string(delimiter))
}
// StringSplitDelimitedEscaped splits a string with a specified rune delimiter after unescaping any instance of that
// string in the string slice that has been escaped. Used with StringJoinDelimitedEscaped.
func StringSplitDelimitedEscaped(value string, delimiter rune) (out []string) {
var escape bool
split := strings.FieldsFunc(value, func(r rune) bool {
if r == '\\' {
escape = !escape
} else if escape && r != delimiter {
escape = false
}
return !escape && r == delimiter
})
for k, v := range split {
split[k] = strings.ReplaceAll(v, "\\"+string(delimiter), string(delimiter))
}
return split
}
// JoinAndCanonicalizeHeaders join header strings by a given sep.
func JoinAndCanonicalizeHeaders(sep []byte, headers ...string) (joined []byte) {
for i, header := range headers {
if i != 0 {
joined = append(joined, sep...)
}
joined = fasthttp.AppendNormalizedHeaderKey(joined, header)
}
return joined
}
// IsURLHostComponent returns true if the provided url.URL that was parsed from a string to a url.URL via url.Parse is
// just a hostname. This is needed because of the way this function parses such strings.
func IsURLHostComponent(u url.URL) (isHostComponent bool) {
return u.Path != "" && u.Scheme == "" && u.Host == "" && u.RawPath == "" && u.Opaque == "" &&
u.RawQuery == "" && u.Fragment == "" && u.RawFragment == ""
}
// IsURLHostComponentWithPort returns true if the provided url.URL that was parsed from a string to a url.URL via
// url.Parse is just a hostname with a port. This is needed because of the way this function parses such strings.
func IsURLHostComponentWithPort(u url.URL) (isHostComponentWithPort bool) {
if u.Opaque != "" && u.Scheme != "" && u.Host == "" && u.Path == "" && u.RawPath == "" &&
u.RawQuery == "" && u.Fragment == "" && u.RawFragment == "" {
_, err := strconv.Atoi(u.Opaque)
return err == nil
}
return false
}
func init() {
rand.Seed(time.Now().UnixNano())
}