1// Copyright 2015 go-swagger maintainers 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 15package swag 16 17import ( 18 "reflect" 19 "strings" 20 "unicode" 21) 22 23// commonInitialisms are common acronyms that are kept as whole uppercased words. 24var commonInitialisms *indexOfInitialisms 25 26// initialisms is a slice of sorted initialisms 27var initialisms []string 28 29var isInitialism func(string) bool 30 31// GoNamePrefixFunc sets an optional rule to prefix go names 32// which do not start with a letter. 33// 34// e.g. to help converting "123" into "{prefix}123" 35// 36// The default is to prefix with "X" 37var GoNamePrefixFunc func(string) string 38 39func init() { 40 // Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769 41 var configuredInitialisms = map[string]bool{ 42 "ACL": true, 43 "API": true, 44 "ASCII": true, 45 "CPU": true, 46 "CSS": true, 47 "DNS": true, 48 "EOF": true, 49 "GUID": true, 50 "HTML": true, 51 "HTTPS": true, 52 "HTTP": true, 53 "ID": true, 54 "IP": true, 55 "IPv4": true, 56 "IPv6": true, 57 "JSON": true, 58 "LHS": true, 59 "OAI": true, 60 "QPS": true, 61 "RAM": true, 62 "RHS": true, 63 "RPC": true, 64 "SLA": true, 65 "SMTP": true, 66 "SQL": true, 67 "SSH": true, 68 "TCP": true, 69 "TLS": true, 70 "TTL": true, 71 "UDP": true, 72 "UI": true, 73 "UID": true, 74 "UUID": true, 75 "URI": true, 76 "URL": true, 77 "UTF8": true, 78 "VM": true, 79 "XML": true, 80 "XMPP": true, 81 "XSRF": true, 82 "XSS": true, 83 } 84 85 // a thread-safe index of initialisms 86 commonInitialisms = newIndexOfInitialisms().load(configuredInitialisms) 87 initialisms = commonInitialisms.sorted() 88 89 // a test function 90 isInitialism = commonInitialisms.isInitialism 91} 92 93const ( 94 //collectionFormatComma = "csv" 95 collectionFormatSpace = "ssv" 96 collectionFormatTab = "tsv" 97 collectionFormatPipe = "pipes" 98 collectionFormatMulti = "multi" 99) 100 101// JoinByFormat joins a string array by a known format (e.g. swagger's collectionFormat attribute): 102// ssv: space separated value 103// tsv: tab separated value 104// pipes: pipe (|) separated value 105// csv: comma separated value (default) 106func JoinByFormat(data []string, format string) []string { 107 if len(data) == 0 { 108 return data 109 } 110 var sep string 111 switch format { 112 case collectionFormatSpace: 113 sep = " " 114 case collectionFormatTab: 115 sep = "\t" 116 case collectionFormatPipe: 117 sep = "|" 118 case collectionFormatMulti: 119 return data 120 default: 121 sep = "," 122 } 123 return []string{strings.Join(data, sep)} 124} 125 126// SplitByFormat splits a string by a known format: 127// ssv: space separated value 128// tsv: tab separated value 129// pipes: pipe (|) separated value 130// csv: comma separated value (default) 131// 132func SplitByFormat(data, format string) []string { 133 if data == "" { 134 return nil 135 } 136 var sep string 137 switch format { 138 case collectionFormatSpace: 139 sep = " " 140 case collectionFormatTab: 141 sep = "\t" 142 case collectionFormatPipe: 143 sep = "|" 144 case collectionFormatMulti: 145 return nil 146 default: 147 sep = "," 148 } 149 var result []string 150 for _, s := range strings.Split(data, sep) { 151 if ts := strings.TrimSpace(s); ts != "" { 152 result = append(result, ts) 153 } 154 } 155 return result 156} 157 158type byInitialism []string 159 160func (s byInitialism) Len() int { 161 return len(s) 162} 163func (s byInitialism) Swap(i, j int) { 164 s[i], s[j] = s[j], s[i] 165} 166func (s byInitialism) Less(i, j int) bool { 167 if len(s[i]) != len(s[j]) { 168 return len(s[i]) < len(s[j]) 169 } 170 171 return strings.Compare(s[i], s[j]) > 0 172} 173 174// Removes leading whitespaces 175func trim(str string) string { 176 return strings.Trim(str, " ") 177} 178 179// Shortcut to strings.ToUpper() 180func upper(str string) string { 181 return strings.ToUpper(trim(str)) 182} 183 184// Shortcut to strings.ToLower() 185func lower(str string) string { 186 return strings.ToLower(trim(str)) 187} 188 189// Camelize an uppercased word 190func Camelize(word string) (camelized string) { 191 for pos, ru := range []rune(word) { 192 if pos > 0 { 193 camelized += string(unicode.ToLower(ru)) 194 } else { 195 camelized += string(unicode.ToUpper(ru)) 196 } 197 } 198 return 199} 200 201// ToFileName lowercases and underscores a go type name 202func ToFileName(name string) string { 203 in := split(name) 204 out := make([]string, 0, len(in)) 205 206 for _, w := range in { 207 out = append(out, lower(w)) 208 } 209 210 return strings.Join(out, "_") 211} 212 213// ToCommandName lowercases and underscores a go type name 214func ToCommandName(name string) string { 215 in := split(name) 216 out := make([]string, 0, len(in)) 217 218 for _, w := range in { 219 out = append(out, lower(w)) 220 } 221 return strings.Join(out, "-") 222} 223 224// ToHumanNameLower represents a code name as a human series of words 225func ToHumanNameLower(name string) string { 226 in := newSplitter(withPostSplitInitialismCheck).split(name) 227 out := make([]string, 0, len(in)) 228 229 for _, w := range in { 230 if !w.IsInitialism() { 231 out = append(out, lower(w.GetOriginal())) 232 } else { 233 out = append(out, w.GetOriginal()) 234 } 235 } 236 237 return strings.Join(out, " ") 238} 239 240// ToHumanNameTitle represents a code name as a human series of words with the first letters titleized 241func ToHumanNameTitle(name string) string { 242 in := newSplitter(withPostSplitInitialismCheck).split(name) 243 244 out := make([]string, 0, len(in)) 245 for _, w := range in { 246 original := w.GetOriginal() 247 if !w.IsInitialism() { 248 out = append(out, Camelize(original)) 249 } else { 250 out = append(out, original) 251 } 252 } 253 return strings.Join(out, " ") 254} 255 256// ToJSONName camelcases a name which can be underscored or pascal cased 257func ToJSONName(name string) string { 258 in := split(name) 259 out := make([]string, 0, len(in)) 260 261 for i, w := range in { 262 if i == 0 { 263 out = append(out, lower(w)) 264 continue 265 } 266 out = append(out, Camelize(w)) 267 } 268 return strings.Join(out, "") 269} 270 271// ToVarName camelcases a name which can be underscored or pascal cased 272func ToVarName(name string) string { 273 res := ToGoName(name) 274 if isInitialism(res) { 275 return lower(res) 276 } 277 if len(res) <= 1 { 278 return lower(res) 279 } 280 return lower(res[:1]) + res[1:] 281} 282 283// ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes 284func ToGoName(name string) string { 285 lexems := newSplitter(withPostSplitInitialismCheck).split(name) 286 287 result := "" 288 for _, lexem := range lexems { 289 goName := lexem.GetUnsafeGoName() 290 291 // to support old behavior 292 if lexem.IsInitialism() { 293 goName = upper(goName) 294 } 295 result += goName 296 } 297 298 if len(result) > 0 { 299 // Only prefix with X when the first character isn't an ascii letter 300 first := []rune(result)[0] 301 if !unicode.IsLetter(first) || (first > unicode.MaxASCII && !unicode.IsUpper(first)) { 302 if GoNamePrefixFunc == nil { 303 return "X" + result 304 } 305 result = GoNamePrefixFunc(name) + result 306 } 307 first = []rune(result)[0] 308 if unicode.IsLetter(first) && !unicode.IsUpper(first) { 309 result = string(append([]rune{unicode.ToUpper(first)}, []rune(result)[1:]...)) 310 } 311 } 312 313 return result 314} 315 316// ContainsStrings searches a slice of strings for a case-sensitive match 317func ContainsStrings(coll []string, item string) bool { 318 for _, a := range coll { 319 if a == item { 320 return true 321 } 322 } 323 return false 324} 325 326// ContainsStringsCI searches a slice of strings for a case-insensitive match 327func ContainsStringsCI(coll []string, item string) bool { 328 for _, a := range coll { 329 if strings.EqualFold(a, item) { 330 return true 331 } 332 } 333 return false 334} 335 336type zeroable interface { 337 IsZero() bool 338} 339 340// IsZero returns true when the value passed into the function is a zero value. 341// This allows for safer checking of interface values. 342func IsZero(data interface{}) bool { 343 // check for things that have an IsZero method instead 344 if vv, ok := data.(zeroable); ok { 345 return vv.IsZero() 346 } 347 // continue with slightly more complex reflection 348 v := reflect.ValueOf(data) 349 switch v.Kind() { 350 case reflect.String: 351 return v.Len() == 0 352 case reflect.Bool: 353 return !v.Bool() 354 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 355 return v.Int() == 0 356 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 357 return v.Uint() == 0 358 case reflect.Float32, reflect.Float64: 359 return v.Float() == 0 360 case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 361 return v.IsNil() 362 case reflect.Struct, reflect.Array: 363 return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface()) 364 case reflect.Invalid: 365 return true 366 } 367 return false 368} 369 370// AddInitialisms add additional initialisms 371func AddInitialisms(words ...string) { 372 for _, word := range words { 373 //commonInitialisms[upper(word)] = true 374 commonInitialisms.add(upper(word)) 375 } 376 // sort again 377 initialisms = commonInitialisms.sorted() 378} 379 380// CommandLineOptionsGroup represents a group of user-defined command line options 381type CommandLineOptionsGroup struct { 382 ShortDescription string 383 LongDescription string 384 Options interface{} 385} 386