blob: 7e0f80a41162c834f7b52bab3302a6b671fbddc9 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001// 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 "math"
19 "reflect"
20 "regexp"
21 "sort"
22 "strings"
23 "sync"
24 "unicode"
25)
26
27// Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769
28var commonInitialisms = map[string]bool{
29 "ACL": true,
30 "API": true,
31 "ASCII": true,
32 "CPU": true,
33 "CSS": true,
34 "DNS": true,
35 "EOF": true,
36 "GUID": true,
37 "HTML": true,
38 "HTTPS": true,
39 "HTTP": true,
40 "ID": true,
41 "IP": true,
42 "JSON": true,
43 "LHS": true,
44 "OAI": true,
45 "QPS": true,
46 "RAM": true,
47 "RHS": true,
48 "RPC": true,
49 "SLA": true,
50 "SMTP": true,
51 "SQL": true,
52 "SSH": true,
53 "TCP": true,
54 "TLS": true,
55 "TTL": true,
56 "UDP": true,
57 "UI": true,
58 "UID": true,
59 "UUID": true,
60 "URI": true,
61 "URL": true,
62 "UTF8": true,
63 "VM": true,
64 "XML": true,
65 "XMPP": true,
66 "XSRF": true,
67 "XSS": true,
68}
69var initialisms []string
70
71var once sync.Once
72
73func sortInitialisms() {
74 for k := range commonInitialisms {
75 initialisms = append(initialisms, k)
76 }
77 sort.Sort(sort.Reverse(byLength(initialisms)))
78}
79
80// JoinByFormat joins a string array by a known format:
81// ssv: space separated value
82// tsv: tab separated value
83// pipes: pipe (|) separated value
84// csv: comma separated value (default)
85func JoinByFormat(data []string, format string) []string {
86 if len(data) == 0 {
87 return data
88 }
89 var sep string
90 switch format {
91 case "ssv":
92 sep = " "
93 case "tsv":
94 sep = "\t"
95 case "pipes":
96 sep = "|"
97 case "multi":
98 return data
99 default:
100 sep = ","
101 }
102 return []string{strings.Join(data, sep)}
103}
104
105// SplitByFormat splits a string by a known format:
106// ssv: space separated value
107// tsv: tab separated value
108// pipes: pipe (|) separated value
109// csv: comma separated value (default)
110func SplitByFormat(data, format string) []string {
111 if data == "" {
112 return nil
113 }
114 var sep string
115 switch format {
116 case "ssv":
117 sep = " "
118 case "tsv":
119 sep = "\t"
120 case "pipes":
121 sep = "|"
122 case "multi":
123 return nil
124 default:
125 sep = ","
126 }
127 var result []string
128 for _, s := range strings.Split(data, sep) {
129 if ts := strings.TrimSpace(s); ts != "" {
130 result = append(result, ts)
131 }
132 }
133 return result
134}
135
136type byLength []string
137
138func (s byLength) Len() int {
139 return len(s)
140}
141func (s byLength) Swap(i, j int) {
142 s[i], s[j] = s[j], s[i]
143}
144func (s byLength) Less(i, j int) bool {
145 return len(s[i]) < len(s[j])
146}
147
148// Prepares strings by splitting by caps, spaces, dashes, and underscore
149func split(str string) (words []string) {
150 repl := strings.NewReplacer(
151 "@", "At ",
152 "&", "And ",
153 "|", "Pipe ",
154 "$", "Dollar ",
155 "!", "Bang ",
156 "-", " ",
157 "_", " ",
158 )
159
160 rex1 := regexp.MustCompile(`(\p{Lu})`)
161 rex2 := regexp.MustCompile(`(\pL|\pM|\pN|\p{Pc})+`)
162
163 str = trim(str)
164
165 // Convert dash and underscore to spaces
166 str = repl.Replace(str)
167
168 // Split when uppercase is found (needed for Snake)
169 str = rex1.ReplaceAllString(str, " $1")
170
171 // check if consecutive single char things make up an initialism
172 once.Do(sortInitialisms)
173 for _, k := range initialisms {
174 str = strings.Replace(str, rex1.ReplaceAllString(k, " $1"), " "+k, -1)
175 }
176 // Get the final list of words
177 words = rex2.FindAllString(str, -1)
178
179 return
180}
181
182// Removes leading whitespaces
183func trim(str string) string {
184 return strings.Trim(str, " ")
185}
186
187// Shortcut to strings.ToUpper()
188func upper(str string) string {
189 return strings.ToUpper(trim(str))
190}
191
192// Shortcut to strings.ToLower()
193func lower(str string) string {
194 return strings.ToLower(trim(str))
195}
196
197// Camelize an uppercased word
198func Camelize(word string) (camelized string) {
199 for pos, ru := range word {
200 if pos > 0 {
201 camelized += string(unicode.ToLower(ru))
202 } else {
203 camelized += string(unicode.ToUpper(ru))
204 }
205 }
206 return
207}
208
209// ToFileName lowercases and underscores a go type name
210func ToFileName(name string) string {
211 var out []string
212
213 for _, w := range split(name) {
214 out = append(out, lower(w))
215 }
216
217 return strings.Join(out, "_")
218}
219
220// ToCommandName lowercases and underscores a go type name
221func ToCommandName(name string) string {
222 var out []string
223 for _, w := range split(name) {
224 out = append(out, lower(w))
225 }
226 return strings.Join(out, "-")
227}
228
229// ToHumanNameLower represents a code name as a human series of words
230func ToHumanNameLower(name string) string {
231 var out []string
232 for _, w := range split(name) {
233 if !commonInitialisms[upper(w)] {
234 out = append(out, lower(w))
235 } else {
236 out = append(out, w)
237 }
238 }
239 return strings.Join(out, " ")
240}
241
242// ToHumanNameTitle represents a code name as a human series of words with the first letters titleized
243func ToHumanNameTitle(name string) string {
244 var out []string
245 for _, w := range split(name) {
246 uw := upper(w)
247 if !commonInitialisms[uw] {
248 out = append(out, upper(w[:1])+lower(w[1:]))
249 } else {
250 out = append(out, w)
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 var out []string
259 for i, w := range split(name) {
260 if i == 0 {
261 out = append(out, lower(w))
262 continue
263 }
264 out = append(out, upper(w[:1])+lower(w[1:]))
265 }
266 return strings.Join(out, "")
267}
268
269// ToVarName camelcases a name which can be underscored or pascal cased
270func ToVarName(name string) string {
271 res := ToGoName(name)
272 if _, ok := commonInitialisms[res]; ok {
273 return lower(res)
274 }
275 if len(res) <= 1 {
276 return lower(res)
277 }
278 return lower(res[:1]) + res[1:]
279}
280
281// ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes
282func ToGoName(name string) string {
283 var out []string
284 for _, w := range split(name) {
285 uw := upper(w)
286 mod := int(math.Min(float64(len(uw)), 2))
287 if !commonInitialisms[uw] && !commonInitialisms[uw[:len(uw)-mod]] {
288 uw = upper(w[:1]) + lower(w[1:])
289 }
290 out = append(out, uw)
291 }
292
293 result := strings.Join(out, "")
294 if len(result) > 0 {
295 ud := upper(result[:1])
296 ru := []rune(ud)
297 if unicode.IsUpper(ru[0]) {
298 result = ud + result[1:]
299 } else {
300 result = "X" + ud + result[1:]
301 }
302 }
303 return result
304}
305
306// ContainsStringsCI searches a slice of strings for a case-insensitive match
307func ContainsStringsCI(coll []string, item string) bool {
308 for _, a := range coll {
309 if strings.EqualFold(a, item) {
310 return true
311 }
312 }
313 return false
314}
315
316type zeroable interface {
317 IsZero() bool
318}
319
320// IsZero returns true when the value passed into the function is a zero value.
321// This allows for safer checking of interface values.
322func IsZero(data interface{}) bool {
323 // check for things that have an IsZero method instead
324 if vv, ok := data.(zeroable); ok {
325 return vv.IsZero()
326 }
327 // continue with slightly more complex reflection
328 v := reflect.ValueOf(data)
329 switch v.Kind() {
330 case reflect.String:
331 return v.Len() == 0
332 case reflect.Bool:
333 return !v.Bool()
334 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
335 return v.Int() == 0
336 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
337 return v.Uint() == 0
338 case reflect.Float32, reflect.Float64:
339 return v.Float() == 0
340 case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
341 return v.IsNil()
342 case reflect.Struct, reflect.Array:
343 return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface())
344 case reflect.Invalid:
345 return true
346 }
347 return false
348}
349
350// AddInitialisms add additional initialisms
351func AddInitialisms(words ...string) {
352 for _, word := range words {
353 commonInitialisms[upper(word)] = true
354 }
355}
356
357// CommandLineOptionsGroup represents a group of user-defined command line options
358type CommandLineOptionsGroup struct {
359 ShortDescription string
360 LongDescription string
361 Options interface{}
362}