github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/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 func CaseCamel(s string) string { 111 return toCamelInitCase(s, true) 112 } 113 114 // CaseCamelLower converts a string to lowerCamelCase. 115 // 116 // Example: 117 // CaseCamelLower("any_kind_of_string") -> anyKindOfString 118 func CaseCamelLower(s string) string { 119 if s == "" { 120 return s 121 } 122 if r := rune(s[0]); r >= 'A' && r <= 'Z' { 123 s = strings.ToLower(string(r)) + s[1:] 124 } 125 return toCamelInitCase(s, false) 126 } 127 128 // CaseSnake converts a string to snake_case. 129 // 130 // Example: 131 // CaseSnake("AnyKindOfString") -> any_kind_of_string 132 func CaseSnake(s string) string { 133 return CaseDelimited(s, '_') 134 } 135 136 // CaseSnakeScreaming converts a string to SNAKE_CASE_SCREAMING. 137 // 138 // Example: 139 // CaseSnakeScreaming("AnyKindOfString") -> ANY_KIND_OF_STRING 140 func CaseSnakeScreaming(s string) string { 141 return CaseDelimitedScreaming(s, '_', true) 142 } 143 144 // CaseSnakeFirstUpper converts a string like "RGBCodeMd5" to "rgb_code_md5". 145 // TODO for efficiency should change regexp to traversing string in future. 146 // 147 // Example: 148 // CaseSnakeFirstUpper("RGBCodeMd5") -> rgb_code_md5 149 func CaseSnakeFirstUpper(word string, underscore ...string) string { 150 replace := "_" 151 if len(underscore) > 0 { 152 replace = underscore[0] 153 } 154 155 m := firstCamelCaseEnd.FindAllStringSubmatch(word, 1) 156 if len(m) > 0 { 157 word = m[0][1] + replace + TrimLeft(ToLower(m[0][2]), replace) 158 } 159 160 for { 161 m = firstCamelCaseStart.FindAllStringSubmatch(word, 1) 162 if len(m) > 0 && m[0][1] != "" { 163 w := strings.ToLower(m[0][1]) 164 w = w[:len(w)-1] + replace + string(w[len(w)-1]) 165 166 word = strings.Replace(word, m[0][1], w, 1) 167 } else { 168 break 169 } 170 } 171 172 return TrimLeft(word, replace) 173 } 174 175 // CaseKebab converts a string to kebab-case. 176 // 177 // Example: 178 // CaseKebab("AnyKindOfString") -> any-kind-of-string 179 func CaseKebab(s string) string { 180 return CaseDelimited(s, '-') 181 } 182 183 // CaseKebabScreaming converts a string to KEBAB-CASE-SCREAMING. 184 // 185 // Example: 186 // CaseKebab("AnyKindOfString") -> ANY-KIND-OF-STRING 187 func CaseKebabScreaming(s string) string { 188 return CaseDelimitedScreaming(s, '-', true) 189 } 190 191 // CaseDelimited converts a string to snake.case.delimited. 192 // 193 // Example: 194 // CaseDelimited("AnyKindOfString", '.') -> any.kind.of.string 195 func CaseDelimited(s string, del byte) string { 196 return CaseDelimitedScreaming(s, del, false) 197 } 198 199 // CaseDelimitedScreaming converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. 200 // 201 // Example: 202 // CaseDelimitedScreaming("AnyKindOfString", '.') -> ANY.KIND.OF.STRING 203 func CaseDelimitedScreaming(s string, del uint8, screaming bool) string { 204 s = addWordBoundariesToNumbers(s) 205 s = strings.Trim(s, " ") 206 n := "" 207 for i, v := range s { 208 // treat acronyms as words, eg for JSONData -> JSON is a whole word 209 nextCaseIsChanged := false 210 if i+1 < len(s) { 211 next := s[i+1] 212 if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') { 213 nextCaseIsChanged = true 214 } 215 } 216 217 if i > 0 && n[len(n)-1] != del && nextCaseIsChanged { 218 // add underscore if next letter case type is changed 219 if v >= 'A' && v <= 'Z' { 220 n += string(del) + string(v) 221 } else if v >= 'a' && v <= 'z' { 222 n += string(v) + string(del) 223 } 224 } else if v == ' ' || v == '_' || v == '-' || v == '.' { 225 // replace spaces/underscores with delimiters 226 n += string(del) 227 } else { 228 n = n + string(v) 229 } 230 } 231 232 if screaming { 233 n = strings.ToUpper(n) 234 } else { 235 n = strings.ToLower(n) 236 } 237 return n 238 } 239 240 func addWordBoundariesToNumbers(s string) string { 241 r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte { 242 var result []byte 243 match := numberSequence.FindSubmatch(bytes) 244 if len(match[1]) > 0 { 245 result = append(result, match[1]...) 246 result = append(result, []byte(" ")...) 247 } 248 result = append(result, match[2]...) 249 if len(match[3]) > 0 { 250 result = append(result, []byte(" ")...) 251 result = append(result, match[3]...) 252 } 253 return result 254 }) 255 return string(r) 256 } 257 258 // Converts a string to CamelCase 259 func toCamelInitCase(s string, initCase bool) string { 260 s = addWordBoundariesToNumbers(s) 261 s = strings.Trim(s, " ") 262 n := "" 263 capNext := initCase 264 for _, v := range s { 265 if v >= 'A' && v <= 'Z' { 266 n += string(v) 267 } 268 if v >= '0' && v <= '9' { 269 n += string(v) 270 } 271 if v >= 'a' && v <= 'z' { 272 if capNext { 273 n += strings.ToUpper(string(v)) 274 } else { 275 n += string(v) 276 } 277 } 278 if v == '_' || v == ' ' || v == '-' || v == '.' { 279 capNext = true 280 } else { 281 capNext = false 282 } 283 } 284 return n 285 }