-
Notifications
You must be signed in to change notification settings - Fork 183
/
strings.go
114 lines (101 loc) · 3.08 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
package utils
import (
"bufio"
"bytes"
"io"
"regexp"
"strings"
"unicode"
)
// FirstNonEmpty returns the first string that is not empty, otherwise ""
func FirstNonEmpty(s ...string) string {
for _, str := range s {
if str != "" {
return str
}
}
return ""
}
// FirstNonZero returns the first int in `ns` that is not zero.
func FirstNonZero(ns ...int) int {
for _, n := range ns {
if n != 0 {
return n
}
}
return 0
}
// IndentLines indents all lines in `ss` by `spaces` number of spaces
func IndentLines(ss string, spaces int) string {
var output string
for i := range ss {
if i == 0 {
output += strings.Repeat(" ", spaces) + string(ss[i])
} else if ss[i] == '\n' && i != len(ss)-1 {
output += "\n" + strings.Repeat(" ", spaces)
} else {
output += string(ss[i])
}
}
return output
}
// LowercaseFirstChar make the first character of a string lowercase
func LowercaseFirstChar(s string) string {
for i, v := range s {
return string(unicode.ToLower(v)) + s[i+1:]
}
return ""
}
// StripIndent looks at the first line in s and strips off whatever whitespace
// indentation it has from every line in s. If subsequent lines do not start
// with the same indentation as the first line, results are undefined.
// If the first line is blank, it will be removed before processing.
func StripIndent(s string) string {
lines := strings.Split(strings.TrimLeft(s, "\n"), "\n")
re := regexp.MustCompile(`^(\s+)`)
matches := re.FindStringSubmatch(lines[0])
if len(matches) > 0 {
indent := matches[1]
for i := range lines {
lines[i] = strings.Replace(lines[i], indent, "", 1)
}
}
return strings.TrimSpace(strings.Join(lines, "\n"))
}
// EnsurePrefix ensures that string s starts with the given prefix. If it
// already starts with that prefix, the original string is returned unaltered.
func EnsurePrefix(s, prefix string) string {
if strings.HasPrefix(s, prefix) {
return s
}
return prefix + s
}
// ChunkScanner looks for a line and all subsequent indented lines and
// returns a scanner that will output that chunk as a single token. This
// assumes that the entire chunk comes in a single read call, which will not
// always be the case.
func ChunkScanner(output io.ReadCloser) *bufio.Scanner {
s := bufio.NewScanner(output)
s.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
lines := bytes.Split(data, []byte{'\n'})
// If there is no newline in the data, lines will only have one element,
// so return and wait for more data.
if len(lines) == 1 && !atEOF {
return 0, nil, nil
}
// For any subsequent indented lines, assume they are part of the same
// log entry. This requires that the whole entry be fed to this
// function in a single chunk, so some entries may get split up
// erroneously.
var i int
for i = 1; i < len(lines) && len(lines[i]) > 0 && (lines[i][0] == ' ' || lines[i][0] == '\t'); i++ {
}
entry := bytes.Join(lines[:i], []byte("\n"))
// the above Join adds back all newlines lost except for one
return len(entry) + 1, entry, nil
})
return s
}