github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/amino/genproto/stringutil/snakecase.go (about) 1 // Copyright 2020-2023 Buf Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // This file has ToLowerSnakeCase which the Protobuf linter uses to make 16 // the expected spelling of a message field name. It is a copy of: 17 // https://github.com/bufbuild/buf/blob/main/private/pkg/stringutil/stringutil.go 18 19 package stringutil 20 21 import ( 22 "sort" 23 "strings" 24 "unicode" 25 ) 26 27 // TrimLines splits the output into individual lines and trims the spaces from each line. 28 // 29 // This also trims the start and end spaces from the original output. 30 func TrimLines(output string) string { 31 return strings.TrimSpace(strings.Join(SplitTrimLines(output), "\n")) 32 } 33 34 // SplitTrimLines splits the output into individual lines and trims the spaces from each line. 35 func SplitTrimLines(output string) []string { 36 // this should work for windows as well as \r will be trimmed 37 split := strings.Split(output, "\n") 38 lines := make([]string, len(split)) 39 for i, line := range split { 40 lines[i] = strings.TrimSpace(line) 41 } 42 return lines 43 } 44 45 // SplitTrimLinesNoEmpty splits the output into individual lines and trims the spaces from each line. 46 // 47 // This removes any empty lines. 48 func SplitTrimLinesNoEmpty(output string) []string { 49 // this should work for windows as well as \r will be trimmed 50 split := strings.Split(output, "\n") 51 lines := make([]string, 0, len(split)) 52 for _, line := range split { 53 line = strings.TrimSpace(line) 54 if line != "" { 55 lines = append(lines, line) 56 } 57 } 58 return lines 59 } 60 61 // MapToSortedSlice transforms m to a sorted slice. 62 func MapToSortedSlice(m map[string]struct{}) []string { 63 s := MapToSlice(m) 64 sort.Strings(s) 65 return s 66 } 67 68 // MapToSlice transforms m to a slice. 69 func MapToSlice(m map[string]struct{}) []string { 70 s := make([]string, 0, len(m)) 71 for e := range m { 72 s = append(s, e) 73 } 74 return s 75 } 76 77 // SliceToMap transforms s to a map. 78 func SliceToMap(s []string) map[string]struct{} { 79 m := make(map[string]struct{}, len(s)) 80 for _, e := range s { 81 m[e] = struct{}{} 82 } 83 return m 84 } 85 86 // SliceToUniqueSortedSlice returns a sorted copy of s with no duplicates. 87 func SliceToUniqueSortedSlice(s []string) []string { 88 return MapToSortedSlice(SliceToMap(s)) 89 } 90 91 // SliceToUniqueSortedSliceFilterEmptyStrings returns a sorted copy of s with no duplicates and no empty strings. 92 // 93 // Strings with only spaces are considered empty. 94 func SliceToUniqueSortedSliceFilterEmptyStrings(s []string) []string { 95 m := SliceToMap(s) 96 for key := range m { 97 if strings.TrimSpace(key) == "" { 98 delete(m, key) 99 } 100 } 101 return MapToSortedSlice(m) 102 } 103 104 // SliceToChunks splits s into chunks of the given chunk size. 105 // 106 // If s is nil or empty, returns empty. 107 // If chunkSize is <=0, returns [][]string{s}. 108 func SliceToChunks(s []string, chunkSize int) [][]string { 109 var chunks [][]string 110 if len(s) == 0 { 111 return chunks 112 } 113 if chunkSize <= 0 { 114 return [][]string{s} 115 } 116 c := make([]string, len(s)) 117 copy(c, s) 118 // https://github.com/golang/go/wiki/SliceTricks#batching-with-minimal-allocation 119 for chunkSize < len(c) { 120 c, chunks = c[chunkSize:], append(chunks, c[0:chunkSize:chunkSize]) 121 } 122 return append(chunks, c) 123 } 124 125 // SliceElementsEqual returns true if the two slices have equal elements. 126 // 127 // Nil and empty slices are treated as equals. 128 func SliceElementsEqual(one []string, two []string) bool { 129 if len(one) != len(two) { 130 return false 131 } 132 for i, elem := range one { 133 if two[i] != elem { 134 return false 135 } 136 } 137 return true 138 } 139 140 // SliceElementsContained returns true if superset contains subset. 141 // 142 // Nil and empty slices are treated as equals. 143 func SliceElementsContained(superset []string, subset []string) bool { 144 m := SliceToMap(superset) 145 for _, elem := range subset { 146 if _, ok := m[elem]; !ok { 147 return false 148 } 149 } 150 return true 151 } 152 153 // JoinSliceQuoted joins the slice with quotes. 154 func JoinSliceQuoted(s []string, sep string) string { 155 if len(s) == 0 { 156 return "" 157 } 158 return `"` + strings.Join(s, `"`+sep+`"`) + `"` 159 } 160 161 // SliceToString prints the slice as [e1,e2]. 162 func SliceToString(s []string) string { 163 if len(s) == 0 { 164 return "" 165 } 166 return "[" + strings.Join(s, ",") + "]" 167 } 168 169 // SliceToHumanString prints the slice as "e1, e2, and e3". 170 func SliceToHumanString(s []string) string { 171 switch len(s) { 172 case 0: 173 return "" 174 case 1: 175 return s[0] 176 case 2: 177 return s[0] + " and " + s[1] 178 default: 179 return strings.Join(s[:len(s)-1], ", ") + ", and " + s[len(s)-1] 180 } 181 } 182 183 // SliceToHumanStringQuoted prints the slice as `"e1", "e2", and "e3"`. 184 func SliceToHumanStringQuoted(s []string) string { 185 switch len(s) { 186 case 0: 187 return "" 188 case 1: 189 return `"` + s[0] + `"` 190 case 2: 191 return `"` + s[0] + `" and "` + s[1] + `"` 192 default: 193 return `"` + strings.Join(s[:len(s)-1], `", "`) + `", and "` + s[len(s)-1] + `"` 194 } 195 } 196 197 // SliceToHumanStringOr prints the slice as "e1, e2, or e3". 198 func SliceToHumanStringOr(s []string) string { 199 switch len(s) { 200 case 0: 201 return "" 202 case 1: 203 return s[0] 204 case 2: 205 return s[0] + " or " + s[1] 206 default: 207 return strings.Join(s[:len(s)-1], ", ") + ", or " + s[len(s)-1] 208 } 209 } 210 211 // SliceToHumanStringOrQuoted prints the slice as `"e1", "e2", or "e3"`. 212 func SliceToHumanStringOrQuoted(s []string) string { 213 switch len(s) { 214 case 0: 215 return "" 216 case 1: 217 return `"` + s[0] + `"` 218 case 2: 219 return `"` + s[0] + `" or "` + s[1] + `"` 220 default: 221 return `"` + strings.Join(s[:len(s)-1], `", "`) + `", or "` + s[len(s)-1] + `"` 222 } 223 } 224 225 // SnakeCaseOption is an option for snake_case conversions. 226 type SnakeCaseOption func(*snakeCaseOptions) 227 228 // SnakeCaseWithNewWordOnDigits is a SnakeCaseOption that signifies 229 // to split on digits, ie foo_bar_1 instead of foo_bar1. 230 func SnakeCaseWithNewWordOnDigits() SnakeCaseOption { 231 return func(snakeCaseOptions *snakeCaseOptions) { 232 snakeCaseOptions.newWordOnDigits = true 233 } 234 } 235 236 // ToLowerSnakeCase transforms s to lower_snake_case. 237 func ToLowerSnakeCase(s string, options ...SnakeCaseOption) string { 238 return strings.ToLower(toSnakeCase(s, options...)) 239 } 240 241 // ToUpperSnakeCase transforms s to UPPER_SNAKE_CASE. 242 func ToUpperSnakeCase(s string, options ...SnakeCaseOption) string { 243 return strings.ToUpper(toSnakeCase(s, options...)) 244 } 245 246 // ToPascalCase converts s to PascalCase. 247 // 248 // Splits on '-', '_', ' ', '\t', '\n', '\r'. 249 // Uppercase letters will stay uppercase, 250 func ToPascalCase(s string) string { 251 output := "" 252 var previous rune 253 for i, c := range strings.TrimSpace(s) { 254 if !isDelimiter(c) { 255 if i == 0 || isDelimiter(previous) || unicode.IsUpper(c) { 256 output += string(unicode.ToUpper(c)) 257 } else { 258 output += string(unicode.ToLower(c)) 259 } 260 } 261 previous = c 262 } 263 return output 264 } 265 266 // IsAlphanumeric returns true for [0-9a-zA-Z]. 267 func IsAlphanumeric(r rune) bool { 268 return IsNumeric(r) || IsAlpha(r) 269 } 270 271 // IsAlpha returns true for [a-zA-Z]. 272 func IsAlpha(r rune) bool { 273 return IsLowerAlpha(r) || IsUpperAlpha(r) 274 } 275 276 // IsLowerAlpha returns true for [a-z]. 277 func IsLowerAlpha(r rune) bool { 278 return 'a' <= r && r <= 'z' 279 } 280 281 // IsUpperAlpha returns true for [A-Z]. 282 func IsUpperAlpha(r rune) bool { 283 return 'A' <= r && r <= 'Z' 284 } 285 286 // IsNumeric returns true for [0-9]. 287 func IsNumeric(r rune) bool { 288 return '0' <= r && r <= '9' 289 } 290 291 // IsLowerAlphanumeric returns true for [0-9a-z]. 292 func IsLowerAlphanumeric(r rune) bool { 293 return IsNumeric(r) || IsLowerAlpha(r) 294 } 295 296 func toSnakeCase(s string, options ...SnakeCaseOption) string { 297 snakeCaseOptions := &snakeCaseOptions{} 298 for _, option := range options { 299 option(snakeCaseOptions) 300 } 301 output := "" 302 s = strings.TrimFunc(s, isDelimiter) 303 for i, c := range s { 304 if isDelimiter(c) { 305 c = '_' 306 } 307 if i == 0 { 308 output += string(c) 309 } else if isSnakeCaseNewWord(c, snakeCaseOptions.newWordOnDigits) && 310 output[len(output)-1] != '_' && 311 ((i < len(s)-1 && !isSnakeCaseNewWord(rune(s[i+1]), true) && !isDelimiter(rune(s[i+1]))) || 312 (snakeCaseOptions.newWordOnDigits && unicode.IsDigit(c)) || 313 (unicode.IsLower(rune(s[i-1])))) { 314 output += "_" + string(c) 315 } else if !(isDelimiter(c) && output[len(output)-1] == '_') { 316 output += string(c) 317 } 318 } 319 return output 320 } 321 322 func isSnakeCaseNewWord(r rune, newWordOnDigits bool) bool { 323 if newWordOnDigits { 324 return unicode.IsUpper(r) || unicode.IsDigit(r) 325 } 326 return unicode.IsUpper(r) 327 } 328 329 func isDelimiter(r rune) bool { 330 return r == '.' || r == '-' || r == '_' || r == ' ' || r == '\t' || r == '\n' || r == '\r' 331 } 332 333 type snakeCaseOptions struct { 334 newWordOnDigits bool 335 }