github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/linter/strs/strs.go (about) 1 // Package strs contains common string manipulation functionality. 2 package strs 3 4 import ( 5 "strings" 6 "unicode" 7 "unicode/utf8" 8 ) 9 10 // IsUpperCamelCase returns true if s is not empty and is camel case with an initial capital. 11 func IsUpperCamelCase(s string) bool { 12 if !isCapitalized(s) { 13 return false 14 } 15 return isCamelCase(s) 16 } 17 18 // IsLowerCamelCase returns true if s is not empty and is camel case without an initial capital. 19 func IsLowerCamelCase(s string) bool { 20 if isCapitalized(s) { 21 return false 22 } 23 return isCamelCase(s) 24 } 25 26 // IsUpperSnakeCase returns true if s only contains uppercase letters, 27 // digits, and/or underscores. s MUST NOT begin or end with an underscore. 28 func IsUpperSnakeCase(s string) bool { 29 if s == "" || s[0] == '_' || s[len(s)-1] == '_' { 30 return false 31 } 32 for _, r := range s { 33 if !(isUpper(r) || isDigit(r) || r == '_') { 34 return false 35 } 36 } 37 return true 38 } 39 40 // IsLowerSnakeCase returns true if s only contains lowercase letters, 41 // digits, and/or underscores. s MUST NOT begin or end with an underscore. 42 func IsLowerSnakeCase(s string) bool { 43 if s == "" || s[0] == '_' || s[len(s)-1] == '_' { 44 return false 45 } 46 for _, r := range s { 47 if !(isLower(r) || isDigit(r) || r == '_') { 48 return false 49 } 50 } 51 return true 52 } 53 54 // isCapitalized returns true if is not empty and the first letter is 55 // an uppercase character. 56 func isCapitalized(s string) bool { 57 if s == "" { 58 return false 59 } 60 r, _ := utf8.DecodeRuneInString(s) 61 return isUpper(r) 62 } 63 64 // isCamelCase returns false if s is empty or contains any character that is 65 // not between 'A' and 'Z', 'a' and 'z', '0' and '9', or in extraRunes. 66 // It does not care about lowercase or uppercase. 67 func isCamelCase(s string) bool { 68 if s == "" { 69 return false 70 } 71 for _, c := range s { 72 if !(isLetter(c) || isDigit(c)) { 73 return false 74 } 75 } 76 return true 77 } 78 79 // isSnake returns true if s only contains letters, digits, and/or underscores. 80 // s MUST NOT begin or end with an underscore. 81 func isSnake(s string) bool { 82 if s == "" || s[0] == '_' || s[len(s)-1] == '_' { 83 return false 84 } 85 for _, r := range s { 86 if !(isLetter(r) || isDigit(r) || r == '_') { 87 return false 88 } 89 } 90 return true 91 } 92 93 // HasAnyUpperCase returns true if s contains any of characters in the range A-Z. 94 func HasAnyUpperCase(s string) bool { 95 for _, r := range s { 96 if isUpper(r) { 97 return true 98 } 99 } 100 return false 101 } 102 103 // ToUpperSnakeCase converts s to UPPER_SNAKE_CASE. 104 func ToUpperSnakeCase(s string) string { 105 s = strings.ReplaceAll(s, ".", "_") 106 s = strings.ReplaceAll(s, "-", "_") 107 ws := SplitCamelCaseWord(s) 108 if ws == nil { 109 ws = []string{s} 110 } 111 return strings.ToUpper( 112 strings.Join(ws, "_"), 113 ) 114 } 115 116 // ToLowerSnakeCase converts s to lower_snake_case. 117 func ToLowerSnakeCase(s string) string { 118 s = strings.ReplaceAll(s, ".", "_") 119 s = strings.ReplaceAll(s, "-", "_") 120 ws := SplitCamelCaseWord(s) 121 if ws == nil { 122 ws = []string{s} 123 } 124 return strings.ToLower( 125 strings.Join(ws, "_"), 126 ) 127 } 128 129 // ToUpperCamelCase converts s to UpperCamelCase. 130 func ToUpperCamelCase(s string) string { 131 if IsUpperSnakeCase(s) { 132 s = strings.ToLower(s) 133 } 134 135 var output string 136 for _, w := range SplitSnakeCaseWord(s) { 137 output += strings.Title(w) 138 } 139 return output 140 } 141 142 // ToLowerCamelCase converts s to lowerCamelCase. 143 func ToLowerCamelCase(s string) string { 144 var output string 145 for i, r := range ToUpperCamelCase(s) { 146 if i == 0 { 147 output += string(unicode.ToLower(r)) 148 } else { 149 output += string(r) 150 } 151 } 152 return output 153 } 154 155 // toSnake converts s to snake_case. 156 func toSnake(s string) string { 157 output := "" 158 s = strings.TrimSpace(s) 159 priorUpperN := 0 160 for _, c := range s { 161 if isLower(c) { 162 if 2 < priorUpperN { 163 output = output[:len(output)-1] + "_" + output[len(output)-1:] 164 } 165 } else if priorUpperN == 0 && len(output) > 0 { 166 output += "_" 167 } 168 output += string(c) 169 if isUpper(c) { 170 priorUpperN += 1 171 } else { 172 priorUpperN = 0 173 } 174 } 175 return output 176 } 177 178 // SplitCamelCaseWord splits a CamelCase word into its parts. 179 // 180 // If s is empty, returns nil. 181 // If s is not CamelCase, returns nil. 182 func SplitCamelCaseWord(s string) []string { 183 if s == "" { 184 return nil 185 } 186 s = strings.TrimSpace(s) 187 if !isCamelCase(s) { 188 return nil 189 } 190 return SplitSnakeCaseWord(toSnake(s)) 191 } 192 193 // SplitSnakeCaseWord splits a snake_case word into its parts. 194 // 195 // If s is empty, returns nil. 196 // If s is not snake_case, returns nil. 197 func SplitSnakeCaseWord(s string) []string { 198 if s == "" { 199 return nil 200 } 201 s = strings.TrimSpace(s) 202 if !isSnake(s) { 203 return nil 204 } 205 return strings.Split(s, "_") 206 } 207 208 func isLetter(r rune) bool { 209 return isUpper(r) || isLower(r) 210 } 211 212 func isLower(r rune) bool { 213 return 'a' <= r && r <= 'z' 214 } 215 216 func isUpper(r rune) bool { 217 return 'A' <= r && r <= 'Z' 218 } 219 220 func isDigit(r rune) bool { 221 return '0' <= r && r <= '9' 222 }