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  //
    24  // Keep in sync with cmd/dist/quoted.go
    25  func 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.
    65  func 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.
   107  type Flag []string
   108  
   109  var _ flag.Value = (*Flag)(nil)
   110  
   111  func (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  
   120  func (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  

View as plain text