workgroups/vendor/github.com/ettle/strcase/split.go
Marvin Preuss 1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
ci: drone yaml with reusable anchors
2021-09-24 17:34:17 +02:00

165 lines
3.8 KiB
Go

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
}