165 lines
3.8 KiB
Go
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
|
|
}
|