github.com/nikron/prototool@v1.3.0/internal/strs/strs.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // Package strs contains common string manipulation functionality. 22 // 23 // This functionality is not really centralized anywhere in Golang OSS world, 24 // and there are some specific requirements we have. This is used mostly 25 // in the lint package. 26 package strs 27 28 import ( 29 "sort" 30 "strings" 31 "unicode" 32 "unicode/utf8" 33 ) 34 35 // IsCapitalized returns true if is not empty and the first letter is 36 // an uppercase character. 37 func IsCapitalized(s string) bool { 38 if s == "" { 39 return false 40 } 41 r, _ := utf8.DecodeRuneInString(s) 42 return isUpper(r) 43 } 44 45 // IsCamelCase returns false if s is empty or contains any character that is 46 // not between 'A' and 'Z', 'a' and 'z', '0' and '9', or in extraRunes. 47 // It does not care about lowercase or uppercase. 48 func IsCamelCase(s string) bool { 49 if s == "" { 50 return false 51 } 52 for _, c := range s { 53 if !(isLetter(c) || isDigit(c)) { 54 return false 55 } 56 } 57 return true 58 } 59 60 // IsLowerSnakeCase returns true if s only contains lowercase letters, 61 // digits, and/or underscores. s MUST NOT begin or end with an underscore. 62 func IsLowerSnakeCase(s string) bool { 63 if s == "" || s[0] == '_' || s[len(s)-1] == '_' { 64 return false 65 } 66 for _, r := range s { 67 if !(isLower(r) || isDigit(r) || r == '_') { 68 return false 69 } 70 } 71 return true 72 } 73 74 // IsUpperSnakeCase returns true if s only contains uppercase letters, 75 // digits, and/or underscores. s MUST NOT begin or end with an underscore. 76 func IsUpperSnakeCase(s string) bool { 77 if s == "" || s[0] == '_' || s[len(s)-1] == '_' { 78 return false 79 } 80 for _, r := range s { 81 if !(isUpper(r) || isDigit(r) || r == '_') { 82 return false 83 } 84 } 85 return true 86 } 87 88 // ToUpperSnakeCase converts s to UPPER_SNAKE_CASE. 89 func ToUpperSnakeCase(s string) string { 90 return strings.ToUpper(toSnake(s)) 91 } 92 93 // ToUpperCamelCase converts s to UpperCamelCase. 94 // 95 // We use this for files, so any delimiter (_, -, or space) is 96 // used to denote word boundaries, but we trim spaces from the 97 // beginning and end of the string first. 98 // 99 // If a letter is uppercase, it will stay uppercase regardless, 100 // this is for cases of abbreviations. 101 func ToUpperCamelCase(s string) string { 102 output := "" 103 var previous rune 104 for i, c := range strings.TrimSpace(s) { 105 if !isDelimiter(c) { 106 if i == 0 || isDelimiter(previous) || isUpper(c) { 107 output += string(unicode.ToUpper(c)) 108 } else { 109 output += string(unicode.ToLower(c)) 110 } 111 } 112 previous = c 113 } 114 return output 115 } 116 117 // DedupeSort returns s with no duplicates and no empty strings, sorted. 118 // If modifier is not nil, modifier will be applied to each element in s. 119 func DedupeSort(s []string, modifier func(string) string) []string { 120 m := make(map[string]struct{}, len(s)) 121 o := make([]string, 0, len(m)) 122 for _, e := range s { 123 if e == "" { 124 continue 125 } 126 key := e 127 if modifier != nil { 128 key = modifier(e) 129 } 130 if _, ok := m[key]; ok { 131 continue 132 } 133 m[key] = struct{}{} 134 o = append(o, key) 135 } 136 sort.Strings(o) 137 return o 138 } 139 140 // Intersection return the intersection between one and 141 // two, sorted and dropping empty strings. 142 func Intersection(one []string, two []string) []string { 143 m1 := make(map[string]struct{}) 144 for _, e := range one { 145 if e == "" { 146 continue 147 } 148 m1[e] = struct{}{} 149 } 150 m2 := make(map[string]struct{}) 151 for _, e := range two { 152 if e == "" { 153 continue 154 } 155 m2[e] = struct{}{} 156 } 157 for key := range m1 { 158 if _, ok := m2[key]; !ok { 159 delete(m1, key) 160 } 161 } 162 s := make([]string, 0, len(m1)) 163 for key := range m1 { 164 s = append(s, key) 165 } 166 sort.Strings(s) 167 return s 168 } 169 170 // IsLowercase returns true if s is not empty and is all lowercase. 171 func IsLowercase(s string) bool { 172 if s == "" { 173 return false 174 } 175 return strings.ToLower(s) == s 176 } 177 178 // IsUppercase returns true if s is not empty and is all uppercase. 179 func IsUppercase(s string) bool { 180 if s == "" { 181 return false 182 } 183 return strings.ToUpper(s) == s 184 } 185 186 // toSnake converts s to snake_case. 187 // It is assumed s has no spaces. 188 func toSnake(s string) string { 189 output := "" 190 for i, c := range s { 191 if i > 0 && isUpper(c) && output[len(output)-1] != '_' && i < len(s)-1 && !isUpper(rune(s[i+1])) { 192 output += "_" + string(c) 193 } else { 194 output += string(c) 195 } 196 } 197 return output 198 } 199 200 func isLetter(r rune) bool { 201 return isUpper(r) || isLower(r) 202 } 203 204 func isLower(r rune) bool { 205 return 'a' <= r && r <= 'z' 206 } 207 208 func isUpper(r rune) bool { 209 return 'A' <= r && r <= 'Z' 210 } 211 212 func isDigit(r rune) bool { 213 return '0' <= r && r <= '9' 214 } 215 216 func isDelimiter(r rune) bool { 217 return r == '-' || r == '_' || r == ' ' || r == '\t' || r == '\n' || r == '\r' 218 }