-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrslice.go
281 lines (240 loc) · 6.38 KB
/
rslice.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
/*
package rslice provides some common []rune patterns for moving runes around
within a slice, justifying embedded text, etc
Charles <asciifaceman> Corbett 2023
MIT License
*/
package rslice
import (
"fmt"
"unicode"
)
// Whitespace returns true if the entire []rune is whitespace
// It will also return true if the slice is empty
func Whitespace(slice []rune) bool {
for _, char := range slice {
if !unicode.IsSpace(char) {
return false
}
}
return true
}
// Words returns a count of non-whitespace groupings of characters
// that may or may not be a word
//
// Can be useful to discover how many areas of whitespace you have for
// purposes such as full text justification across a width
//
// Does not recognize control characters (such as \n or \t) as non-whitespace
// characters as per unicode stdlib
func Words(slice []rune) int {
count := 0
word := false
for _, char := range slice {
if !unicode.IsSpace(char) {
if !word {
word = true
count++
}
} else {
if word {
word = false
}
}
}
return count
}
// Valid returns true a slice has width and is not all whitespace
func Valid(slice []rune) bool {
if len(slice) > 0 && !Whitespace(slice) {
return true
}
return false
}
// ShiftLeft shifts the rune slice one to the left and returns a copy
// if the slice is not all whitespace
func ShiftLeft(slice []rune) []rune {
if !Valid(slice) {
return slice
}
return append(slice[1:], slice[0])
}
// ShiftRight shits the rune slice one to the right and returns a copy
// if the slice is not all whitespace
func ShiftRight(slice []rune) []rune {
if !Valid(slice) {
return slice
}
return append(slice[len(slice)-1:], slice[:len(slice)-1]...)
}
// ShiftWhitespaceLeft shifts any whitespace right of the last non-whitespace
// character to the left of the first non-whitespace character in the rune slice
// and returns a copy if the slice is not all whitespace
func ShiftWhitespaceLeft(slice []rune) []rune {
if !Valid(slice) {
return slice
}
if unicode.IsSpace(slice[len(slice)-1]) {
slice = ShiftRight(slice)
return ShiftWhitespaceLeft(slice)
} else {
return slice
}
}
// ShiftWhitespaceRight shifts any whitespace left of the first non-whitespace
// character to the right of the last non-whitespace character in the rune sluce
// and returns a copy if the slice is not all whitespace
func ShiftWhitespaceRight(slice []rune) []rune {
if !Valid(slice) {
return slice
}
if unicode.IsSpace(slice[0]) {
slice = ShiftLeft(slice)
return ShiftWhitespaceRight(slice)
} else {
return slice
}
}
// Newline returns true if the given rune is a Linux, Darwin, or Windows newline character
func Newline(r rune) bool {
if unicode.IsControl(r) {
if r == rune('\r') || r == rune('\n') {
return true
}
}
return false
}
// TrimExcessWhitespace will remove any occurance of whitespace greater
// than one count
func TrimExcessWhitespace(slice []rune) []rune {
count := 0
for i, r := range slice {
if unicode.IsSpace(r) {
count++
} else {
count = 0
}
if count > 1 {
if i == 1 {
slice = slice[1:]
} else {
slice = append(slice[:i-1], slice[i:]...)
}
return TrimExcessWhitespace(slice)
}
}
return slice
}
// LeastWhitespaceIndex returns an index point of a []rune with
// the least whitespace between the left and right
// most characters
//
// It will wait until it has passed at least one non-whitespace character
// before recording the potential index.
//
// A return of -1 indicates there is no suitable index, an example
// string would be ` a ` which has no whitespace between two non-whitespace
// characters
//
// Currently this function will trigger an ignore on whitespace after a control
// character is encountered until the next non-whitespace non-control character
// and effectively erase any whitespace between the previous non-ws/non-cc rune
// to prevent returning an index between or before a control character wh
func LeastWhitespaceIndex(slice []rune) int {
var idx int
count := len(slice)
word := false
ignore := false
subcount := count
for i, r := range slice {
if unicode.IsControl(r) {
// ignore any new whitespaces until the
// next non-whitespace character
ignore = true
continue
}
if !unicode.IsSpace(r) {
if !word {
word = true
if !ignore {
// count the index if not ignored
if subcount < count {
idx = i
count = subcount
}
subcount = 0
} else {
// if ignored reset the count to terminate
// any whitespace since the last valid word char
subcount = 0
}
}
// disable ignore if it is a word boundary
ignore = false
subcount = 0
} else {
if word {
word = false
}
// increase count of this segment of whitespace
subcount++
}
}
return idx - 1
}
/*
NormalizeWhitespace takes the left and right whitespace of the given
rune slice and spreads it across the interior whitespace of the rune
slice between the inner and outer most non-whitespace character
Maintains the []rune's width
Returns the slice unchanged if the []rune contains only whitespace,
has no length, or has fewer than 2 words since there would be no
inner whitespace to utilize
Usage:
```go
s := []rune(" A string with whitespace to the left and right ")
s = rslice.NormalizeWhitespace(s)
// s should now be "A string with whitespace to the left and right"
```
*/
func NormalizeWhitespace(slice []rune) []rune {
wordCount := Words(slice)
if Whitespace(slice) || len(slice) < 1 || wordCount < 2 {
return slice
}
slice = ShiftWhitespaceLeft(slice)
slice = Normalize(slice)
return slice
}
/*
Normalize is a recursive function that will take all whitespace left
of the left most non-whitespace and non-control-character space and move it
somewhere in the interior starting on the left most interior and working in
Normalize maintains the []rune's width
Example
```go
s := []rune(" A string with whitespace to the left")
s = rslice.Normalize(s)
// s should now be "A string with whitespace to the left"
```
*/
func Normalize(slice []rune) []rune {
if !Valid(slice) {
return slice
}
if unicode.IsSpace(slice[0]) {
d := LeastWhitespaceIndex(slice)
if d == -1 {
// need to find a test condition that will
// cause a -1 maybe after deep iteration?
return slice
}
fmt.Println(d)
slice = slice[1:]
slice = append(slice[:d+1], slice[d:]...)
slice[d] = rune(' ')
return Normalize(slice)
} else {
return slice
}
}