1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package str provides string manipulation utilities.
6package str
7
8import (
9	"bytes"
10	"fmt"
11	"unicode"
12	"unicode/utf8"
13)
14
15// StringList flattens its arguments into a single []string.
16// Each argument in args must have type string or []string.
17func StringList(args ...interface{}) []string {
18	var x []string
19	for _, arg := range args {
20		switch arg := arg.(type) {
21		case []string:
22			x = append(x, arg...)
23		case string:
24			x = append(x, arg)
25		default:
26			panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg))
27		}
28	}
29	return x
30}
31
32// ToFold returns a string with the property that
33//	strings.EqualFold(s, t) iff ToFold(s) == ToFold(t)
34// This lets us test a large set of strings for fold-equivalent
35// duplicates without making a quadratic number of calls
36// to EqualFold. Note that strings.ToUpper and strings.ToLower
37// do not have the desired property in some corner cases.
38func ToFold(s string) string {
39	// Fast path: all ASCII, no upper case.
40	// Most paths look like this already.
41	for i := 0; i < len(s); i++ {
42		c := s[i]
43		if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
44			goto Slow
45		}
46	}
47	return s
48
49Slow:
50	var buf bytes.Buffer
51	for _, r := range s {
52		// SimpleFold(x) cycles to the next equivalent rune > x
53		// or wraps around to smaller values. Iterate until it wraps,
54		// and we've found the minimum value.
55		for {
56			r0 := r
57			r = unicode.SimpleFold(r0)
58			if r <= r0 {
59				break
60			}
61		}
62		// Exception to allow fast path above: A-Z => a-z
63		if 'A' <= r && r <= 'Z' {
64			r += 'a' - 'A'
65		}
66		buf.WriteRune(r)
67	}
68	return buf.String()
69}
70
71// FoldDup reports a pair of strings from the list that are
72// equal according to strings.EqualFold.
73// It returns "", "" if there are no such strings.
74func FoldDup(list []string) (string, string) {
75	clash := map[string]string{}
76	for _, s := range list {
77		fold := ToFold(s)
78		if t := clash[fold]; t != "" {
79			if s > t {
80				s, t = t, s
81			}
82			return s, t
83		}
84		clash[fold] = s
85	}
86	return "", ""
87}
88
89// Contains reports whether x contains s.
90func Contains(x []string, s string) bool {
91	for _, t := range x {
92		if t == s {
93			return true
94		}
95	}
96	return false
97}
98
99func isSpaceByte(c byte) bool {
100	return c == ' ' || c == '\t' || c == '\n' || c == '\r'
101}
102
103// SplitQuotedFields splits s into a list of fields,
104// allowing single or double quotes around elements.
105// There is no unescaping or other processing within
106// quoted fields.
107func SplitQuotedFields(s string) ([]string, error) {
108	// Split fields allowing '' or "" around elements.
109	// Quotes further inside the string do not count.
110	var f []string
111	for len(s) > 0 {
112		for len(s) > 0 && isSpaceByte(s[0]) {
113			s = s[1:]
114		}
115		if len(s) == 0 {
116			break
117		}
118		// Accepted quoted string. No unescaping inside.
119		if s[0] == '"' || s[0] == '\'' {
120			quote := s[0]
121			s = s[1:]
122			i := 0
123			for i < len(s) && s[i] != quote {
124				i++
125			}
126			if i >= len(s) {
127				return nil, fmt.Errorf("unterminated %c string", quote)
128			}
129			f = append(f, s[:i])
130			s = s[i+1:]
131			continue
132		}
133		i := 0
134		for i < len(s) && !isSpaceByte(s[i]) {
135			i++
136		}
137		f = append(f, s[:i])
138		s = s[i:]
139	}
140	return f, nil
141}
142