mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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:
parent
cfb532158f
commit
4f73fd05a9
35 changed files with 314 additions and 292 deletions
|
|
@ -29,7 +29,7 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
)
|
||||
|
||||
var debugDefine = flag.Bool("debug-define", false, "print relevant #defines")
|
||||
|
|
@ -1568,7 +1568,7 @@ func checkGCCBaseCmd() ([]string, error) {
|
|||
if value == "" {
|
||||
value = defaultCC(goos, goarch)
|
||||
}
|
||||
args, err := str.SplitQuotedFields(value)
|
||||
args, err := quoted.Split(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package ssa_test
|
|||
|
||||
import (
|
||||
cmddwarf "cmd/internal/dwarf"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"debug/dwarf"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
|
|
@ -58,7 +58,7 @@ func TestStmtLines(t *testing.T) {
|
|||
if extld == "" {
|
||||
extld = "gcc"
|
||||
}
|
||||
extldArgs, err := str.SplitQuotedFields(extld)
|
||||
extldArgs, err := quoted.Split(extld)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
2
src/cmd/dist/buildtool.go
vendored
2
src/cmd/dist/buildtool.go
vendored
|
|
@ -46,8 +46,8 @@ var bootstrapDirs = []string{
|
|||
"cmd/internal/obj/...",
|
||||
"cmd/internal/objabi",
|
||||
"cmd/internal/pkgpath",
|
||||
"cmd/internal/quoted",
|
||||
"cmd/internal/src",
|
||||
"cmd/internal/str",
|
||||
"cmd/internal/sys",
|
||||
"cmd/link",
|
||||
"cmd/link/internal/...",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
// A Command is an implementation of a go command
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
var err error
|
||||
*v, err = str.SplitQuotedFields(s)
|
||||
*v, err = quoted.Split(s)
|
||||
if *v == nil {
|
||||
*v = []string{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
)
|
||||
|
||||
var CmdEnv = &base.Command{
|
||||
|
|
@ -470,7 +470,7 @@ func checkEnvWrite(key, val string) error {
|
|||
if val == "" {
|
||||
break
|
||||
}
|
||||
args, err := str.SplitQuotedFields(val)
|
||||
args, err := quoted.Split(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s: %v", key, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
var CmdGenerate = &base.Command{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import (
|
|||
"cmd/go/internal/vcs"
|
||||
"cmd/go/internal/web"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"cmd/go/internal/modinfo"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
var CmdList = &base.Command{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package load
|
|||
|
||||
import (
|
||||
"cmd/go/internal/base"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -63,7 +63,7 @@ func (f *PerPackageFlag) set(v, cwd string) error {
|
|||
match = MatchPackage(pattern, cwd)
|
||||
v = v[i+1:]
|
||||
}
|
||||
flags, err := str.SplitQuotedFields(v)
|
||||
flags, err := quoted.Split(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ import (
|
|||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/par"
|
||||
"cmd/go/internal/search"
|
||||
"cmd/go/internal/str"
|
||||
"cmd/go/internal/trace"
|
||||
"cmd/go/internal/vcs"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/sys"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/trace"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
var TestMainDeps = []string{
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"cmd/go/internal/imports"
|
||||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/lockedfile"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
// Downloaded size limits.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import (
|
|||
|
||||
"cmd/go/internal/lockedfile"
|
||||
"cmd/go/internal/par"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
// A VCSError indicates an error using a version control system.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/search"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ import (
|
|||
"cmd/go/internal/mvs"
|
||||
"cmd/go/internal/par"
|
||||
"cmd/go/internal/search"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"cmd/go/internal/modfetch"
|
||||
"cmd/go/internal/search"
|
||||
"cmd/go/internal/trace"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
var CmdRun = &base.Command{
|
||||
|
|
|
|||
111
src/cmd/go/internal/str/str.go
Normal file
111
src/cmd/go/internal/str/str.go
Normal 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
|
||||
}
|
||||
29
src/cmd/go/internal/str/str_test.go
Normal file
29
src/cmd/go/internal/str/str_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ import (
|
|||
"cmd/go/internal/search"
|
||||
"cmd/go/internal/trace"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
"cmd/internal/test2json"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/search"
|
||||
"cmd/go/internal/web"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/internal/buildid"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
// Build IDs
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ import (
|
|||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/str"
|
||||
"cmd/go/internal/trace"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"cmd/internal/sys"
|
||||
)
|
||||
|
||||
|
|
@ -2666,7 +2667,7 @@ func envList(key, def string) []string {
|
|||
if v == "" {
|
||||
v = def
|
||||
}
|
||||
args, err := str.SplitQuotedFields(v)
|
||||
args, err := quoted.Split(v)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not parse environment variable %s with value %q: %v", key, v, err))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ import (
|
|||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/load"
|
||||
"cmd/go/internal/str"
|
||||
"cmd/internal/objabi"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"cmd/internal/sys"
|
||||
"crypto/sha1"
|
||||
)
|
||||
|
|
@ -565,7 +566,7 @@ func setextld(ldflags []string, compiler []string) ([]string, error) {
|
|||
return ldflags, nil
|
||||
}
|
||||
}
|
||||
joined, err := str.JoinAndQuoteFields(compiler)
|
||||
joined, err := quoted.Join(compiler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/load"
|
||||
"cmd/internal/pkgpath"
|
||||
"cmd/internal/str"
|
||||
"cmd/go/internal/str"
|
||||
)
|
||||
|
||||
// The Gccgo toolchain.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fsys"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"cmd/internal/sys"
|
||||
"fmt"
|
||||
"os"
|
||||
|
|
@ -46,7 +46,7 @@ func BuildInit() {
|
|||
// Make sure CC, CXX, and FC are absolute paths.
|
||||
for _, key := range []string{"CC", "CXX", "FC"} {
|
||||
value := cfg.Getenv(key)
|
||||
args, err := str.SplitQuotedFields(value)
|
||||
args, err := quoted.Split(value)
|
||||
if err != nil {
|
||||
base.Fatalf("go: %s environment variable could not be parsed: %v", key, err)
|
||||
}
|
||||
|
|
|
|||
127
src/cmd/internal/quoted/quoted.go
Normal file
127
src/cmd/internal/quoted/quoted.go
Normal 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
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package str
|
||||
package quoted
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
|
@ -10,27 +10,7 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitQuotedFields(t *testing.T) {
|
||||
func TestSplit(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
value string
|
||||
|
|
@ -54,7 +34,7 @@ func TestSplitQuotedFields(t *testing.T) {
|
|||
{name: "quote_unclosed", value: `'a`, wantErr: "unterminated ' string"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := SplitQuotedFields(test.value)
|
||||
got, err := Split(test.value)
|
||||
if err != nil {
|
||||
if test.wantErr == "" {
|
||||
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 {
|
||||
name 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"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := JoinAndQuoteFields(test.args)
|
||||
got, err := Join(test.args)
|
||||
if err != nil {
|
||||
if test.wantErr == "" {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"bytes"
|
||||
cmddwarf "cmd/internal/dwarf"
|
||||
"cmd/internal/objfile"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"debug/dwarf"
|
||||
"internal/testenv"
|
||||
"os"
|
||||
|
|
@ -68,7 +68,7 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string)
|
|||
if extld == "" {
|
||||
extld = "gcc"
|
||||
}
|
||||
extldArgs, err := str.SplitQuotedFields(extld)
|
||||
extldArgs, err := quoted.Split(extld)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
"bufio"
|
||||
"cmd/internal/goobj"
|
||||
"cmd/internal/objabi"
|
||||
"cmd/internal/str"
|
||||
"cmd/internal/quoted"
|
||||
"cmd/internal/sys"
|
||||
"cmd/link/internal/benchmark"
|
||||
"flag"
|
||||
|
|
@ -76,8 +76,8 @@ var (
|
|||
flagLibGCC = flag.String("libgcc", "", "compiler support lib for internal linking; use \"none\" to disable")
|
||||
flagTmpdir = flag.String("tmpdir", "", "use `directory` for temporary files")
|
||||
|
||||
flagExtld str.QuotedStringListFlag
|
||||
flagExtldflags str.QuotedStringListFlag
|
||||
flagExtld quoted.Flag
|
||||
flagExtldflags quoted.Flag
|
||||
flagExtar = flag.String("extar", "", "archive program for buildmode=c-archive")
|
||||
|
||||
flagA = flag.Bool("a", false, "no-op (deprecated)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue