cmd: move internal/str back to cmd/go

cmd/go is not subject to all the same restrictions as most of cmd.
In particular it need not be buildable with the bootstrap toolchain.
So it is better to keep as little code shared between cmd/go and
cmd/compile, cmd/link, cmd/cgo as possible.

cmd/internal/str started as cmd/go/internal/str but was moved
to cmd/internal in order to make use of the quoted string code.
Move that code to cmd/internal/quoted and then move the rest of
cmd/internal/str back to cmd/go/internal/str.

Change-Id: I3a98f754d545cc3af7e9a32c2b77a5a035ea7b9a
Reviewed-on: https://go-review.googlesource.com/c/go/+/355010
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Russ Cox 2021-10-11 11:57:24 -04:00
parent cfb532158f
commit 4f73fd05a9
35 changed files with 314 additions and 292 deletions

View file

@ -29,7 +29,7 @@ import (
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"cmd/internal/str" "cmd/internal/quoted"
) )
var debugDefine = flag.Bool("debug-define", false, "print relevant #defines") var debugDefine = flag.Bool("debug-define", false, "print relevant #defines")
@ -1568,7 +1568,7 @@ func checkGCCBaseCmd() ([]string, error) {
if value == "" { if value == "" {
value = defaultCC(goos, goarch) value = defaultCC(goos, goarch)
} }
args, err := str.SplitQuotedFields(value) args, err := quoted.Split(value)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -2,7 +2,7 @@ package ssa_test
import ( import (
cmddwarf "cmd/internal/dwarf" cmddwarf "cmd/internal/dwarf"
"cmd/internal/str" "cmd/internal/quoted"
"debug/dwarf" "debug/dwarf"
"debug/elf" "debug/elf"
"debug/macho" "debug/macho"
@ -58,7 +58,7 @@ func TestStmtLines(t *testing.T) {
if extld == "" { if extld == "" {
extld = "gcc" extld = "gcc"
} }
extldArgs, err := str.SplitQuotedFields(extld) extldArgs, err := quoted.Split(extld)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -46,8 +46,8 @@ var bootstrapDirs = []string{
"cmd/internal/obj/...", "cmd/internal/obj/...",
"cmd/internal/objabi", "cmd/internal/objabi",
"cmd/internal/pkgpath", "cmd/internal/pkgpath",
"cmd/internal/quoted",
"cmd/internal/src", "cmd/internal/src",
"cmd/internal/str",
"cmd/internal/sys", "cmd/internal/sys",
"cmd/link", "cmd/link",
"cmd/link/internal/...", "cmd/link/internal/...",

View file

@ -17,7 +17,7 @@ import (
"sync" "sync"
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/internal/str" "cmd/go/internal/str"
) )
// A Command is an implementation of a go command // A Command is an implementation of a go command

View file

@ -9,7 +9,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/internal/str" "cmd/internal/quoted"
) )
// A StringsFlag is a command-line flag that interprets its argument // A StringsFlag is a command-line flag that interprets its argument
@ -18,7 +18,7 @@ type StringsFlag []string
func (v *StringsFlag) Set(s string) error { func (v *StringsFlag) Set(s string) error {
var err error var err error
*v, err = str.SplitQuotedFields(s) *v, err = quoted.Split(s)
if *v == nil { if *v == nil {
*v = []string{} *v = []string{}
} }

View file

@ -26,7 +26,7 @@ import (
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str" "cmd/internal/quoted"
) )
var CmdEnv = &base.Command{ var CmdEnv = &base.Command{
@ -470,7 +470,7 @@ func checkEnvWrite(key, val string) error {
if val == "" { if val == "" {
break break
} }
args, err := str.SplitQuotedFields(val) args, err := quoted.Split(val)
if err != nil { if err != nil {
return fmt.Errorf("invalid %s: %v", key, err) return fmt.Errorf("invalid %s: %v", key, err)
} }

View file

@ -10,7 +10,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/internal/str" "cmd/go/internal/str"
"context" "context"
"fmt" "fmt"
"os" "os"

View file

@ -26,7 +26,7 @@ import (
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str" "cmd/go/internal/str"
) )
var CmdGenerate = &base.Command{ var CmdGenerate = &base.Command{

View file

@ -20,7 +20,7 @@ import (
"cmd/go/internal/vcs" "cmd/go/internal/vcs"
"cmd/go/internal/web" "cmd/go/internal/web"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str" "cmd/go/internal/str"
"golang.org/x/mod/module" "golang.org/x/mod/module"
) )

View file

@ -24,7 +24,7 @@ import (
"cmd/go/internal/modinfo" "cmd/go/internal/modinfo"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str" "cmd/go/internal/str"
) )
var CmdList = &base.Command{ var CmdList = &base.Command{

View file

@ -6,7 +6,7 @@ package load
import ( import (
"cmd/go/internal/base" "cmd/go/internal/base"
"cmd/internal/str" "cmd/internal/quoted"
"fmt" "fmt"
"strings" "strings"
) )
@ -63,7 +63,7 @@ func (f *PerPackageFlag) set(v, cwd string) error {
match = MatchPackage(pattern, cwd) match = MatchPackage(pattern, cwd)
v = v[i+1:] v = v[i+1:]
} }
flags, err := str.SplitQuotedFields(v) flags, err := quoted.Split(v)
if err != nil { if err != nil {
return err return err
} }

View file

@ -38,9 +38,9 @@ import (
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/par" "cmd/go/internal/par"
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/go/internal/str"
"cmd/go/internal/trace" "cmd/go/internal/trace"
"cmd/go/internal/vcs" "cmd/go/internal/vcs"
"cmd/internal/str"
"cmd/internal/sys" "cmd/internal/sys"
"golang.org/x/mod/modfile" "golang.org/x/mod/modfile"

View file

@ -23,7 +23,7 @@ import (
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/go/internal/trace" "cmd/go/internal/trace"
"cmd/internal/str" "cmd/go/internal/str"
) )
var TestMainDeps = []string{ var TestMainDeps = []string{

View file

@ -24,7 +24,7 @@ import (
"cmd/go/internal/imports" "cmd/go/internal/imports"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/internal/str" "cmd/go/internal/str"
"golang.org/x/mod/module" "golang.org/x/mod/module"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"

View file

@ -21,7 +21,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/lockedfile" "cmd/go/internal/lockedfile"
"cmd/internal/str" "cmd/go/internal/str"
) )
// Downloaded size limits. // Downloaded size limits.

View file

@ -20,7 +20,7 @@ import (
"cmd/go/internal/lockedfile" "cmd/go/internal/lockedfile"
"cmd/go/internal/par" "cmd/go/internal/par"
"cmd/internal/str" "cmd/go/internal/str"
) )
// A VCSError indicates an error using a version control system. // A VCSError indicates an error using a version control system.

View file

@ -14,7 +14,7 @@ import (
"cmd/go/internal/base" "cmd/go/internal/base"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/internal/str" "cmd/go/internal/str"
"golang.org/x/mod/module" "golang.org/x/mod/module"
) )

View file

@ -119,7 +119,7 @@ import (
"cmd/go/internal/mvs" "cmd/go/internal/mvs"
"cmd/go/internal/par" "cmd/go/internal/par"
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/internal/str" "cmd/go/internal/str"
"golang.org/x/mod/module" "golang.org/x/mod/module"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"

View file

@ -22,7 +22,7 @@ import (
"cmd/go/internal/modfetch" "cmd/go/internal/modfetch"
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/go/internal/trace" "cmd/go/internal/trace"
"cmd/internal/str" "cmd/go/internal/str"
"golang.org/x/mod/module" "golang.org/x/mod/module"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"

View file

@ -19,7 +19,7 @@ import (
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str" "cmd/go/internal/str"
) )
var CmdRun = &base.Command{ var CmdRun = &base.Command{

View file

@ -0,0 +1,111 @@
// 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 str provides string manipulation utilities.
package str
import (
"bytes"
"fmt"
"unicode"
"unicode/utf8"
)
// StringList flattens its arguments into a single []string.
// Each argument in args must have type string or []string.
func StringList(args ...interface{}) []string {
var x []string
for _, arg := range args {
switch arg := arg.(type) {
case []string:
x = append(x, arg...)
case string:
x = append(x, arg)
default:
panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg))
}
}
return x
}
// ToFold returns a string with the property that
// strings.EqualFold(s, t) iff ToFold(s) == ToFold(t)
// This lets us test a large set of strings for fold-equivalent
// duplicates without making a quadratic number of calls
// to EqualFold. Note that strings.ToUpper and strings.ToLower
// do not have the desired property in some corner cases.
func ToFold(s string) string {
// Fast path: all ASCII, no upper case.
// Most paths look like this already.
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
goto Slow
}
}
return s
Slow:
var buf bytes.Buffer
for _, r := range s {
// SimpleFold(x) cycles to the next equivalent rune > x
// or wraps around to smaller values. Iterate until it wraps,
// and we've found the minimum value.
for {
r0 := r
r = unicode.SimpleFold(r0)
if r <= r0 {
break
}
}
// Exception to allow fast path above: A-Z => a-z
if 'A' <= r && r <= 'Z' {
r += 'a' - 'A'
}
buf.WriteRune(r)
}
return buf.String()
}
// FoldDup reports a pair of strings from the list that are
// equal according to strings.EqualFold.
// It returns "", "" if there are no such strings.
func FoldDup(list []string) (string, string) {
clash := map[string]string{}
for _, s := range list {
fold := ToFold(s)
if t := clash[fold]; t != "" {
if s > t {
s, t = t, s
}
return s, t
}
clash[fold] = s
}
return "", ""
}
// Contains reports whether x contains s.
func Contains(x []string, s string) bool {
for _, t := range x {
if t == s {
return true
}
}
return false
}
// Uniq removes consecutive duplicate strings from ss.
func Uniq(ss *[]string) {
if len(*ss) <= 1 {
return
}
uniq := (*ss)[:1]
for _, s := range *ss {
if s != uniq[len(uniq)-1] {
uniq = append(uniq, s)
}
}
*ss = uniq
}

View file

@ -0,0 +1,29 @@
// 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 str
import (
"testing"
)
var foldDupTests = []struct {
list []string
f1, f2 string
}{
{StringList("math/rand", "math/big"), "", ""},
{StringList("math", "strings"), "", ""},
{StringList("strings"), "", ""},
{StringList("strings", "strings"), "strings", "strings"},
{StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"},
}
func TestFoldDup(t *testing.T) {
for _, tt := range foldDupTests {
f1, f2 := FoldDup(tt.list)
if f1 != tt.f1 || f2 != tt.f2 {
t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2)
}
}
}

View file

@ -33,7 +33,7 @@ import (
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/go/internal/trace" "cmd/go/internal/trace"
"cmd/go/internal/work" "cmd/go/internal/work"
"cmd/internal/str" "cmd/go/internal/str"
"cmd/internal/test2json" "cmd/internal/test2json"
) )

View file

@ -27,7 +27,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/go/internal/web" "cmd/go/internal/web"
"cmd/internal/str" "cmd/go/internal/str"
"golang.org/x/mod/module" "golang.org/x/mod/module"
) )

View file

@ -16,7 +16,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/internal/buildid" "cmd/internal/buildid"
"cmd/internal/str" "cmd/go/internal/str"
) )
// Build IDs // Build IDs

View file

@ -34,8 +34,9 @@ import (
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/str"
"cmd/go/internal/trace" "cmd/go/internal/trace"
"cmd/internal/str" "cmd/internal/quoted"
"cmd/internal/sys" "cmd/internal/sys"
) )
@ -2666,7 +2667,7 @@ func envList(key, def string) []string {
if v == "" { if v == "" {
v = def v = def
} }
args, err := str.SplitQuotedFields(v) args, err := quoted.Split(v)
if err != nil { if err != nil {
panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err)) panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
} }

View file

@ -20,8 +20,9 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/str"
"cmd/internal/objabi" "cmd/internal/objabi"
"cmd/internal/str" "cmd/internal/quoted"
"cmd/internal/sys" "cmd/internal/sys"
"crypto/sha1" "crypto/sha1"
) )
@ -565,7 +566,7 @@ func setextld(ldflags []string, compiler []string) ([]string, error) {
return ldflags, nil return ldflags, nil
} }
} }
joined, err := str.JoinAndQuoteFields(compiler) joined, err := quoted.Join(compiler)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -17,7 +17,7 @@ import (
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/internal/pkgpath" "cmd/internal/pkgpath"
"cmd/internal/str" "cmd/go/internal/str"
) )
// The Gccgo toolchain. // The Gccgo toolchain.

View file

@ -11,7 +11,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/fsys" "cmd/go/internal/fsys"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/internal/str" "cmd/internal/quoted"
"cmd/internal/sys" "cmd/internal/sys"
"fmt" "fmt"
"os" "os"
@ -46,7 +46,7 @@ func BuildInit() {
// Make sure CC, CXX, and FC are absolute paths. // Make sure CC, CXX, and FC are absolute paths.
for _, key := range []string{"CC", "CXX", "FC"} { for _, key := range []string{"CC", "CXX", "FC"} {
value := cfg.Getenv(key) value := cfg.Getenv(key)
args, err := str.SplitQuotedFields(value) args, err := quoted.Split(value)
if err != nil { if err != nil {
base.Fatalf("go: %s environment variable could not be parsed: %v", key, err) base.Fatalf("go: %s environment variable could not be parsed: %v", key, err)
} }

View file

@ -0,0 +1,127 @@
// 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.
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, []byte(arg)...)
case !sawSingleQuote:
buf = append(buf, '\'')
buf = append(buf, []byte(arg)...)
buf = append(buf, '\'')
case !sawDoubleQuote:
buf = append(buf, '"')
buf = append(buf, []byte(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
}

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package str package quoted
import ( import (
"reflect" "reflect"
@ -10,27 +10,7 @@ import (
"testing" "testing"
) )
var foldDupTests = []struct { func TestSplit(t *testing.T) {
list []string
f1, f2 string
}{
{StringList("math/rand", "math/big"), "", ""},
{StringList("math", "strings"), "", ""},
{StringList("strings"), "", ""},
{StringList("strings", "strings"), "strings", "strings"},
{StringList("Rand", "rand", "math", "math/rand", "math/Rand"), "Rand", "rand"},
}
func TestFoldDup(t *testing.T) {
for _, tt := range foldDupTests {
f1, f2 := FoldDup(tt.list)
if f1 != tt.f1 || f2 != tt.f2 {
t.Errorf("foldDup(%q) = %q, %q, want %q, %q", tt.list, f1, f2, tt.f1, tt.f2)
}
}
}
func TestSplitQuotedFields(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
name string name string
value string value string
@ -54,7 +34,7 @@ func TestSplitQuotedFields(t *testing.T) {
{name: "quote_unclosed", value: `'a`, wantErr: "unterminated ' string"}, {name: "quote_unclosed", value: `'a`, wantErr: "unterminated ' string"},
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
got, err := SplitQuotedFields(test.value) got, err := Split(test.value)
if err != nil { if err != nil {
if test.wantErr == "" { if test.wantErr == "" {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -73,7 +53,7 @@ func TestSplitQuotedFields(t *testing.T) {
} }
} }
func TestJoinAndQuoteFields(t *testing.T) { func TestJoin(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
name string name string
args []string args []string
@ -88,7 +68,7 @@ func TestJoinAndQuoteFields(t *testing.T) {
{name: "unquoteable", args: []string{`'"`}, wantErr: "contains both single and double quotes and cannot be quoted"}, {name: "unquoteable", args: []string{`'"`}, wantErr: "contains both single and double quotes and cannot be quoted"},
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
got, err := JoinAndQuoteFields(test.args) got, err := Join(test.args)
if err != nil { if err != nil {
if test.wantErr == "" { if test.wantErr == "" {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)

View file

@ -1,227 +0,0 @@
// 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 str provides string manipulation utilities.
package str
import (
"bytes"
"flag"
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// StringList flattens its arguments into a single []string.
// Each argument in args must have type string or []string.
func StringList(args ...interface{}) []string {
var x []string
for _, arg := range args {
switch arg := arg.(type) {
case []string:
x = append(x, arg...)
case string:
x = append(x, arg)
default:
panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg))
}
}
return x
}
// ToFold returns a string with the property that
// strings.EqualFold(s, t) iff ToFold(s) == ToFold(t)
// This lets us test a large set of strings for fold-equivalent
// duplicates without making a quadratic number of calls
// to EqualFold. Note that strings.ToUpper and strings.ToLower
// do not have the desired property in some corner cases.
func ToFold(s string) string {
// Fast path: all ASCII, no upper case.
// Most paths look like this already.
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' {
goto Slow
}
}
return s
Slow:
var buf bytes.Buffer
for _, r := range s {
// SimpleFold(x) cycles to the next equivalent rune > x
// or wraps around to smaller values. Iterate until it wraps,
// and we've found the minimum value.
for {
r0 := r
r = unicode.SimpleFold(r0)
if r <= r0 {
break
}
}
// Exception to allow fast path above: A-Z => a-z
if 'A' <= r && r <= 'Z' {
r += 'a' - 'A'
}
buf.WriteRune(r)
}
return buf.String()
}
// FoldDup reports a pair of strings from the list that are
// equal according to strings.EqualFold.
// It returns "", "" if there are no such strings.
func FoldDup(list []string) (string, string) {
clash := map[string]string{}
for _, s := range list {
fold := ToFold(s)
if t := clash[fold]; t != "" {
if s > t {
s, t = t, s
}
return s, t
}
clash[fold] = s
}
return "", ""
}
// Contains reports whether x contains s.
func Contains(x []string, s string) bool {
for _, t := range x {
if t == s {
return true
}
}
return false
}
// Uniq removes consecutive duplicate strings from ss.
func Uniq(ss *[]string) {
if len(*ss) <= 1 {
return
}
uniq := (*ss)[:1]
for _, s := range *ss {
if s != uniq[len(uniq)-1] {
uniq = append(uniq, s)
}
}
*ss = uniq
}
func isSpaceByte(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
// SplitQuotedFields splits s into a list of fields,
// allowing single or double quotes around elements.
// There is no unescaping or other processing within
// quoted fields.
func SplitQuotedFields(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
}
// JoinAndQuoteFields joins a list of arguments into a string that can be parsed
// with SplitQuotedFields. 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 JoinAndQuoteFields(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, []byte(arg)...)
case !sawSingleQuote:
buf = append(buf, '\'')
buf = append(buf, []byte(arg)...)
buf = append(buf, '\'')
case !sawDoubleQuote:
buf = append(buf, '"')
buf = append(buf, []byte(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 QuotedStringListFlag parses a list of string arguments encoded with
// JoinAndQuoteFields. It is useful for flags like cmd/link's -extldflags.
type QuotedStringListFlag []string
var _ flag.Value = (*QuotedStringListFlag)(nil)
func (f *QuotedStringListFlag) Set(v string) error {
fs, err := SplitQuotedFields(v)
if err != nil {
return err
}
*f = fs[:len(fs):len(fs)]
return nil
}
func (f *QuotedStringListFlag) String() string {
if f == nil {
return ""
}
s, err := JoinAndQuoteFields(*f)
if err != nil {
return strings.Join(*f, " ")
}
return s
}

View file

@ -8,7 +8,7 @@ import (
"bytes" "bytes"
cmddwarf "cmd/internal/dwarf" cmddwarf "cmd/internal/dwarf"
"cmd/internal/objfile" "cmd/internal/objfile"
"cmd/internal/str" "cmd/internal/quoted"
"debug/dwarf" "debug/dwarf"
"internal/testenv" "internal/testenv"
"os" "os"
@ -68,7 +68,7 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string)
if extld == "" { if extld == "" {
extld = "gcc" extld = "gcc"
} }
extldArgs, err := str.SplitQuotedFields(extld) extldArgs, err := quoted.Split(extld)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -34,7 +34,7 @@ import (
"bufio" "bufio"
"cmd/internal/goobj" "cmd/internal/goobj"
"cmd/internal/objabi" "cmd/internal/objabi"
"cmd/internal/str" "cmd/internal/quoted"
"cmd/internal/sys" "cmd/internal/sys"
"cmd/link/internal/benchmark" "cmd/link/internal/benchmark"
"flag" "flag"
@ -76,8 +76,8 @@ var (
flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable") flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable")
flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files") flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files")
flagExtld str.QuotedStringListFlag flagExtld quoted.Flag
flagExtldflags str.QuotedStringListFlag flagExtldflags quoted.Flag
flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive") flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive")
flagA = flag.Bool("a", false, "no-op (deprecated)") flagA = flag.Bool("a", false, "no-op (deprecated)")