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