| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package str provides string manipulation utilities. | |
| package str | |
| import ( | |
| "bytes" | |
| "flag" | |
| "fmt" | |
| "strings" | |
| "unicode" | |
| "unicode/utf8" | |
| ) | |
| // StringList flattens its arguments into a single []string. | |
| // Each argument in args must have type string or []string. | |
| func StringList(args ...interface{}) []string { | |
| var x []string | |
| for _, arg := range args { | |
| switch arg := arg.(type) { | |
| case []string: | |
| x = append(x, arg...) | |
| case string: | |
| x = append(x, arg) | |
| default: | |
| panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg)) | |
| } | |
| } | |
| return x | |
| } | |
| // ToFold returns a string with the property that | |
| // strings.EqualFold(s, t) iff ToFold(s) == ToFold(t) | |
| // This lets us test a large set of strings for fold-equivalent | |
| // duplicates without making a quadratic number of calls | |
| // to EqualFold. Note that strings.ToUpper and strings.ToLower | |
| // do not have the desired property in some corner cases. | |
| func ToFold(s string) string { | |
| // Fast path: all ASCII, no upper case. | |
| // Most paths look like this already. | |
| for i := 0; i < len(s); i++ { | |
| c := s[i] | |
| if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' { | |
| goto Slow | |
| } | |
| } | |
| return s | |
| Slow: | |
| var buf bytes.Buffer | |
| for _, r := range s { | |
| // SimpleFold(x) cycles to the next equivalent rune > x | |
| // or wraps around to smaller values. Iterate until it wraps, | |
| // and we've found the minimum value. | |
| for { | |
| r0 := r | |
| r = unicode.SimpleFold(r0) | |
| if r <= r0 { | |
| break | |
| } | |
| } | |
| // Exception to allow fast path above: A-Z => a-z | |
| if 'A' <= r && r <= 'Z' { | |
| r += 'a' - 'A' | |
| } | |
| buf.WriteRune(r) | |
| } | |
| return buf.String() | |
| } | |
| // FoldDup reports a pair of strings from the list that are | |
| // equal according to strings.EqualFold. | |
| // It returns "", "" if there are no such strings. | |
| func FoldDup(list []string) (string, string) { | |
| clash := map[string]string{} | |
| for _, s := range list { | |
| fold := ToFold(s) | |
| if t := clash[fold]; t != "" { | |
| if s > t { | |
| s, t = t, s | |
| } | |
| return s, t | |
| } | |
| clash[fold] = s | |
| } | |
| return "", "" | |
| } | |
| // Contains reports whether x contains s. | |
| func Contains(x []string, s string) bool { | |
| for _, t := range x { | |
| if t == s { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // Uniq removes consecutive duplicate strings from ss. | |
| func Uniq(ss *[]string) { | |
| if len(*ss) <= 1 { | |
| return | |
| } | |
| uniq := (*ss)[:1] | |
| for _, s := range *ss { | |
| if s != uniq[len(uniq)-1] { | |
| uniq = append(uniq, s) | |
| } | |
| } | |
| *ss = uniq | |
| } | |
| func isSpaceByte(c byte) bool { | |
| return c == ' ' || c == '\t' || c == '\n' || c == '\r' | |
| } | |
| // SplitQuotedFields splits s into a list of fields, | |
| // allowing single or double quotes around elements. | |
| // There is no unescaping or other processing within | |
| // quoted fields. | |
| func SplitQuotedFields(s string) ([]string, error) { | |
| // Split fields allowing '' or "" around elements. | |
| // Quotes further inside the string do not count. | |
| var f []string | |
| for len(s) > 0 { | |
| for len(s) > 0 && isSpaceByte(s[0]) { | |
| s = s[1:] | |
| } | |
| if len(s) == 0 { | |
| break | |
| } | |
| // Accepted quoted string. No unescaping inside. | |
| if s[0] == '"' || s[0] == '\'' { | |
| quote := s[0] | |
| s = s[1:] | |
| i := 0 | |
| for i < len(s) && s[i] != quote { | |
| i++ | |
| } | |
| if i >= len(s) { | |
| return nil, fmt.Errorf("unterminated %c string", quote) | |
| } | |
| f = append(f, s[:i]) | |
| s = s[i+1:] | |
| continue | |
| } | |
| i := 0 | |
| for i < len(s) && !isSpaceByte(s[i]) { | |
| i++ | |
| } | |
| f = append(f, s[:i]) | |
| s = s[i:] | |
| } | |
| return f, nil | |
| } | |
| // JoinAndQuoteFields joins a list of arguments into a string that can be parsed | |
| // with SplitQuotedFields. Arguments are quoted only if necessary; arguments | |
| // without spaces or quotes are kept as-is. No argument may contain both | |
| // single and double quotes. | |
| func JoinAndQuoteFields(args []string) (string, error) { | |
| var buf []byte | |
| for i, arg := range args { | |
| if i > 0 { | |
| buf = append(buf, ' ') | |
| } | |
| var sawSpace, sawSingleQuote, sawDoubleQuote bool | |
| for _, c := range arg { | |
| switch { | |
| case c > unicode.MaxASCII: | |
| continue | |
| case isSpaceByte(byte(c)): | |
| sawSpace = true | |
| case c == '\'': | |
| sawSingleQuote = true | |
| case c == '"': | |
| sawDoubleQuote = true | |
| } | |
| } | |
| switch { | |
| case !sawSpace && !sawSingleQuote && !sawDoubleQuote: | |
| buf = append(buf, []byte(arg)...) | |
| case !sawSingleQuote: | |
| buf = append(buf, '\'') | |
| buf = append(buf, []byte(arg)...) | |
| buf = append(buf, '\'') | |
| case !sawDoubleQuote: | |
| buf = append(buf, '"') | |
| buf = append(buf, []byte(arg)...) | |
| buf = append(buf, '"') | |
| default: | |
| return "", fmt.Errorf("argument %q contains both single and double quotes and cannot be quoted", arg) | |
| } | |
| } | |
| return string(buf), nil | |
| } | |
| // A QuotedStringListFlag parses a list of string arguments encoded with | |
| // JoinAndQuoteFields. It is useful for flags like cmd/link's -extldflags. | |
| type QuotedStringListFlag []string | |
| var _ flag.Value = (*QuotedStringListFlag)(nil) | |
| func (f *QuotedStringListFlag) Set(v string) error { | |
| fs, err := SplitQuotedFields(v) | |
| if err != nil { | |
| return err | |
| } | |
| *f = fs[:len(fs):len(fs)] | |
| return nil | |
| } | |
| func (f *QuotedStringListFlag) String() string { | |
| if f == nil { | |
| return "" | |
| } | |
| s, err := JoinAndQuoteFields(*f) | |
| if err != nil { | |
| return strings.Join(*f, " ") | |
| } | |
| return s | |
| } |