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  }