| // 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 |
| } |