forked from coder/coder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstrings.go
More file actions
128 lines (116 loc) · 3.21 KB
/
strings.go
File metadata and controls
128 lines (116 loc) · 3.21 KB
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
package strings
import (
"fmt"
"strconv"
"strings"
"unicode"
"github.com/acarl005/stripansi"
"github.com/microcosm-cc/bluemonday"
)
// EmptyToNil returns a `nil` for an empty string, or a pointer to the string
// otherwise. Useful when needing to treat zero values as nil in APIs.
func EmptyToNil(s string) *string {
if s == "" {
return nil
}
return &s
}
// JoinWithConjunction joins a slice of strings with commas except for the last
// two which are joined with "and".
func JoinWithConjunction(s []string) string {
last := len(s) - 1
if last == 0 {
return s[last]
}
return fmt.Sprintf("%s and %s",
strings.Join(s[:last], ", "),
s[last],
)
}
type TruncateOption int
func (o TruncateOption) String() string {
switch o {
case TruncateWithEllipsis:
return "TruncateWithEllipsis"
case TruncateWithFullWords:
return "TruncateWithFullWords"
default:
return fmt.Sprintf("TruncateOption(%d)", o)
}
}
const (
// TruncateWithEllipsis adds a Unicode ellipsis character to the end of the string.
TruncateWithEllipsis TruncateOption = 1 << 0
// TruncateWithFullWords ensures that words are not split in the middle.
// As a special case, if there is no word boundary, the string is truncated.
TruncateWithFullWords TruncateOption = 1 << 1
)
// Truncate truncates s to n characters.
// Additional behaviors can be specified using TruncateOptions.
func Truncate(s string, n int, opts ...TruncateOption) string {
var options TruncateOption
for _, opt := range opts {
options |= opt
}
if n < 1 {
return ""
}
if len(s) <= n {
return s
}
maxLen := n
if options&TruncateWithEllipsis != 0 {
maxLen--
}
var sb strings.Builder
// If we need to truncate to full words, find the last word boundary before n.
if options&TruncateWithFullWords != 0 {
lastWordBoundary := strings.LastIndexFunc(s[:maxLen], unicode.IsSpace)
if lastWordBoundary < 0 {
// We cannot find a word boundary. At this point, we'll truncate the string.
// It's better than nothing.
_, _ = sb.WriteString(s[:maxLen])
} else { // lastWordBoundary <= maxLen
_, _ = sb.WriteString(s[:lastWordBoundary])
}
} else {
_, _ = sb.WriteString(s[:maxLen])
}
if options&TruncateWithEllipsis != 0 {
_, _ = sb.WriteString("…")
}
return sb.String()
}
var bmPolicy = bluemonday.StrictPolicy()
// UISanitize sanitizes a string for display in the UI.
// The following transformations are applied, in order:
// - HTML tags are removed using bluemonday's strict policy.
// - ANSI escape codes are stripped using stripansi.
// - Consecutive backslashes are replaced with a single backslash.
// - Non-printable characters are removed.
// - Whitespace characters are replaced with spaces.
// - Multiple spaces are collapsed into a single space.
// - Leading and trailing whitespace is trimmed.
func UISanitize(in string) string {
if unq, err := strconv.Unquote(`"` + in + `"`); err == nil {
in = unq
}
in = bmPolicy.Sanitize(in)
in = stripansi.Strip(in)
var b strings.Builder
var spaceSeen bool
for _, r := range in {
if unicode.IsSpace(r) {
if !spaceSeen {
_, _ = b.WriteRune(' ')
spaceSeen = true
}
continue
}
spaceSeen = false
if unicode.IsPrint(r) {
_, _ = b.WriteRune(r)
}
}
return strings.TrimSpace(b.String())
}