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