github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/strings.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package util 12 13 import ( 14 "bytes" 15 "fmt" 16 io "io" 17 "math/rand" 18 "strings" 19 "text/tabwriter" 20 "unicode/utf8" 21 22 "github.com/cockroachdb/errors" 23 "github.com/cockroachdb/redact" 24 ) 25 26 // GetSingleRune decodes the string s as a single rune if possible. 27 func GetSingleRune(s string) (rune, error) { 28 if s == "" { 29 return 0, nil 30 } 31 r, sz := utf8.DecodeRuneInString(s) 32 if r == utf8.RuneError { 33 return 0, errors.Errorf("invalid character: %s", s) 34 } 35 if sz != len(s) { 36 return r, errors.New("must be only one character") 37 } 38 return r, nil 39 } 40 41 // ToLowerSingleByte returns the lowercase of a given single ASCII byte. 42 // A non ASCII byte is returned unchanged. 43 func ToLowerSingleByte(b byte) byte { 44 if b >= 'A' && b <= 'Z' { 45 return 'a' + (b - 'A') 46 } 47 return b 48 } 49 50 // TruncateString truncates a string to a given number of runes. 51 func TruncateString(s string, maxRunes int) string { 52 // This is a fast path (len(s) is an upper bound for RuneCountInString). 53 if len(s) <= maxRunes { 54 return s 55 } 56 n := utf8.RuneCountInString(s) 57 if n <= maxRunes { 58 return s 59 } 60 // Fast path for ASCII strings. 61 if len(s) == n { 62 return s[:maxRunes] 63 } 64 i := 0 65 for pos := range s { 66 if i == maxRunes { 67 return s[:pos] 68 } 69 i++ 70 } 71 // This code should be unreachable. 72 return s 73 } 74 75 // RemoveTrailingSpaces splits the input string into lines, trims any trailing 76 // spaces from each line, then puts the lines back together. 77 // 78 // Any newlines at the end of the input string are ignored. 79 // 80 // The output string always ends in a newline. 81 func RemoveTrailingSpaces(input string) string { 82 lines := strings.TrimRight(input, "\n") 83 var buf bytes.Buffer 84 for _, line := range strings.Split(lines, "\n") { 85 fmt.Fprintf(&buf, "%s\n", strings.TrimRight(line, " ")) 86 } 87 return buf.String() 88 } 89 90 // StringListBuilder helps printing out lists of items. See 91 // MakeStringListBuilder. 92 type StringListBuilder struct { 93 begin, separator, end string 94 95 // started is true if we had at least one entry (and thus wrote out <begin>). 96 started bool 97 } 98 99 // MakeStringListBuilder creates a StringListBuilder, which is used to print out 100 // lists of items. Sample usage: 101 // 102 // b := MakeStringListBuilder("(", ", ", ")") 103 // b.Add(&buf, "x") 104 // b.Add(&buf, "y") 105 // b.Finish(&buf) // By now, we wrote "(x, y)". 106 // 107 // If Add is not called, nothing is written. 108 func MakeStringListBuilder(begin, separator, end string) StringListBuilder { 109 return StringListBuilder{ 110 begin: begin, 111 separator: separator, 112 end: end, 113 started: false, 114 } 115 } 116 117 func (b *StringListBuilder) prepareToAdd(w io.Writer) { 118 if b.started { 119 _, _ = w.Write([]byte(b.separator)) 120 } else { 121 _, _ = w.Write([]byte(b.begin)) 122 b.started = true 123 } 124 } 125 126 // Add an item to the list. 127 func (b *StringListBuilder) Add(w io.Writer, val string) { 128 b.prepareToAdd(w) 129 _, _ = w.Write([]byte(val)) 130 } 131 132 // Addf is a format variant of Add. 133 func (b *StringListBuilder) Addf(w io.Writer, format string, args ...interface{}) { 134 b.prepareToAdd(w) 135 fmt.Fprintf(w, format, args...) 136 } 137 138 // Finish must be called after all the elements have been added. 139 func (b *StringListBuilder) Finish(w io.Writer) { 140 if b.started { 141 _, _ = w.Write([]byte(b.end)) 142 } 143 } 144 145 // ExpandTabsInRedactableBytes expands tabs in the redactable byte 146 // slice, so that columns are aligned. The correctness of this 147 // function depends on the assumption that the `tabwriter` does not 148 // replace characters. 149 func ExpandTabsInRedactableBytes(s redact.RedactableBytes) (redact.RedactableBytes, error) { 150 var buf bytes.Buffer 151 tw := tabwriter.NewWriter(&buf, 2, 1, 2, ' ', 0) 152 if _, err := tw.Write([]byte(s)); err != nil { 153 return nil, err 154 } 155 if err := tw.Flush(); err != nil { 156 return nil, err 157 } 158 return redact.RedactableBytes(buf.Bytes()), nil 159 } 160 161 // RandString generates a random string of the desired length from the 162 // input alphabet. 163 func RandString(rng *rand.Rand, length int, alphabet string) string { 164 buf := make([]byte, length) 165 for i := range buf { 166 buf[i] = alphabet[rng.Intn(len(alphabet))] 167 } 168 return string(buf) 169 }