text/stringcase/casevalidator.go
package stringcase
import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/grokify/mogo/errors/errorsutil"
"github.com/grokify/mogo/type/stringsutil"
)
/*
https://stackoverflow.com/questions/1128305/regex-for-pascalcased-words-aka-camelcased-with-leading-uppercase-letter
https://gist.github.com/manjeettahkur/ff114ef92d8ffee1b797091ff77ea89f
https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
*/
const (
CamelCase = "camelCase"
KebabCase = "kebab-case"
PascalCase = "PascalCase"
SnakeCase = "snake_case"
)
var ErrUnknownCaseString = errors.New("unknown case string")
var mapCaseConst = map[string]string{
"camel": CamelCase,
"camelcase": CamelCase,
"camel-case": CamelCase,
"camel_case": CamelCase,
"camel case": CamelCase,
"kebab": KebabCase,
"kebabcase": KebabCase,
"kebab-case": KebabCase,
"kebab_case": KebabCase,
"kebab case": KebabCase,
"pascal": PascalCase,
"pascalcase": PascalCase,
"pascal-case": PascalCase,
"pascal_case": PascalCase,
"pascal case": PascalCase,
"snake": SnakeCase,
"snakecase": SnakeCase,
"snake-case": SnakeCase,
"snake_case": SnakeCase,
"snake case": SnakeCase,
}
func Parse(s string) (string, error) {
s = strings.ToLower(stringsutil.CondenseSpace(s))
if caseConst, ok := mapCaseConst[s]; ok {
return caseConst, nil
}
return "", errorsutil.Wrapf(ErrUnknownCaseString, "case (%s) not parsed", s)
}
func IsCase(caseType, s string) (bool, error) {
caseTypeCanonical, err := Parse(caseType)
if err != nil {
return false, err
}
switch caseTypeCanonical {
case CamelCase:
return IsCamelCase(s), nil
case KebabCase:
return IsKebabCase(s), nil
case PascalCase:
return IsPascalCase(s), nil
case SnakeCase:
return IsSnakeCase(s), nil
}
return false, fmt.Errorf("unknown string case type [%s]", caseType)
}
var (
rxCamelCase = regexp.MustCompile(`^[a-z][0-9A-Za-z]*$`)
rxKebabCase = regexp.MustCompile(`^[a-z][0-9a-z-]*$`)
rxPascalCase = regexp.MustCompile(`^[A-Z][0-9A-Za-z]*$`)
rxSnakeCase = regexp.MustCompile(`^[a-z][0-9a-z_]*$`)
rxCamelCaseIDSuffix = regexp.MustCompile(`[0-9a-z](I[dD])$`)
rxHypen = regexp.MustCompile(`-`)
rxUnderscore = regexp.MustCompile(`_`)
)
// IsCamelCase returns if a string is camelCase or not.
func IsCamelCase(input string) bool {
if !rxCamelCase.MatchString(input) {
return false
}
m := rxCamelCaseIDSuffix.FindStringSubmatch(input)
if len(m) == 2 {
if m[1] != "Id" {
return false
}
}
return true
}
func IsKebabCase(input string) bool {
return rxKebabCase.MatchString(input)
}
// IsPascalCase returns if a string is PascalCase or not.
func IsPascalCase(input string) bool {
if !rxPascalCase.MatchString(input) {
return false
}
m := rxCamelCaseIDSuffix.FindStringSubmatch(input)
if len(m) == 2 {
if m[1] != "Id" {
return false
}
}
return true
}
func IsSnakeCase(input string) bool {
return rxSnakeCase.MatchString(input)
}
var rxFirstAlphaUpper = regexp.MustCompile(`^[A-Z]`)
// IsFirstAlphaUpper returns if the first character is
// a capital [A-Z] character.
func IsFirstAlphaUpper(s string) bool {
return rxFirstAlphaUpper.MatchString(s)
}