summaryrefslogtreecommitdiffstats
path: root/src/cmd/internal/quoted
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/cmd/internal/quoted
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/internal/quoted')
-rw-r--r--src/cmd/internal/quoted/quoted.go129
-rw-r--r--src/cmd/internal/quoted/quoted_test.go88
2 files changed, 217 insertions, 0 deletions
diff --git a/src/cmd/internal/quoted/quoted.go b/src/cmd/internal/quoted/quoted.go
new file mode 100644
index 0000000..a812275
--- /dev/null
+++ b/src/cmd/internal/quoted/quoted.go
@@ -0,0 +1,129 @@
+// 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.
+//
+// Keep in sync with cmd/dist/quoted.go
+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, arg...)
+
+ case !sawSingleQuote:
+ buf = append(buf, '\'')
+ buf = append(buf, arg...)
+ buf = append(buf, '\'')
+
+ case !sawDoubleQuote:
+ buf = append(buf, '"')
+ buf = append(buf, 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
+}
diff --git a/src/cmd/internal/quoted/quoted_test.go b/src/cmd/internal/quoted/quoted_test.go
new file mode 100644
index 0000000..d76270c
--- /dev/null
+++ b/src/cmd/internal/quoted/quoted_test.go
@@ -0,0 +1,88 @@
+// Copyright 2020 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
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestSplit(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ value string
+ want []string
+ wantErr string
+ }{
+ {name: "empty", value: "", want: nil},
+ {name: "space", value: " ", want: nil},
+ {name: "one", value: "a", want: []string{"a"}},
+ {name: "leading_space", value: " a", want: []string{"a"}},
+ {name: "trailing_space", value: "a ", want: []string{"a"}},
+ {name: "two", value: "a b", want: []string{"a", "b"}},
+ {name: "two_multi_space", value: "a b", want: []string{"a", "b"}},
+ {name: "two_tab", value: "a\tb", want: []string{"a", "b"}},
+ {name: "two_newline", value: "a\nb", want: []string{"a", "b"}},
+ {name: "quote_single", value: `'a b'`, want: []string{"a b"}},
+ {name: "quote_double", value: `"a b"`, want: []string{"a b"}},
+ {name: "quote_both", value: `'a '"b "`, want: []string{"a ", "b "}},
+ {name: "quote_contains", value: `'a "'"'b"`, want: []string{`a "`, `'b`}},
+ {name: "escape", value: `\'`, want: []string{`\'`}},
+ {name: "quote_unclosed", value: `'a`, wantErr: "unterminated ' string"},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := Split(test.value)
+ if err != nil {
+ if test.wantErr == "" {
+ t.Fatalf("unexpected error: %v", err)
+ } else if errMsg := err.Error(); !strings.Contains(errMsg, test.wantErr) {
+ t.Fatalf("error %q does not contain %q", errMsg, test.wantErr)
+ }
+ return
+ }
+ if test.wantErr != "" {
+ t.Fatalf("unexpected success; wanted error containing %q", test.wantErr)
+ }
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("got %q; want %q", got, test.want)
+ }
+ })
+ }
+}
+
+func TestJoin(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ args []string
+ want, wantErr string
+ }{
+ {name: "empty", args: nil, want: ""},
+ {name: "one", args: []string{"a"}, want: "a"},
+ {name: "two", args: []string{"a", "b"}, want: "a b"},
+ {name: "space", args: []string{"a ", "b"}, want: "'a ' b"},
+ {name: "newline", args: []string{"a\n", "b"}, want: "'a\n' b"},
+ {name: "quote", args: []string{`'a `, "b"}, want: `"'a " b`},
+ {name: "unquoteable", args: []string{`'"`}, wantErr: "contains both single and double quotes and cannot be quoted"},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ got, err := Join(test.args)
+ if err != nil {
+ if test.wantErr == "" {
+ t.Fatalf("unexpected error: %v", err)
+ } else if errMsg := err.Error(); !strings.Contains(errMsg, test.wantErr) {
+ t.Fatalf("error %q does not contain %q", errMsg, test.wantErr)
+ }
+ return
+ }
+ if test.wantErr != "" {
+ t.Fatalf("unexpected success; wanted error containing %q", test.wantErr)
+ }
+ if got != test.want {
+ t.Errorf("got %s; want %s", got, test.want)
+ }
+ })
+ }
+}