github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/strcase/snake.go (about) 1 package strcase 2 3 import ( 4 "strings" 5 ) 6 7 // ToSnake converts a string to snake_case 8 func ToSnake(s string) string { 9 return ToDelimited(s, '_') 10 } 11 12 // ToSnakeUpper converts a string to SCREAMING_SNAKE_CASE 13 func ToSnakeUpper(s string) string { 14 return ToDelimitedScreaming(s, '_', true) 15 } 16 17 // ToKebab converts a string to kebab-case 18 func ToKebab(s string) string { 19 return ToDelimited(s, '-') 20 } 21 22 // ToKebabUpper converts a string to SCREAMING-KEBAB-CASE 23 func ToKebabUpper(s string) string { 24 return ToDelimitedScreaming(s, '-', true) 25 } 26 27 // ToDelimited converts a string to delimited.snake.case (in this case `del = '.'`) 28 func ToDelimited(s string, del uint8) string { 29 return ToDelimitedScreaming(s, del, false) 30 } 31 32 // ToDelimitedUpper converts a string to SCREAMING.DELIMITED.SNAKE.CASE 33 // (in this case `del = '.'; screaming = true`) or delimited.snake.case (in this case `del = '.'; screaming = false`) 34 func ToDelimitedUpper(s string, del uint8) string { 35 return ToDelimitedScreaming(s, del, true) 36 } 37 38 // ToDelimitedScreaming converts a string to SCREAMING.DELIMITED.SNAKE.CASE 39 // (in this case `del = '.'; screaming = true`) or delimited.snake.case (in this case `del = '.'; screaming = false`) 40 func ToDelimitedScreaming(s string, del uint8, screaming bool) string { 41 s = addWordBoundariesToNumbers(s) 42 s = strings.Trim(s, " ") 43 n := "" 44 45 for i, v := range s { 46 // treat acronyms as words, eg for JSONData -> JSON is a whole word 47 nextCaseIsChanged := false 48 49 if i+1 < len(s) { 50 next := s[i+1] 51 if isCaseChanged(v, int32(next)) { 52 nextCaseIsChanged = true 53 } 54 } 55 56 switch { 57 case i > 0 && n[len(n)-1] != del && nextCaseIsChanged: 58 // add underscore if next letter case type is changed 59 if IsA2Z(v) { 60 n += string(del) + string(v) 61 } else if Isa2z(v) { 62 n += string(v) + string(del) 63 } 64 case anyOf(v, ' ', '_', '-'): 65 if len(n) > 0 && n[len(n)-1] == del { 66 continue 67 } 68 // replace spaces/underscores with delimiters 69 n += string(del) 70 default: 71 n += string(v) 72 } 73 } 74 75 if screaming { 76 return strings.ToUpper(n) 77 } 78 79 return strings.ToLower(n) 80 } 81 82 func anyOf(v int32, oneOfs ...int32) bool { 83 for _, one := range oneOfs { 84 if v == one { 85 return true 86 } 87 } 88 89 return false 90 } 91 92 func isCaseChanged(v, next int32) bool { 93 return IsA2Z(v) && Isa2z(next) || 94 Isa2z(v) && IsA2Z(next) || 95 Is029(v) && IsA2Z(next) 96 } 97 98 // Is029 tells v is 0-9 99 func Is029(v int32) bool { 100 return v >= '0' && v <= '9' 101 } 102 103 // Isa2z tells v is a-z 104 func Isa2z(v int32) bool { 105 return v >= 'a' && v <= 'z' 106 } 107 108 // IsA2Z tells v is A-Z 109 func IsA2Z(v int32) bool { 110 return v >= 'A' && v <= 'Z' 111 }