-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsplit.go
165 lines (145 loc) · 3.8 KB
/
split.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
package strcase
import "unicode"
// SplitFn defines how to split a string into words
type SplitFn func(prev, curr, next rune) SplitAction
// NewSplitFn returns a SplitFn based on the options provided.
//
// NewSplitFn covers the majority of common options that other strcase
// libraries provide and should allow you to simply create a custom caser.
// For more complicated use cases, feel free to write your own SplitFn
//
//nolint:gocyclo
func NewSplitFn(
delimiters []rune,
splitOptions ...SplitOption,
) SplitFn {
var splitCase, splitAcronym, splitBeforeNumber, splitAfterNumber, preserveNumberFormatting bool
for _, option := range splitOptions {
switch option {
case SplitCase:
splitCase = true
case SplitAcronym:
splitAcronym = true
case SplitBeforeNumber:
splitBeforeNumber = true
case SplitAfterNumber:
splitAfterNumber = true
case PreserveNumberFormatting:
preserveNumberFormatting = true
}
}
return func(prev, curr, next rune) SplitAction {
// The most common case will be that it's just a letter
// There are safe cases to process
if isLower(curr) && !isNumber(prev) {
return Noop
}
if isUpper(prev) && isUpper(curr) && isUpper(next) {
return Noop
}
if preserveNumberFormatting {
if (curr == '.' || curr == ',') &&
isNumber(prev) && isNumber(next) {
return Noop
}
}
if unicode.IsSpace(curr) {
return SkipSplit
}
for _, d := range delimiters {
if curr == d {
return SkipSplit
}
}
if splitBeforeNumber {
if isNumber(curr) && !isNumber(prev) {
if preserveNumberFormatting && (prev == '.' || prev == ',') {
return Noop
}
return Split
}
}
if splitAfterNumber {
if isNumber(prev) && !isNumber(curr) {
return Split
}
}
if splitCase {
if !isUpper(prev) && isUpper(curr) {
return Split
}
}
if splitAcronym {
if isUpper(prev) && isUpper(curr) && isLower(next) {
return Split
}
}
return Noop
}
}
// SplitOption are options that allow for configuring NewSplitFn
type SplitOption int
const (
// SplitCase - FooBar -> Foo_Bar
SplitCase SplitOption = iota
// SplitAcronym - FOOBar -> Foo_Bar
// It won't preserve FOO's case. If you want, you can set the Caser's initialisms so FOO will be in all caps
SplitAcronym
// SplitBeforeNumber - port80 -> port_80
SplitBeforeNumber
// SplitAfterNumber - 200status -> 200_status
SplitAfterNumber
// PreserveNumberFormatting - a.b.2,000.3.c -> a_b_2,000.3_c
PreserveNumberFormatting
)
// SplitAction defines if and how to split a string
type SplitAction int
const (
// Noop - Continue to next character
Noop SplitAction = iota
// Split - Split between words
// e.g. to split between wordsWithoutDelimiters
Split
// SkipSplit - Split the word and drop the character
// e.g. to split words with delimiters
SkipSplit
// Skip - Remove the character completely
Skip
)
//nolint:gocyclo
func defaultSplitFn(prev, curr, next rune) SplitAction {
// The most common case will be that it's just a letter so let lowercase letters return early since we know what they should do
if isLower(curr) {
return Noop
}
// Delimiters are _, -, ., and unicode spaces
// Handle . lower down as it needs to happen after number exceptions
if curr == '_' || curr == '-' || isSpace(curr) {
return SkipSplit
}
if isUpper(curr) {
if isLower(prev) {
// fooBar
return Split
} else if isUpper(prev) && isLower(next) {
// FOOBar
return Split
}
}
// Do numeric exceptions last to avoid perf penalty
if unicode.IsNumber(prev) {
// v4.3 is not split
if (curr == '.' || curr == ',') && unicode.IsNumber(next) {
return Noop
}
if !unicode.IsNumber(curr) && curr != '.' {
return Split
}
}
// While period is a default delimiter, keep it down here to avoid
// penalty for other delimiters
if curr == '.' {
return SkipSplit
}
return Noop
}