-
Notifications
You must be signed in to change notification settings - Fork 2
/
strings.go
169 lines (153 loc) · 4.28 KB
/
strings.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
// Copyright 2016 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package util
import (
"bytes"
"fmt"
io "io"
"math/rand"
"strings"
"text/tabwriter"
"unicode/utf8"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/redact"
)
// GetSingleRune decodes the string s as a single rune if possible.
func GetSingleRune(s string) (rune, error) {
if s == "" {
return 0, nil
}
r, sz := utf8.DecodeRuneInString(s)
if r == utf8.RuneError {
return 0, errors.Errorf("invalid character: %s", s)
}
if sz != len(s) {
return r, errors.New("must be only one character")
}
return r, nil
}
// ToLowerSingleByte returns the lowercase of a given single ASCII byte.
// A non ASCII byte is returned unchanged.
func ToLowerSingleByte(b byte) byte {
if b >= 'A' && b <= 'Z' {
return 'a' + (b - 'A')
}
return b
}
// TruncateString truncates a string to a given number of runes.
func TruncateString(s string, maxRunes int) string {
// This is a fast path (len(s) is an upper bound for RuneCountInString).
if len(s) <= maxRunes {
return s
}
n := utf8.RuneCountInString(s)
if n <= maxRunes {
return s
}
// Fast path for ASCII strings.
if len(s) == n {
return s[:maxRunes]
}
i := 0
for pos := range s {
if i == maxRunes {
return s[:pos]
}
i++
}
// This code should be unreachable.
return s
}
// RemoveTrailingSpaces splits the input string into lines, trims any trailing
// spaces from each line, then puts the lines back together.
//
// Any newlines at the end of the input string are ignored.
//
// The output string always ends in a newline.
func RemoveTrailingSpaces(input string) string {
lines := strings.TrimRight(input, "\n")
var buf bytes.Buffer
for _, line := range strings.Split(lines, "\n") {
fmt.Fprintf(&buf, "%s\n", strings.TrimRight(line, " "))
}
return buf.String()
}
// StringListBuilder helps printing out lists of items. See
// MakeStringListBuilder.
type StringListBuilder struct {
begin, separator, end string
// started is true if we had at least one entry (and thus wrote out <begin>).
started bool
}
// MakeStringListBuilder creates a StringListBuilder, which is used to print out
// lists of items. Sample usage:
//
// b := MakeStringListBuilder("(", ", ", ")")
// b.Add(&buf, "x")
// b.Add(&buf, "y")
// b.Finish(&buf) // By now, we wrote "(x, y)".
//
// If Add is not called, nothing is written.
func MakeStringListBuilder(begin, separator, end string) StringListBuilder {
return StringListBuilder{
begin: begin,
separator: separator,
end: end,
started: false,
}
}
func (b *StringListBuilder) prepareToAdd(w io.Writer) {
if b.started {
_, _ = w.Write([]byte(b.separator))
} else {
_, _ = w.Write([]byte(b.begin))
b.started = true
}
}
// Add an item to the list.
func (b *StringListBuilder) Add(w io.Writer, val string) {
b.prepareToAdd(w)
_, _ = w.Write([]byte(val))
}
// Addf is a format variant of Add.
func (b *StringListBuilder) Addf(w io.Writer, format string, args ...interface{}) {
b.prepareToAdd(w)
fmt.Fprintf(w, format, args...)
}
// Finish must be called after all the elements have been added.
func (b *StringListBuilder) Finish(w io.Writer) {
if b.started {
_, _ = w.Write([]byte(b.end))
}
}
// ExpandTabsInRedactableBytes expands tabs in the redactable byte
// slice, so that columns are aligned. The correctness of this
// function depends on the assumption that the `tabwriter` does not
// replace characters.
func ExpandTabsInRedactableBytes(s redact.RedactableBytes) (redact.RedactableBytes, error) {
var buf bytes.Buffer
tw := tabwriter.NewWriter(&buf, 2, 1, 2, ' ', 0)
if _, err := tw.Write([]byte(s)); err != nil {
return nil, err
}
if err := tw.Flush(); err != nil {
return nil, err
}
return redact.RedactableBytes(buf.Bytes()), nil
}
// RandString generates a random string of the desired length from the
// input alphabet.
func RandString(rng *rand.Rand, length int, alphabet string) string {
buf := make([]byte, length)
for i := range buf {
buf[i] = alphabet[rng.Intn(len(alphabet))]
}
return string(buf)
}