Source file src/cmd/internal/quoted/quoted.go

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

View as plain text