| // 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 quoted provides string manipulation utilities. |
| package quoted |
| |
| import ( |
| "flag" |
| "fmt" |
| "strings" |
| "unicode" |
| ) |
| |
| func isSpaceByte(c byte) bool { |
| return c == ' ' || c == '\t' || c == '\n' || c == '\r' |
| } |
| |
| // Split 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 Split(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 |
| } |
| |
| // Join joins a list of arguments into a string that can be parsed |
| // with Split. 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 Join(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 Flag parses a list of string arguments encoded with Join. |
| // It is useful for flags like cmd/link's -extldflags. |
| type Flag []string |
| |
| var _ flag.Value = (*Flag)(nil) |
| |
| func (f *Flag) Set(v string) error { |
| fs, err := Split(v) |
| if err != nil { |
| return err |
| } |
| *f = fs[:len(fs):len(fs)] |
| return nil |
| } |
| |
| func (f *Flag) String() string { |
| if f == nil { |
| return "" |
| } |
| s, err := Join(*f) |
| if err != nil { |
| return strings.Join(*f, " ") |
| } |
| return s |
| } |