github.com/gogf/gf/v2@v2.7.4/text/gstr/gstr_case.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 // 7 // | Function | Result | 8 // |-----------------------------------|--------------------| 9 // | CaseSnake(s) | any_kind_of_string | 10 // | CaseSnakeScreaming(s) | ANY_KIND_OF_STRING | 11 // | CaseSnakeFirstUpper("RGBCodeMd5") | rgb_code_md5 | 12 // | CaseKebab(s) | any-kind-of-string | 13 // | CaseKebabScreaming(s) | ANY-KIND-OF-STRING | 14 // | CaseDelimited(s, '.') | any.kind.of.string | 15 // | CaseDelimitedScreaming(s, '.') | ANY.KIND.OF.STRING | 16 // | CaseCamel(s) | AnyKindOfString | 17 // | CaseCamelLower(s) | anyKindOfString | 18 19 package gstr 20 21 import ( 22 "regexp" 23 "strings" 24 ) 25 26 // CaseType is the type for Case. 27 type CaseType string 28 29 // The case type constants. 30 const ( 31 Camel CaseType = "Camel" 32 CamelLower CaseType = "CamelLower" 33 Snake CaseType = "Snake" 34 SnakeFirstUpper CaseType = "SnakeFirstUpper" 35 SnakeScreaming CaseType = "SnakeScreaming" 36 Kebab CaseType = "Kebab" 37 KebabScreaming CaseType = "KebabScreaming" 38 Lower CaseType = "Lower" 39 ) 40 41 var ( 42 numberSequence = regexp.MustCompile(`([a-zA-Z]{0,1})(\d+)([a-zA-Z]{0,1})`) 43 firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`) 44 firstCamelCaseEnd = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`) 45 ) 46 47 // CaseTypeMatch matches the case type from string. 48 func CaseTypeMatch(caseStr string) CaseType { 49 caseTypes := []CaseType{ 50 Camel, 51 CamelLower, 52 Snake, 53 SnakeFirstUpper, 54 SnakeScreaming, 55 Kebab, 56 KebabScreaming, 57 Lower, 58 } 59 60 for _, caseType := range caseTypes { 61 if Equal(caseStr, string(caseType)) { 62 return caseType 63 } 64 } 65 66 return CaseType(caseStr) 67 } 68 69 // CaseConvert converts a string to the specified naming convention. 70 // Use CaseTypeMatch to match the case type from string. 71 func CaseConvert(s string, caseType CaseType) string { 72 if s == "" || caseType == "" { 73 return s 74 } 75 76 switch caseType { 77 case Camel: 78 return CaseCamel(s) 79 80 case CamelLower: 81 return CaseCamelLower(s) 82 83 case Kebab: 84 return CaseKebab(s) 85 86 case KebabScreaming: 87 return CaseKebabScreaming(s) 88 89 case Snake: 90 return CaseSnake(s) 91 92 case SnakeFirstUpper: 93 return CaseSnakeFirstUpper(s) 94 95 case SnakeScreaming: 96 return CaseSnakeScreaming(s) 97 98 case Lower: 99 return ToLower(s) 100 101 default: 102 return s 103 } 104 } 105 106 // CaseCamel converts a string to CamelCase. 107 // 108 // Example: 109 // CaseCamel("any_kind_of_string") -> AnyKindOfString 110 // CaseCamel("anyKindOfString") -> AnyKindOfString 111 func CaseCamel(s string) string { 112 return toCamelInitCase(s, true) 113 } 114 115 // CaseCamelLower converts a string to lowerCamelCase. 116 // 117 // Example: 118 // CaseCamelLower("any_kind_of_string") -> anyKindOfString 119 // CaseCamelLower("AnyKindOfString") -> anyKindOfString 120 func CaseCamelLower(s string) string { 121 if s == "" { 122 return s 123 } 124 if r := rune(s[0]); r >= 'A' && r <= 'Z' { 125 s = strings.ToLower(string(r)) + s[1:] 126 } 127 return toCamelInitCase(s, false) 128 } 129 130 // CaseSnake converts a string to snake_case. 131 // 132 // Example: 133 // CaseSnake("AnyKindOfString") -> any_kind_of_string 134 func CaseSnake(s string) string { 135 return CaseDelimited(s, '_') 136 } 137 138 // CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING. 139 // 140 // Example: 141 // CaseSnakeScreaming("AnyKindOfString") -> ANY_KIND_OF_STRING 142 func CaseSnakeScreaming(s string) string { 143 return CaseDelimitedScreaming(s, '_', true) 144 } 145 146 // CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5". 147 // TODO for efficiency should change regexp to traversing string in future. 148 // 149 // Example: 150 // CaseSnakeFirstUpper("RGBCodeMd5") -> rgb_code_md5 151 func CaseSnakeFirstUpper(word string, underscore ...string) string { 152 replace := "_" 153 if len(underscore) > 0 { 154 replace = underscore[0] 155 } 156 157 m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1) 158 if len(m) > 0 { 159 word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace) 160 } 161 162 for { 163 m = firstCamelCaseStart.FindAllStringSubmatch(word, 1) 164 if len(m) > 0 && m[0][1] != "" { 165 w := strings.ToLower(m[0][1]) 166 w = w[:len(w)-1] + replace + string(w[len(w)-1]) 167 168 word = strings.Replace(word, m[0][1], w, 1) 169 } else { 170 break 171 } 172 } 173 174 return TrimLeft(word, replace) 175 } 176 177 // CaseKebab converts a string to kebab-case. 178 // 179 // Example: 180 // CaseKebab("AnyKindOfString") -> any-kind-of-string 181 func CaseKebab(s string) string { 182 return CaseDelimited(s, '-') 183 } 184 185 // CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING. 186 // 187 // Example: 188 // CaseKebab("AnyKindOfString") -> ANY-KIND-OF-STRING 189 func CaseKebabScreaming(s string) string { 190 return CaseDelimitedScreaming(s, '-', true) 191 } 192 193 // CaseDelimited converts a string to snake.case.delimited. 194 // 195 // Example: 196 // CaseDelimited("AnyKindOfString", '.') -> any.kind.of.string 197 func CaseDelimited(s string, del byte) string { 198 return CaseDelimitedScreaming(s, del, false) 199 } 200 201 // CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. 202 // 203 // Example: 204 // CaseDelimitedScreaming("AnyKindOfString", '.') -> ANY.KIND.OF.STRING 205 func CaseDelimitedScreaming(s string, del uint8, screaming bool) string { 206 s = addWordBoundariesToNumbers(s) 207 s = strings.Trim(s, " ") 208 n := "" 209 for i, v := range s { 210 // treat acronyms as words, eg for JSONData -> JSON is a whole word 211 nextCaseIsChanged := false 212 if i+1 < len(s) { 213 next := s[i+1] 214 if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') { 215 nextCaseIsChanged = true 216 } 217 } 218 219 if i > 0 && n[len(n)-1] != del && nextCaseIsChanged { 220 // add underscore if next letter case type is changed 221 if v >= 'A' && v <= 'Z' { 222 n += string(del) + string(v) 223 } else if v >= 'a' && v <= 'z' { 224 n += string(v) + string(del) 225 } 226 } else if v == ' ' || v == '_' || v == '-' || v == '.' { 227 // replace spaces/underscores with delimiters 228 n += string(del) 229 } else { 230 n = n + string(v) 231 } 232 } 233 234 if screaming { 235 n = strings.ToUpper(n) 236 } else { 237 n = strings.ToLower(n) 238 } 239 return n 240 } 241 242 func addWordBoundariesToNumbers(s string) string { 243 r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte { 244 var result []byte 245 match := numberSequence.FindSubmatch(bytes) 246 if len(match[1]) > 0 { 247 result = append(result, match[1]...) 248 result = append(result, []byte(" ")...) 249 } 250 result = append(result, match[2]...) 251 if len(match[3]) > 0 { 252 result = append(result, []byte(" ")...) 253 result = append(result, match[3]...) 254 } 255 return result 256 }) 257 return string(r) 258 } 259 260 // Converts a string to CamelCase 261 func toCamelInitCase(s string, initCase bool) string { 262 s = addWordBoundariesToNumbers(s) 263 s = strings.Trim(s, " ") 264 n := "" 265 capNext := initCase 266 for _, v := range s { 267 if v >= 'A' && v <= 'Z' { 268 n += string(v) 269 } 270 if v >= '0' && v <= '9' { 271 n += string(v) 272 } 273 if v >= 'a' && v <= 'z' { 274 if capNext { 275 n += strings.ToUpper(string(v)) 276 } else { 277 n += string(v) 278 } 279 } 280 if v == '_' || v == ' ' || v == '-' || v == '.' { 281 capNext = true 282 } else { 283 capNext = false 284 } 285 } 286 return n 287 }