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  }