github.com/coveo/gotemplate@v2.7.7+incompatible/collections/string.go (about) 1 package collections 2 3 import ( 4 "fmt" 5 "math" 6 "regexp" 7 "strconv" 8 "strings" 9 "unicode" 10 ) 11 12 // String is an enhanced class implementation of the standard go string library. 13 // This is convenient when manipulating go template string to have it considered as an object. 14 type String string 15 16 // Compare returns an integer comparing two strings lexicographically. 17 // The result will be 0 if a==b, -1 if a < b, and +1 if a > b. 18 func (s String) Compare(b string) int { return strings.Compare(string(s), b) } 19 20 // Contains reports whether substr is within s. 21 func (s String) Contains(substr string) bool { return strings.Contains(string(s), substr) } 22 23 // ContainsAny reports whether any Unicode code points in chars are within s. 24 func (s String) ContainsAny(chars string) bool { return strings.ContainsAny(string(s), chars) } 25 26 // ContainsRune reports whether the Unicode code point r is within s. 27 func (s String) ContainsRune(r rune) bool { return strings.ContainsRune(string(s), r) } 28 29 // Count counts the number of non-overlapping instances of substr in s. 30 // If substr is an empty string, Count returns 1 + the number of Unicode code points in s. 31 func (s String) Count(substr string) int { return strings.Count(string(s), substr) } 32 33 // EqualFold reports whether s and t, interpreted as UTF-8 strings, 34 // are equal under Unicode case-folding. 35 func (s String) EqualFold(t string) bool { return strings.EqualFold(string(s), t) } 36 37 // Fields splits the string s around each instance of one or more consecutive white space 38 // characters, as defined by unicode.IsSpace, returning an array of substrings of s or an 39 // empty list if s contains only white space. 40 func (s String) Fields() StringArray { return stringArray(strings.Fields(string(s))) } 41 42 // FieldsFunc splits the string s at each run of Unicode code points c satisfying f(c) 43 // and returns an array of slices of s. If all code points in s satisfy f(c) or the 44 // string is empty, an empty slice is returned. 45 // FieldsFunc makes no guarantees about the order in which it calls f(c). 46 // If f does not return consistent results for a given c, FieldsFunc may crash. 47 func (s String) FieldsFunc(f func(rune) bool) StringArray { 48 return stringArray(strings.FieldsFunc(string(s), f)) 49 } 50 51 // HasPrefix tests whether the string s begins with prefix. 52 func (s String) HasPrefix(prefix string) bool { return strings.HasPrefix(string(s), prefix) } 53 54 // HasSuffix tests whether the string s ends with suffix. 55 func (s String) HasSuffix(suffix string) bool { return strings.HasSuffix(string(s), suffix) } 56 57 // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s. 58 func (s String) Index(substr string) int { return strings.Index(string(s), substr) } 59 60 // IndexAny returns the index of the first instance of any Unicode code point 61 // from chars in s, or -1 if no Unicode code point from chars is present in s. 62 func (s String) IndexAny(chars string) int { return strings.IndexAny(string(s), chars) } 63 64 // IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s. 65 func (s String) IndexByte(c byte) int { return strings.IndexByte(string(s), c) } 66 67 // IndexFunc returns the index into s of the first Unicode code point satisfying f(c), or -1 if none do. 68 func (s String) IndexFunc(f func(rune) bool) int { return strings.IndexFunc(string(s), f) } 69 70 // IndexRune returns the index of the first instance of the Unicode code point 71 // r, or -1 if rune is not present in s. 72 // If r is utf8.RuneError, it returns the first instance of any 73 // invalid UTF-8 byte sequence. 74 func (s String) IndexRune(r rune) int { return strings.IndexRune(string(s), r) } 75 76 // Join concatenates the elements of array to create a single string. The string 77 // object is placed between elements in the resulting string. 78 func (s String) Join(array ...interface{}) String { 79 return stringArray(ToStrings(array)).Join(string(s)) 80 } 81 82 // LastIndex returns the index of the last instance of substr in s, or -1 if substr is not present in s. 83 func (s String) LastIndex(substr string) int { return strings.LastIndex(string(s), substr) } 84 85 // LastIndexAny returns the index of the last instance of any Unicode code point from chars in s, or -1 86 // if no Unicode code point from chars is present in s. 87 func (s String) LastIndexAny(chars string) int { return strings.LastIndexAny(string(s), chars) } 88 89 // LastIndexByte returns the index of the last instance of c in s, or -1 if c is not present in s. 90 func (s String) LastIndexByte(c byte) int { return strings.LastIndexByte(string(s), c) } 91 92 // LastIndexFunc returns the index into s of the last 93 // Unicode code point satisfying f(c), or -1 if none do. 94 func (s String) LastIndexFunc(f func(rune) bool) int { return strings.LastIndexFunc(string(s), f) } 95 96 // Lines splits up s into a StringArray using the newline as character separator 97 func (s String) Lines() StringArray { return s.Split("\n") } 98 99 // Map returns a copy of the string s with all its characters modified 100 // according to the mapping function. If mapping returns a negative value, the character is 101 // dropped from the string with no replacement. 102 func (s String) Map(mapping func(rune) rune) String { return String(strings.Map(mapping, string(s))) } 103 104 // Repeat returns a new string consisting of count copies of the string s. 105 // 106 // It panics if count is negative or if the result of (len(s) * count) overflows. 107 func (s String) Repeat(count int) String { return String(strings.Repeat(string(s), count)) } 108 109 // Split slices s into all substrings separated by sep and returns a slice of the substrings between those separators. 110 // 111 // If s does not contain sep and sep is not empty, Split returns a slice of length 1 whose only element is s. 112 // 113 // If sep is empty, Split splits after each UTF-8 sequence. If both s and sep are empty, Split returns an empty slice. 114 // 115 // It is equivalent to SplitN with a count of -1. 116 func (s String) Split(sep string) StringArray { return stringArray(strings.Split(string(s), sep)) } 117 118 // SplitAfter slices s into all substrings after each instance of sep and returns a slice of those substrings. 119 // 120 // If s does not contain sep and sep is not empty, SplitAfter returns a slice of length 1 whose only element is s. 121 // 122 // If sep is empty, SplitAfter splits after each UTF-8 sequence. If both s and sep are empty, SplitAfter returns an empty slice. 123 // 124 // It is equivalent to SplitAfterN with a count of -1. 125 func (s String) SplitAfter(sep string) StringArray { 126 return stringArray(strings.SplitAfter(string(s), sep)) 127 } 128 129 // SplitAfterN slices s into substrings after each instance of sep and returns a slice of those substrings. 130 // 131 // The count determines the number of substrings to return: 132 // n > 0: at most n substrings; the last substring will be the unsplit remainder. 133 // n == 0: the result is nil (zero substrings) 134 // n < 0: all substrings 135 // 136 // Edge cases for s and sep (for example, empty strings) are handled as described in the documentation for SplitAfter. 137 func (s String) SplitAfterN(sep string, n int) StringArray { 138 return stringArray(strings.SplitAfterN(string(s), sep, n)) 139 } 140 141 // SplitN slices s into substrings separated by sep and returns a slice of the substrings between those separators. 142 // 143 // The count determines the number of substrings to return: 144 // n > 0: at most n substrings; the last substring will be the unsplit remainder. 145 // n == 0: the result is nil (zero substrings) 146 // n < 0: all substrings 147 // 148 // Edge cases for s and sep (for example, empty strings) are handled as described in the documentation for Split. 149 func (s String) SplitN(sep string, n int) StringArray { 150 return stringArray(strings.SplitN(string(s), sep, n)) 151 } 152 153 // Title returns a copy of the string s with all Unicode letters that begin words mapped to their title case. 154 // 155 // BUG(rsc): The rule Title uses for word boundaries does not handle Unicode punctuation properly. 156 func (s String) Title() String { return String(strings.Title(string(s))) } 157 158 // ToLower returns a copy of the string s with all Unicode letters mapped to their lower case. 159 func (s String) ToLower() String { return String(strings.ToLower(string(s))) } 160 161 // ToTitle returns a copy of the string s with all Unicode letters mapped to their title case. 162 func (s String) ToTitle() String { return String(strings.ToTitle(string(s))) } 163 164 // ToUpper returns a copy of the string s with all Unicode letters mapped to their upper case. 165 func (s String) ToUpper() String { return String(strings.ToUpper(string(s))) } 166 167 // Trim returns a slice of the string s with all leading and // trailing Unicode code points contained in cutset removed. 168 func (s String) Trim(cutset string) String { return String(strings.Trim(string(s), cutset)) } 169 170 // TrimFunc returns a slice of the string s with all leading and trailing Unicode code points c satisfying f(c) removed. 171 func (s String) TrimFunc(f func(rune) bool) String { return String(strings.TrimFunc(string(s), f)) } 172 173 // TrimLeft returns a slice of the string s with all leading Unicode code points contained in cutset removed. 174 func (s String) TrimLeft(cutset string) String { return String(strings.TrimLeft(string(s), cutset)) } 175 176 // TrimLeftFunc returns a slice of the string s with all leading Unicode code points c satisfying f(c) removed. 177 func (s String) TrimLeftFunc(f func(rune) bool) String { 178 return String(strings.TrimLeftFunc(string(s), f)) 179 } 180 181 // TrimPrefix returns s without the provided leading prefix string. 182 // If s doesn't start with prefix, s is returned unchanged. 183 func (s String) TrimPrefix(prefix string) String { return String(strings.TrimPrefix(string(s), prefix)) } 184 185 // TrimRight returns a slice of the string s, with all trailing Unicode code points contained in cutset removed. 186 func (s String) TrimRight(cutset string) String { return String(strings.TrimRight(string(s), cutset)) } 187 188 // TrimRightFunc returns a slice of the string s with all trailing Unicode code points c satisfying f(c) removed. 189 func (s String) TrimRightFunc(f func(rune) bool) String { 190 return String(strings.TrimRightFunc(string(s), f)) 191 } 192 193 // TrimSpace returns a slice of the string s, with all leading and trailing white space removed, as defined by Unicode. 194 func (s String) TrimSpace() String { return String(strings.TrimSpace(string(s))) } 195 196 // TrimSuffix returns s without the provided trailing suffix string. 197 // If s doesn't end with suffix, s is returned unchanged. 198 func (s String) TrimSuffix(suffix string) String { return String(strings.TrimSuffix(string(s), suffix)) } 199 200 // ToLowerSpecial returns a copy of the string s with all Unicode letters mapped to their 201 // lower case, giving priority to the special casing rules. 202 func (s String) ToLowerSpecial(c unicode.SpecialCase) String { 203 return String(strings.ToLowerSpecial(c, string(s))) 204 } 205 206 // ToTitleSpecial returns a copy of the string s with all Unicode letters mapped to their 207 // title case, giving priority to the special casing rules. 208 func (s String) ToTitleSpecial(c unicode.SpecialCase) String { 209 return String(strings.ToTitleSpecial(c, string(s))) 210 } 211 212 // ToUpperSpecial returns a copy of the string s with all Unicode letters mapped to their 213 // upper case, giving priority to the special casing rules. 214 func (s String) ToUpperSpecial(c unicode.SpecialCase) String { 215 return String(strings.ToUpperSpecial(c, string(s))) 216 } 217 218 // ------------------------------------------------------------------------------------------------------------------- 219 // The following functions are extension or variation of the standard go string library 220 221 // String simply convert a String object into a regular string. 222 func (s String) String() string { return string(s) } 223 224 // Str is an abbreviation of String. 225 func (s String) Str() string { return string(s) } 226 227 // Len returns the length of the string. 228 func (s String) Len() int { return len(s) } 229 230 // Quote returns the string between quotes. 231 func (s String) Quote() String { return String(fmt.Sprintf("%q", s)) } 232 233 // Escape returns the representation of the string with escape characters. 234 func (s String) Escape() String { 235 q := s.Quote() 236 return String(q[1 : len(q)-1]) 237 } 238 239 // FieldsID splits the string s at character that is not a valid identifier character (letter, digit or underscore). 240 func (s String) FieldsID() StringArray { 241 f := func(c rune) bool { 242 return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_' 243 } 244 return s.FieldsFunc(f) 245 } 246 247 // Center returns the string centered within the specified width. 248 func (s String) Center(width int) String { return String(CenterString(string(s), width)) } 249 250 // Wrap returns the string wrapped with newline when exceeding the specified width. 251 func (s String) Wrap(width int) String { return String(WrapString(string(s), width)) } 252 253 // Replace returns a copy of the string s with the first n non-overlapping instances of old replaced by new. 254 // If old is empty, it matches at the beginning of the string and after each UTF-8 sequence, yielding up to 255 // k+1 replacements for a k-rune string. 256 func (s String) Replace(old, new string) String { 257 return String(strings.Replace(string(s), old, new, -1)) 258 } 259 260 // ReplaceN returns a copy of the string s with the first n non-overlapping instances of old replaced by new. 261 // If old is empty, it matches at the beginning of the string and after each UTF-8 sequence, yielding up to 262 // If n < 0, there is no limit on the number of replacements. 263 func (s String) ReplaceN(old, new string, n int) String { 264 return String(strings.Replace(string(s), old, new, n)) 265 } 266 267 // Indent returns the indented version of the supplied string (indent represents the string used to indent the lines). 268 func (s String) Indent(indent string) String { 269 return String(Indent(string(s), indent)) 270 } 271 272 // IndentN returns the indented version of the supplied string (indent represents the number of spaces used to indent the lines). 273 func (s String) IndentN(indent int) String { 274 return String(IndentN(string(s), indent)) 275 } 276 277 // UnIndent returns the string unindented. 278 func (s String) UnIndent() String { 279 return String(UnIndent(string(s))) 280 } 281 282 // GetWordAtPosition returns the selected word and the start position from the specified position. 283 func (s String) GetWordAtPosition(pos int, accept ...string) (String, int) { 284 if pos < 0 || pos >= len(s) { 285 return "", -1 286 } 287 288 acceptChars := strings.Join(accept, "") 289 isBreak := func(c rune) bool { 290 return unicode.IsSpace(c) || unicode.IsPunct(c) && !strings.ContainsRune(acceptChars, c) 291 } 292 begin, end := pos, pos 293 for begin >= 0 && !isBreak(rune(s[begin])) { 294 begin-- 295 } 296 for end < len(s) && !isBreak(rune(s[end])) { 297 end++ 298 } 299 if begin != end { 300 begin++ 301 } 302 return s[begin:end], begin 303 } 304 305 // SelectWord returns the selected word from the specified position. 306 func (s String) SelectWord(pos int, accept ...string) String { 307 result, _ := s.GetWordAtPosition(pos, accept...) 308 return result 309 } 310 311 // IndexAll returns all positions where substring is found within s. 312 func (s String) IndexAll(substr string) (result []int) { 313 if substr == "" || s == "" { 314 return nil 315 } 316 start, lenSubstr := 0, len(substr) 317 for pos := s.Index(substr); pos >= 0; pos = s[start:].Index(substr) { 318 result = append(result, start+pos) 319 start += pos + lenSubstr 320 } 321 return 322 } 323 324 // GetContextAtPosition tries to extend the context from the specified position within specified boundaries. 325 func (s String) GetContextAtPosition(pos int, left, right string) (a String, b int) { 326 if pos < 0 || pos >= len(s) { 327 // Trying to select context out of bound 328 return "", -1 329 } 330 331 findLeft := func(s String, pos int) int { 332 if left == "" { 333 return pos 334 } 335 return s[:pos].LastIndex(left) 336 } 337 begin, end, lenLeft, lenRight := findLeft(s, pos), pos+1, len(left), len(right) 338 339 if begin >= 0 && lenRight > 0 { 340 if end = s[pos:].Index(right); end >= 0 { 341 end += pos + lenRight 342 back := findLeft(s[:end], end-lenRight) 343 if left != "" && back != begin { 344 // There is at least one enclosed start, we must find the corresponding end 345 pos = begin + lenLeft 346 lefts := s[pos:].IndexAll(left) 347 rights := s[pos:].IndexAll(right) 348 for i := range lefts { 349 if i == len(rights) { 350 return "", -1 351 } 352 if lefts[i] > rights[i] { 353 return s[begin : rights[i]+pos+lenRight], begin 354 } 355 } 356 if len(rights) > len(lefts) { 357 return s[begin : rights[len(lefts)]+pos+lenRight], begin 358 } 359 end = -1 360 } 361 } 362 } 363 if begin < 0 || end < 0 { 364 return "", -1 365 } 366 return s[begin:end], begin 367 } 368 369 // SelectContext returns the selected word from the specified position. 370 func (s String) SelectContext(pos int, left, right string) String { 371 result, _ := s.GetContextAtPosition(pos, left, right) 372 return result 373 } 374 375 // Protect returns a string with all included strings replaced by a token and an array of all replaced strings. 376 // The function is able to detect strings enclosed between quotes "" or backtick `` and it detects escaped characters. 377 func (s String) Protect() (result String, array StringArray) { 378 defer func() { result += s }() 379 380 escaped := func(from int) bool { 381 // Determine if the previous characters are escaping the current value 382 count := 0 383 for ; s[from-count] == '\\'; count++ { 384 } 385 return count%2 == 1 386 } 387 388 for end := 0; end >= 0; { 389 pos := s.IndexAny("`\"") 390 if pos < 0 { 391 break 392 } 393 394 for end = s[pos+1:].IndexRune(rune(s[pos])); end >= 0; { 395 end += pos + 1 396 if s[end] == '"' && escaped(end-1) { 397 // The quote has been escaped, so we find the next one 398 newEnd := s[end+1:].IndexRune('"') 399 if newEnd < 0 { 400 return 401 } 402 end += newEnd - pos 403 } else { 404 array = append(array, s[pos:end+1]) 405 result += s[:pos] + String(fmt.Sprintf(replacementFormat, len(array)-1)) 406 s = s[end+1:] 407 break 408 } 409 } 410 } 411 return 412 } 413 414 // RestoreProtected restores a string transformed by ProtectString to its original value. 415 func (s String) RestoreProtected(array StringArray) String { 416 return String(replacementRegex.ReplaceAllStringFunc(s.Str(), func(match string) string { 417 index := must(strconv.Atoi(replacementRegex.FindStringSubmatch(match)[1])).(int) 418 return array[index].Str() 419 })) 420 } 421 422 const replacementFormat = `"♠%d"` 423 424 var replacementRegex = regexp.MustCompile(`"♠(\d+)"`) 425 426 // AddLineNumber adds line number to a string 427 func (s String) AddLineNumber(space int) String { 428 lines := s.Lines() 429 if space <= 0 { 430 space = int(math.Log10(float64(len(lines)))) + 1 431 } 432 433 for i := range lines { 434 lines[i] = String(fmt.Sprintf("%*d %s", space, i+1, lines[i])) 435 } 436 return lines.Join("\n") 437 } 438 439 // ParseBool returns true if variable exist and is not clearly a false value 440 // i.e. empty, 0, Off, No, n, false, f 441 func (s String) ParseBool() bool { 442 // We first try with the strconv library 443 if result, err := strconv.ParseBool(s.Str()); err == nil { 444 return result 445 } 446 switch s.ToUpper() { 447 case "", "N", "NO", "OFF": 448 return false 449 default: 450 // Any other value is considered as true 451 return true 452 } 453 }