-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathstrings.go
More file actions
152 lines (138 loc) · 3.7 KB
/
Copy pathstrings.go
File metadata and controls
152 lines (138 loc) · 3.7 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package strings
import (
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"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 runes.
// 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 ""
}
// Find the byte offset of the (n+1)th rune, if any; early exit
// avoids decoding s past what's needed.
runeCount := 0
cutoff := -1
for i := range s {
runeCount++
if runeCount > n {
cutoff = i
break
}
}
if cutoff < 0 {
return s
}
maxLen := n
if options&TruncateWithEllipsis != 0 {
maxLen--
}
runes := []rune(s[:cutoff])
var sb strings.Builder
if options&TruncateWithFullWords != 0 {
// Convert the rune-safe prefix to a string, then find
// the last word boundary (byte offset within that prefix).
truncated := string(runes[:maxLen])
lastWordBoundary := strings.LastIndexFunc(truncated, unicode.IsSpace)
if lastWordBoundary < 0 {
_, _ = sb.WriteString(truncated)
} else {
_, _ = sb.WriteString(truncated[:lastWordBoundary])
}
} else {
_, _ = sb.WriteString(string(runes[: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())
}
// Capitalize returns s with its first rune upper-cased. It is safe for
// multi-byte UTF-8 characters, unlike naive byte-slicing approaches.
func Capitalize(s string) string {
r, size := utf8.DecodeRuneInString(s)
if size == 0 {
return s
}
return string(unicode.ToUpper(r)) + s[size:]
}