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 quoted provides string manipulation utilities. 6package quoted 7 8import ( 9 "flag" 10 "fmt" 11 "strings" 12 "unicode" 13) 14 15func isSpaceByte(c byte) bool { 16 return c == ' ' || c == '\t' || c == '\n' || c == '\r' 17} 18 19// Split splits s into a list of fields, 20// allowing single or double quotes around elements. 21// There is no unescaping or other processing within 22// quoted fields. 23// 24// Keep in sync with cmd/dist/quoted.go 25func Split(s string) ([]string, error) { 26 // Split fields allowing '' or "" around elements. 27 // Quotes further inside the string do not count. 28 var f []string 29 for len(s) > 0 { 30 for len(s) > 0 && isSpaceByte(s[0]) { 31 s = s[1:] 32 } 33 if len(s) == 0 { 34 break 35 } 36 // Accepted quoted string. No unescaping inside. 37 if s[0] == '"' || s[0] == '\'' { 38 quote := s[0] 39 s = s[1:] 40 i := 0 41 for i < len(s) && s[i] != quote { 42 i++ 43 } 44 if i >= len(s) { 45 return nil, fmt.Errorf("unterminated %c string", quote) 46 } 47 f = append(f, s[:i]) 48 s = s[i+1:] 49 continue 50 } 51 i := 0 52 for i < len(s) && !isSpaceByte(s[i]) { 53 i++ 54 } 55 f = append(f, s[:i]) 56 s = s[i:] 57 } 58 return f, nil 59} 60 61// Join joins a list of arguments into a string that can be parsed 62// with Split. Arguments are quoted only if necessary; arguments 63// without spaces or quotes are kept as-is. No argument may contain both 64// single and double quotes. 65func Join(args []string) (string, error) { 66 var buf []byte 67 for i, arg := range args { 68 if i > 0 { 69 buf = append(buf, ' ') 70 } 71 var sawSpace, sawSingleQuote, sawDoubleQuote bool 72 for _, c := range arg { 73 switch { 74 case c > unicode.MaxASCII: 75 continue 76 case isSpaceByte(byte(c)): 77 sawSpace = true 78 case c == '\'': 79 sawSingleQuote = true 80 case c == '"': 81 sawDoubleQuote = true 82 } 83 } 84 switch { 85 case !sawSpace && !sawSingleQuote && !sawDoubleQuote: 86 buf = append(buf, arg...) 87 88 case !sawSingleQuote: 89 buf = append(buf, '\'') 90 buf = append(buf, arg...) 91 buf = append(buf, '\'') 92 93 case !sawDoubleQuote: 94 buf = append(buf, '"') 95 buf = append(buf, arg...) 96 buf = append(buf, '"') 97 98 default: 99 return "", fmt.Errorf("argument %q contains both single and double quotes and cannot be quoted", arg) 100 } 101 } 102 return string(buf), nil 103} 104 105// A Flag parses a list of string arguments encoded with Join. 106// It is useful for flags like cmd/link's -extldflags. 107type Flag []string 108 109var _ flag.Value = (*Flag)(nil) 110 111func (f *Flag) Set(v string) error { 112 fs, err := Split(v) 113 if err != nil { 114 return err 115 } 116 *f = fs[:len(fs):len(fs)] 117 return nil 118} 119 120func (f *Flag) String() string { 121 if f == nil { 122 return "" 123 } 124 s, err := Join(*f) 125 if err != nil { 126 return strings.Join(*f, " ") 127 } 128 return s 129} 130