-
Notifications
You must be signed in to change notification settings - Fork 0
/
extended.go
347 lines (302 loc) · 8.42 KB
/
extended.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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// Package StringExt provides a set of functions that extend the functionality of
// the built-in string type.
package StringExt
import (
"crypto/rand"
"encoding/hex"
"slices"
"strings"
"unicode/utf8"
ue "github.com/PlayerR9/MyGoLib/Units/Errors"
)
// ToUTF8Runes converts a string to a slice of runes.
//
// Parameters:
// - s: The string to convert.
//
// Returns:
// - []rune: The slice of runes
// - error: An error of type *ErrInvalidUTF8Encoding if the string contains
// invalid UTF-8 encoding.
//
// Behaviors:
// - An empty string returns a nil slice with no errors.
// - The function stops at the first invalid UTF-8 encoding; returning an
// error and the runes found up to that point.
func ToUTF8Runes(s string) ([]rune, error) {
if s == "" {
return nil, nil
}
if !utf8.ValidString(s) {
return nil, NewErrInvalidUTF8Encoding()
}
return []rune(s), nil
}
// ReplaceSuffix replaces the end of the given string with the provided suffix.
//
// Parameters:
// - str: The original string.
// - suffix: The suffix to replace the end of the string.
//
// Returns:
// - string: The resulting string after replacing the end with the suffix.
// - error: An error of type *ErrLongerSuffix if the suffix is longer than
// the string.
//
// Examples:
//
// const (
// str string = "hello world"
// suffix string = "Bob"
// )
//
// result, err := ReplaceSuffix(str, suffix)
//
// if err != nil {
// fmt.Println(err)
// } else {
// fmt.Println(result) // Output: hello woBob
// }
func ReplaceSuffix(str, suffix string) (string, error) {
if suffix == "" {
return str, nil
}
countStr := utf8.RuneCountInString(str)
countSuffix := utf8.RuneCountInString(suffix)
if countStr < countSuffix {
return "", NewErrLongerSuffix(str, suffix)
}
if countStr == countSuffix {
return suffix, nil
}
return str[:countStr-countSuffix] + suffix, nil
}
// FindContentIndexes searches for the positions of opening and closing
// tokens in a slice of strings.
//
// Parameters:
// - openingToken: The string that marks the beginning of the content.
// - closingToken: The string that marks the end of the content.
// - contentTokens: The slice of strings in which to search for the tokens.
//
// Returns:
// - result: An array of two integers representing the start and end indexes
// of the content.
// - err: Any error that occurred while searching for the tokens.
//
// Errors:
// - *ue.ErrInvalidParameter: If the openingToken or closingToken is an
// empty string.
// - *ErrTokenNotFound: If the opening or closing token is not found in the
// content.
// - *ErrNeverOpened: If the closing token is found without any
// corresponding opening token.
//
// Behaviors:
// - The first index of the content is inclusive, while the second index is
// exclusive.
// - This function returns a partial result when errors occur. ([-1, -1] if
// errors occur before finding the opening token, [index, 0] if the opening
// token is found but the closing token is not found.
func FindContentIndexes(openingToken, closingToken string, contentTokens []string) (result [2]int, err error) {
result[0] = -1
result[1] = -1
if openingToken == "" {
err = ue.NewErrInvalidParameter(
"openingToken",
ue.NewErrEmptyString(),
)
return
} else if closingToken == "" {
err = ue.NewErrInvalidParameter(
"closingToken",
ue.NewErrEmptyString(),
)
return
}
openingTokenIndex := slices.Index(contentTokens, openingToken)
if openingTokenIndex < 0 {
err = NewErrTokenNotFound(openingToken, OpToken)
return
} else {
result[0] = openingTokenIndex + 1
}
tokenBalance := 1
closingTokenIndex := -1
for i := result[0]; i < len(contentTokens); i++ {
if contentTokens[i] == closingToken {
tokenBalance--
if tokenBalance == 0 {
closingTokenIndex = i
break
}
} else if contentTokens[i] == openingToken {
tokenBalance++
}
}
if closingTokenIndex != -1 {
result[1] = closingTokenIndex + 1
return
}
if tokenBalance < 0 {
err = NewErrNeverOpened(openingToken, closingToken)
return
} else if tokenBalance != 1 || closingToken != "\n" {
err = NewErrTokenNotFound(closingToken, ClToken)
return
}
result[1] = len(contentTokens)
return
}
// SplitSentenceIntoFields splits the string into fields, where each field is a
// substring separated by one or more whitespace charactue.
//
// Parameters:
// - sentence: The string to split into fields.
// - indentLevel: The number of spaces that a tab character is replaced with.
//
// Returns:
// - [][]string: A two-dimensional slice of strings, where each inner slice
// represents the fields of a line from the input string.
// - error: An error of type *ue.ErrInvalidRuneAt if an invalid rune is found in
// the sentence.
//
// Behaviors:
// - Negative indentLevel values are converted to positive values.
// - Empty sentences return a nil slice with no errors.
// - The function handles the following whitespace characters: space, tab,
// vertical tab, carriage return, line feed, and form feed.
// - The function returns a partial result if an invalid rune is found where
// the result are the fields found up to that point.
func AdvancedFieldsSplitter(sentence string, indentLevel int) ([][]string, error) {
if sentence == "" {
return nil, nil
}
if indentLevel < 0 {
indentLevel *= -1
}
lines := make([][]string, 0)
words := make([]string, 0)
var builder strings.Builder
for j := 0; len(sentence) > 0; j++ {
char, size := utf8.DecodeRuneInString(sentence)
sentence = sentence[size:]
if char == utf8.RuneError {
if builder.Len() != 0 {
words = append(words, builder.String())
builder.Reset()
}
if len(words) > 0 {
lines = append(lines, words)
}
return lines, ue.NewErrAt(j, ue.NewErrInvalidRune(nil))
}
switch char {
case '\t':
// Replace tabs with N spaces
builder.WriteString(strings.Repeat(" ", indentLevel)) // 3 spaces
case '\v':
// Do nothing
case '\r':
if size != 0 {
nextRune, size := utf8.DecodeRuneInString(sentence)
if nextRune == '\n' {
sentence = sentence[size:]
}
}
fallthrough
case '\n', '\u0085', ' ', '\f':
if builder.Len() != 0 {
words = append(words, builder.String())
builder.Reset()
}
if char != ' ' {
if len(words) > 0 {
lines = append(lines, words)
words = make([]string, 0)
}
if char == '\f' {
lines = append(lines, []string{string(char)})
}
}
case '\u00A0':
builder.WriteRune(' ')
default:
builder.WriteRune(char)
}
}
if builder.Len() != 0 {
words = append(words, builder.String())
}
if len(words) > 0 {
lines = append(lines, words)
}
return lines, nil
}
// GenerateID generates a random ID of the specified size (in bytes).
//
// Parameters:
// - size: The size of the ID to generate (in bytes).
//
// Returns:
// - string: The generated ID.
// - error: An error if the ID cannot be generated.
//
// Errors:
// - *ue.ErrInvalidParameter: If the size is less than 1.
// - Any error returned by the rand.Read function.
//
// Behaviors:
// - The function uses the crypto/rand package to generate a random ID of the
// specified size.
// - The ID is returned as a hexadecimal string.
func GenerateID(size int) (string, error) {
if size < 1 {
return "", ue.NewErrInvalidParameter(
"size",
ue.NewErrGT(0),
)
}
b := make([]byte, size) // 128 bits
_, err := rand.Read(b)
if err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
// FitString fits a string to the specified width by adding spaces to the end
// of the string until the width is reached.
//
// Parameters:
// - width: The width to fit the string to.
//
// Returns:
// - string: The string with spaces added to the end to fit the width.
// - error: An error if the width is less than 0.
//
// Behaviors:
// - If the width is less than the length of the string, the string is
// truncated to fit the width.
// - If the width is greater than the length of the string, spaces are added
// to the end of the string until the width is reached.
func FitString(s string, width int) (string, error) {
if width < 0 {
return "", ue.NewErrInvalidParameter(
"width",
ue.NewErrGTE(0),
)
}
len := len([]rune(s))
if width == 0 {
return "", nil
} else if len == 0 {
return strings.Repeat(" ", width), nil
} else if len == width {
return s, nil
}
if len > width {
return s[:width], nil
} else {
return s + strings.Repeat(" ", width-len), nil
}
}