internal/str: add package

Change-Id: Icb5ec1385be4d200f19613eebcd4d290b3b01642
diff --git a/internal/str/path.go b/internal/str/path.go
new file mode 100644
index 0000000..a0f2c49
--- /dev/null
+++ b/internal/str/path.go
@@ -0,0 +1,61 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package str
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+// HasPath reports whether the slash-separated path s
+// begins with the elements in prefix.
+func HasPathPrefix(s, prefix string) bool {
+	if len(s) == len(prefix) {
+		return s == prefix
+	}
+	if prefix == "" {
+		return true
+	}
+	if len(s) > len(prefix) {
+		if prefix[len(prefix)-1] == '/' || s[len(prefix)] == '/' {
+			return s[:len(prefix)] == prefix
+		}
+	}
+	return false
+}
+
+// HasFilePathPrefix reports whether the filesystem path s
+// begins with the elements in prefix.
+func HasFilePathPrefix(s, prefix string) bool {
+	sv := strings.ToUpper(filepath.VolumeName(s))
+	pv := strings.ToUpper(filepath.VolumeName(prefix))
+	s = s[len(sv):]
+	prefix = prefix[len(pv):]
+	switch {
+	default:
+		return false
+	case sv != pv:
+		return false
+	case len(s) == len(prefix):
+		return s == prefix
+	case prefix == "":
+		return true
+	case len(s) > len(prefix):
+		if prefix[len(prefix)-1] == filepath.Separator {
+			return strings.HasPrefix(s, prefix)
+		}
+		return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
+	}
+}
diff --git a/internal/str/str.go b/internal/str/str.go
new file mode 100644
index 0000000..f8a44cb
--- /dev/null
+++ b/internal/str/str.go
@@ -0,0 +1,151 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package str provides string manipulation utilities.
+package str // import "cuelang.org/go/internal/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
+}
+
+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
+}