pkg/path: activate OS-dependent version
This also fixes a bad interaction between validators
and variable arguments: IsAbs qualifies as a validator,
but it is ambiguous to allow both. In the futurre we
could introduce an AbsDir function, or alike, that
functions only as a validator. Also `must` would help
here.
The fix has been left in this CL, as it isn't a problem
before this CL and it makes the context clear.
Overall, this doesn't seem to be too big of a deal.
Change-Id: I4f28ad507cd4d8a42c3d02bde28be53b7db3ad63
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7846
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 939ef6e..44891e0 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -980,10 +980,12 @@
Value Value // Could become Value later, using disjunctions for defaults.
}
+// Kind returns the kind mask of this parameter.
func (p Param) Kind() Kind {
return p.Value.Kind()
}
+// Default reports the default value for this Param or nil if there is none.
func (p Param) Default() Value {
d, ok := p.Value.(*Disjunction)
if !ok || d.NumDefaults != 1 {
@@ -1009,8 +1011,12 @@
return &BuiltinValidator{Builtin: x}
}
-func (x *Builtin) IsValidator(numArgs int) bool {
- return len(x.Params)-1 == numArgs && x.Result&^BoolKind == 0
+// IsValidator reports whether b should be interpreted as a Validator for the
+// given number of arguments.
+func (b *Builtin) IsValidator(numArgs int) bool {
+ return numArgs == len(b.Params)-1 &&
+ b.Result&^BoolKind == 0 &&
+ b.Params[numArgs].Default() == nil
}
func bottom(v Value) *Bottom {
@@ -1040,21 +1046,35 @@
args = append(args, v)
}
for i, a := range args {
- if x.Params[i].Kind() != BottomKind {
- if b := bottom(a); b != nil {
- return b
+ if x.Params[i].Kind() == BottomKind {
+ continue
+ }
+ if b := bottom(a); b != nil {
+ return b
+ }
+ if k := kind(a); x.Params[i].Kind()&k == BottomKind {
+ code := EvalError
+ b, _ := args[i].(*Bottom)
+ if b != nil {
+ code = b.Code
}
- if k := kind(a); x.Params[i].Kind()&k == BottomKind {
- code := EvalError
- b, _ := args[i].(*Bottom)
- if b != nil {
- code = b.Code
- }
- c.addErrf(code, pos(a),
- "cannot use %s (type %s) as %s in argument %d to %s",
- a, k, x.Params[i].Kind(), i+1, fun)
- return nil
+ c.addErrf(code, pos(a),
+ "cannot use %s (type %s) as %s in argument %d to %s",
+ a, k, x.Params[i].Kind(), i+1, fun)
+ return nil
+ }
+ v := x.Params[i].Value
+ if _, ok := v.(*BasicType); !ok {
+ env := c.Env(0)
+ x := &BinaryExpr{Op: AndOp, X: v, Y: a}
+ n := &Vertex{Conjuncts: []Conjunct{{env, x, 0}}}
+ c.Unifier.Unify(c, n, Finalized)
+ if _, ok := n.BaseValue.(*Bottom); ok {
+ c.addErrf(0, pos(a),
+ "cannot use %s as %s in argument %d to %s",
+ a, v, i+1, fun)
}
+ args[i] = n
}
}
return x.Func(c, args)
diff --git a/pkg/internal/builtin.go b/pkg/internal/builtin.go
index bbd0ccb..ab23868 100644
--- a/pkg/internal/builtin.go
+++ b/pkg/internal/builtin.go
@@ -55,9 +55,8 @@
}
type Param struct {
- Kind adt.Kind
- Value adt.Value // input constraint
- Default adt.Value // may be nil
+ Kind adt.Kind
+ Value adt.Value // input constraint (may be nil)
}
type Package struct {
@@ -112,16 +111,9 @@
func toBuiltin(ctx *adt.OpContext, b *Builtin) *adt.Builtin {
params := make([]adt.Param, len(b.Params))
for i, p := range b.Params {
- // TODO: use Value.
- params[i].Value = &adt.BasicType{K: p.Kind}
- if p.Default != nil {
- params[i].Value = &adt.Disjunction{
- NumDefaults: 1,
- Values: []*adt.Vertex{
- adt.ToVertex(p.Default),
- adt.ToVertex(params[i].Value),
- },
- }
+ params[i].Value = p.Value
+ if params[i].Value == nil {
+ params[i].Value = &adt.BasicType{K: p.Kind}
}
}
diff --git a/pkg/path/testdata/example_nix_test.go b/pkg/path/example_nix_test.go
similarity index 100%
rename from pkg/path/testdata/example_nix_test.go
rename to pkg/path/example_nix_test.go
diff --git a/pkg/path/testdata/example_test.go b/pkg/path/example_test.go
similarity index 100%
rename from pkg/path/testdata/example_test.go
rename to pkg/path/example_test.go
diff --git a/pkg/path/manual.go b/pkg/path/manual.go
deleted file mode 100644
index 99525a7..0000000
--- a/pkg/path/manual.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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 path
-
-import "path"
-
-var split = path.Split
-
-// Split splits path immediately following the final slash and returns them as
-// the list [dir, file], separating it into a directory and file name component.
-// If there is no slash in path, Split returns an empty dir and file set to
-// path. The returned values have the property that path = dir+file.
-func Split(path string) []string {
- file, dir := split(path)
- return []string{file, dir}
-}
diff --git a/pkg/path/testdata/match.go b/pkg/path/match.go
similarity index 100%
rename from pkg/path/testdata/match.go
rename to pkg/path/match.go
diff --git a/pkg/path/testdata/match_test.go b/pkg/path/match_test.go
similarity index 100%
rename from pkg/path/testdata/match_test.go
rename to pkg/path/match_test.go
diff --git a/pkg/path/testdata/os.go b/pkg/path/os.go
similarity index 96%
rename from pkg/path/testdata/os.go
rename to pkg/path/os.go
index d5de0be..08592c3 100644
--- a/pkg/path/testdata/os.go
+++ b/pkg/path/os.go
@@ -14,6 +14,7 @@
package path
+// OS must be a valid runtime.GOOS value or "unix".
type OS string
const (
diff --git a/pkg/path/path.go b/pkg/path/path.go
index bd82f26..9f29dd2 100644
--- a/pkg/path/path.go
+++ b/pkg/path/path.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The CUE Authors
+// Copyright 2020 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,54 +12,80 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Copyright 2018 The Go Authors. All rights reserved.
+// Copyright 2009 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.
-//go:generate go run cuelang.org/go/internal/cmd/qgo -exclude=Split,Join extract path
-
+// Package filepath implements utility routines for manipulating filename paths
+// in a way compatible with the target operating system-defined file paths.
+//
+// The filepath package uses either forward slashes or backslashes,
+// depending on the operating system. To process paths such as URLs
+// that always use forward slashes regardless of the operating
+// system, see the path package.
package path
-import "path"
+import (
+ "errors"
+ "strings"
+)
-// Match reports whether name matches the shell pattern.
-// The pattern syntax is:
-//
-// pattern:
-// { term }
-// term:
-// '*' matches any sequence of non-/ characters
-// '?' matches any single non-/ character
-// '[' [ '^' ] { character-range } ']'
-// character class (must be non-empty)
-// c matches character c (c != '*', '?', '\\', '[')
-// '\\' c matches character c
-//
-// character-range:
-// c matches character c (c != '\\', '-', ']')
-// '\\' c matches character c
-// lo '-' hi matches character c for lo <= c <= hi
-//
-// Match requires pattern to match all of name, not just a substring.
-// The only possible returned error is ErrBadPattern, when pattern
-// is malformed.
-//
-func Match(pattern, name string) (matched bool, err error) {
- return path.Match(pattern, name)
+// A lazybuf is a lazily constructed path buffer.
+// It supports append, reading previously appended bytes,
+// and retrieving the final string. It does not allocate a buffer
+// to hold the output until that output diverges.
+type lazybuf struct {
+ path string
+ buf []byte
+ w int
+ volAndPath string
+ volLen int
+}
+
+func (b *lazybuf) index(i int) byte {
+ if b.buf != nil {
+ return b.buf[i]
+ }
+ return b.path[i]
+}
+
+func (b *lazybuf) append(c byte) {
+ if b.buf == nil {
+ if b.w < len(b.path) && b.path[b.w] == c {
+ b.w++
+ return
+ }
+ b.buf = make([]byte, len(b.path))
+ copy(b.buf, b.path[:b.w])
+ }
+ b.buf[b.w] = c
+ b.w++
+}
+
+func (b *lazybuf) string() string {
+ if b.buf == nil {
+ return b.volAndPath[:b.volLen+b.w]
+ }
+ return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
}
// Clean returns the shortest path name equivalent to path
-// by purely lexical processing. It applies the following rules
+// by purely lexical processing. The default value for os is Unix.
+// It applies the following rules
// iteratively until no further processing can be done:
//
-// 1. Replace multiple slashes with a single slash.
+// 1. Replace multiple Separator elements with a single one.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
-// that is, replace "/.." by "/" at the beginning of a path.
+// that is, replace "/.." by "/" at the beginning of a path,
+// assuming Separator is '/'.
//
-// The returned path ends in a slash only if it is the root "/".
+// The returned path ends in a slash only if it represents a root directory,
+// such as "/" on Unix or `C:\` on Windows.
+//
+// Finally, any occurrences of slash are replaced by Separator.
//
// If the result of this process is an empty string, Clean
// returns the string ".".
@@ -67,38 +93,319 @@
// See also Rob Pike, ``Lexical File Names in Plan 9 or
// Getting Dot-Dot Right,''
// https://9p.io/sys/doc/lexnames.html
-func Clean(path string) string { return pathClean(path) }
+func Clean(path string, os OS) string {
+ return clean(path, getOS(os))
+}
-var pathClean = path.Clean
+func clean(path string, os os) string {
+ originalPath := path
+ volLen := os.volumeNameLen(path)
+ path = path[volLen:]
+ if path == "" {
+ if volLen > 1 && originalPath[1] != ':' {
+ // should be UNC
+ return fromSlash(originalPath, os)
+ }
+ return originalPath + "."
+ }
+ rooted := os.IsPathSeparator(path[0])
+
+ // Invariants:
+ // reading from path; r is index of next byte to process.
+ // writing to buf; w is index of next byte to write.
+ // dotdot is index in buf where .. must stop, either because
+ // it is the leading slash or it is a leading ../../.. prefix.
+ n := len(path)
+ out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
+ r, dotdot := 0, 0
+ if rooted {
+ out.append(os.Separator)
+ r, dotdot = 1, 1
+ }
+
+ for r < n {
+ switch {
+ case os.IsPathSeparator(path[r]):
+ // empty path element
+ r++
+ case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
+ // . element
+ r++
+ case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
+ // .. element: remove to last separator
+ r += 2
+ switch {
+ case out.w > dotdot:
+ // can backtrack
+ out.w--
+ for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
+ out.w--
+ }
+ case !rooted:
+ // cannot backtrack, but not rooted, so append .. element.
+ if out.w > 0 {
+ out.append(os.Separator)
+ }
+ out.append('.')
+ out.append('.')
+ dotdot = out.w
+ }
+ default:
+ // real path element.
+ // add slash if needed
+ if rooted && out.w != 1 || !rooted && out.w != 0 {
+ out.append(os.Separator)
+ }
+ // copy element
+ for ; r < n && !os.IsPathSeparator(path[r]); r++ {
+ out.append(path[r])
+ }
+ }
+ }
+
+ // Turn empty string into "."
+ if out.w == 0 {
+ out.append('.')
+ }
+
+ return fromSlash(out.string(), os)
+}
+
+// ToSlash returns the result of replacing each separator character
+// in path with a slash ('/') character. Multiple separators are
+// replaced by multiple slashes.
+func ToSlash(path string, os OS) string {
+ return toSlash(path, getOS(os))
+}
+
+func toSlash(path string, os os) string {
+ if os.Separator == '/' {
+ return path
+ }
+ return strings.ReplaceAll(path, string(os.Separator), "/")
+}
+
+// FromSlash returns the result of replacing each slash ('/') character
+// in path with a separator character. Multiple slashes are replaced
+// by multiple separators.
+func FromSlash(path string, os OS) string {
+ return fromSlash(path, getOS(os))
+}
+
+func fromSlash(path string, os os) string {
+ if os.Separator == '/' {
+ return path
+ }
+ return strings.ReplaceAll(path, "/", string(os.Separator))
+}
+
+// SplitList splits a list of paths joined by the OS-specific ListSeparator,
+// usually found in PATH or GOPATH environment variables.
+// Unlike strings.Split, SplitList returns an empty slice when passed an empty
+// string.
+func SplitList(path string, os OS) []string {
+ return getOS(os).splitList(path)
+}
+
+// Split splits path immediately following the final slash and returns them as
+// the list [dir, file], separating it into a directory and file name component.
+// If there is no slash in path, Split returns an empty dir and file set to
+// path. The returned values have the property that path = dir+file.
+// The default value for os is Unix.
+func Split(path string, os OS) []string {
+ x := getOS(os)
+ vol := volumeName(path, x)
+ i := len(path) - 1
+ for i >= len(vol) && !x.IsPathSeparator(path[i]) {
+ i--
+ }
+ return []string{path[:i+1], path[i+1:]}
+}
+
+// Join joins any number of path elements into a single path,
+// separating them with an OS specific Separator. Empty elements
+// are ignored. The result is Cleaned. However, if the argument
+// list is empty or all its elements are empty, Join returns
+// an empty string.
+// On Windows, the result will only be a UNC path if the first
+// non-empty element is a UNC path.
+// The default value for os is Unix.
+func Join(elem []string, os OS) string {
+ return getOS(os).join(elem)
+}
// Ext returns the file name extension used by path.
// The extension is the suffix beginning at the final dot
-// in the final slash-separated element of path;
-// it is empty if there is no dot.
-func Ext(path string) string { return pathExt(path) }
+// in the final element of path; it is empty if there is
+// no dot. The default value for os is Unix.
+func Ext(path string, os OS) string {
+ x := getOS(os)
+ for i := len(path) - 1; i >= 0 && !x.IsPathSeparator(path[i]); i-- {
+ if path[i] == '.' {
+ return path[i:]
+ }
+ }
+ return ""
+}
-var pathExt = path.Ext
+// Resolve reports the path of sub relative to dir. If sub is an absolute path,
+// or if dir is empty, it will return sub. If sub is empty, it will return dir.
+// Resolve calls Clean on the result. The default value for os is Unix.
+func Resolve(dir, sub string, os OS) string {
+ x := getOS(os)
+ if x.IsAbs(sub) {
+ return clean(sub, x)
+ }
+ dir = clean(dir, x)
+ return x.join([]string{dir, sub})
+}
+
+// Rel returns a relative path that is lexically equivalent to targpath when
+// joined to basepath with an intervening separator. That is,
+// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
+// On success, the returned path will always be relative to basepath,
+// even if basepath and targpath share no elements.
+// An error is returned if targpath can't be made relative to basepath or if
+// knowing the current working directory would be necessary to compute it.
+// Rel calls Clean on the result. The default value for os is Unix.
+func Rel(basepath, targpath string, os OS) (string, error) {
+ x := getOS(os)
+ baseVol := volumeName(basepath, x)
+ targVol := volumeName(targpath, x)
+ base := clean(basepath, x)
+ targ := clean(targpath, x)
+ if x.sameWord(targ, base) {
+ return ".", nil
+ }
+ base = base[len(baseVol):]
+ targ = targ[len(targVol):]
+ if base == "." {
+ base = ""
+ }
+ // Can't use IsAbs - `\a` and `a` are both relative in Windows.
+ baseSlashed := len(base) > 0 && base[0] == x.Separator
+ targSlashed := len(targ) > 0 && targ[0] == x.Separator
+ if baseSlashed != targSlashed || !x.sameWord(baseVol, targVol) {
+ return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
+ }
+ // Position base[b0:bi] and targ[t0:ti] at the first differing elements.
+ bl := len(base)
+ tl := len(targ)
+ var b0, bi, t0, ti int
+ for {
+ for bi < bl && base[bi] != x.Separator {
+ bi++
+ }
+ for ti < tl && targ[ti] != x.Separator {
+ ti++
+ }
+ if !x.sameWord(targ[t0:ti], base[b0:bi]) {
+ break
+ }
+ if bi < bl {
+ bi++
+ }
+ if ti < tl {
+ ti++
+ }
+ b0 = bi
+ t0 = ti
+ }
+ if base[b0:bi] == ".." {
+ return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
+ }
+ if b0 != bl {
+ // Base elements left. Must go up before going down.
+ seps := strings.Count(base[b0:bl], string(x.Separator))
+ size := 2 + seps*3
+ if tl != t0 {
+ size += 1 + tl - t0
+ }
+ buf := make([]byte, size)
+ n := copy(buf, "..")
+ for i := 0; i < seps; i++ {
+ buf[n] = x.Separator
+ copy(buf[n+1:], "..")
+ n += 3
+ }
+ if t0 != tl {
+ buf[n] = x.Separator
+ copy(buf[n+1:], targ[t0:])
+ }
+ return string(buf), nil
+ }
+ return targ[t0:], nil
+}
// Base returns the last element of path.
-// Trailing slashes are removed before extracting the last element.
+// Trailing path separators are removed before extracting the last element.
// If the path is empty, Base returns ".".
-// If the path consists entirely of slashes, Base returns "/".
-func Base(path string) string { return pathBase(path) }
-
-var pathBase = path.Base
-
-// IsAbs reports whether the path is absolute.
-func IsAbs(path string) bool { return pathIsAbs(path) }
-
-var pathIsAbs = path.IsAbs
+// If the path consists entirely of separators, Base returns a single separator.
+// The default value for os is Unix.
+func Base(path string, os OS) string {
+ x := getOS(os)
+ if path == "" {
+ return "."
+ }
+ // Strip trailing slashes.
+ for len(path) > 0 && x.IsPathSeparator(path[len(path)-1]) {
+ path = path[0 : len(path)-1]
+ }
+ // Throw away volume name
+ path = path[x.volumeNameLen(path):]
+ // Find the last element
+ i := len(path) - 1
+ for i >= 0 && !x.IsPathSeparator(path[i]) {
+ i--
+ }
+ if i >= 0 {
+ path = path[i+1:]
+ }
+ // If empty now, it had only slashes.
+ if path == "" {
+ return string(x.Separator)
+ }
+ return path
+}
// Dir returns all but the last element of path, typically the path's directory.
-// After dropping the final element using Split, the path is Cleaned and trailing
+// After dropping the final element, Dir calls Clean on the path and trailing
// slashes are removed.
// If the path is empty, Dir returns ".".
-// If the path consists entirely of slashes followed by non-slash bytes, Dir
-// returns a single slash. In any other case, the returned path does not end in a
-// slash.
-func Dir(path string) string { return pathDir(path) }
+// If the path consists entirely of separators, Dir returns a single separator.
+// The returned path does not end in a separator unless it is the root directory.
+// The default value for os is Unix.
+func Dir(path string, os OS) string {
+ x := getOS(os)
+ vol := volumeName(path, x)
+ i := len(path) - 1
+ for i >= len(vol) && !x.IsPathSeparator(path[i]) {
+ i--
+ }
+ dir := clean(path[len(vol):i+1], x)
+ if dir == "." && len(vol) > 2 {
+ // must be UNC
+ return vol
+ }
+ return vol + dir
+}
-var pathDir = path.Dir
+// IsAbs reports whether the path is absolute. The default value for os is Unix.
+// Note that because IsAbs has a default value, it cannot be used as
+// a validator.
+func IsAbs(path string, os OS) bool {
+ return getOS(os).IsAbs(path)
+}
+
+// VolumeName returns leading volume name.
+// Given "C:\foo\bar" it returns "C:" on Windows.
+// Given "\\host\share\foo" it returns "\\host\share".
+// On other platforms it returns "".
+// The default value for os is Windows.
+func VolumeName(path string, os OS) string {
+ return volumeName(path, getOS(os))
+}
+
+func volumeName(path string, os os) string {
+ return path[:os.volumeNameLen(path)]
+}
diff --git a/pkg/path/testdata/path_nix.go b/pkg/path/path_nix.go
similarity index 100%
rename from pkg/path/testdata/path_nix.go
rename to pkg/path/path_nix.go
diff --git a/pkg/path/testdata/path_p9.go b/pkg/path/path_p9.go
similarity index 100%
rename from pkg/path/testdata/path_p9.go
rename to pkg/path/path_p9.go
diff --git a/pkg/path/testdata/path_test.go b/pkg/path/path_test.go
similarity index 100%
rename from pkg/path/testdata/path_test.go
rename to pkg/path/path_test.go
diff --git a/pkg/path/testdata/path_win.go b/pkg/path/path_win.go
similarity index 100%
rename from pkg/path/testdata/path_win.go
rename to pkg/path/path_win.go
diff --git a/pkg/path/testdata/path_windows_test.go b/pkg/path/path_windows_test.go
similarity index 100%
rename from pkg/path/testdata/path_windows_test.go
rename to pkg/path/path_windows_test.go
diff --git a/pkg/path/testdata/pathtxtar_test.go b/pkg/path/pathtxtar_test.go
similarity index 100%
rename from pkg/path/testdata/pathtxtar_test.go
rename to pkg/path/pathtxtar_test.go
diff --git a/pkg/path/pkg.go b/pkg/path/pkg.go
index 5446870..dbe0f74 100644
--- a/pkg/path/pkg.go
+++ b/pkg/path/pkg.go
@@ -1,7 +1,16 @@
-// Code generated by go generate. DO NOT EDIT.
-
-//go:generate rm pkg.go
-//go:generate go run ../gen/gen.go
+// Copyright 2020 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 path
@@ -16,90 +25,245 @@
var _ = adt.TopKind // in case the adt package isn't used
+var (
+ osRequired = &adt.Disjunction{
+ Values: allOS,
+ }
+
+ unixDefault = &adt.Disjunction{
+ NumDefaults: 1,
+ Values: allOS,
+ }
+
+ // windowsDefault is the default for VolumeName.
+ windowsDefault = &adt.Disjunction{
+ NumDefaults: 1,
+ Values: append([]*adt.Vertex{
+ newStr("windows"),
+ newStr("unix"),
+ newStr("plan9")}, unixOS...),
+ }
+
+ allOS = append([]*adt.Vertex{
+ newStr("unix"),
+ newStr("windows"),
+ newStr("plan9"),
+ }, unixOS...)
+
+ // These all fall back to unix
+ unixOS = []*adt.Vertex{
+ newStr("aix"),
+ newStr("android"),
+ newStr("darwin"),
+ newStr("dragonfly"),
+ newStr("freebsd"),
+ newStr("hurd"),
+ newStr("illumos"),
+ newStr("ios"),
+ newStr("js"),
+ newStr("linux"),
+ newStr("nacl"),
+ newStr("netbsd"),
+ newStr("openbsd"),
+ newStr("solaris"),
+ newStr("zos"),
+ }
+)
+
+func newStr(s string) *adt.Vertex {
+ v := &adt.Vertex{}
+ v.SetValue(nil, adt.Finalized, &adt.String{Str: s})
+ return v
+}
+
var pkg = &internal.Package{
+ CUE: `{
+ Unix: "unix"
+ Windows: "windows"
+ Plan9: "plan9"
+ }`,
Native: []*internal.Builtin{{
Name: "Split",
Params: []internal.Param{
{Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
},
Result: adt.ListKind,
Func: func(c *internal.CallCtxt) {
- path := c.String(0)
+ path, os := c.String(0), c.String(1)
if c.Do() {
- c.Ret = Split(path)
+ c.Ret = Split(path, OS(os))
+ }
+ },
+ }, {
+ Name: "SplitList",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: osRequired},
+ },
+ Result: adt.ListKind,
+ Func: func(c *internal.CallCtxt) {
+ path, os := c.String(0), c.String(1)
+ if c.Do() {
+ c.Ret = SplitList(path, OS(os))
+ }
+ },
+ }, {
+ Name: "Join",
+ Params: []internal.Param{
+ {Kind: adt.ListKind},
+ {Kind: adt.StringKind, Value: unixDefault},
+ },
+ Result: adt.StringKind,
+ Func: func(c *internal.CallCtxt) {
+ list, os := c.StringList(0), c.String(1)
+ if c.Do() {
+ c.Ret = Join(list, OS(os))
}
},
}, {
Name: "Match",
Params: []internal.Param{
{Kind: adt.StringKind},
- {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
},
Result: adt.BoolKind,
Func: func(c *internal.CallCtxt) {
- pattern, name := c.String(0), c.String(1)
+ pattern, name, os := c.String(0), c.String(1), c.String(2)
if c.Do() {
- c.Ret, c.Err = Match(pattern, name)
+ c.Ret, c.Err = Match(pattern, name, OS(os))
}
},
}, {
Name: "Clean",
Params: []internal.Param{
{Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
},
Result: adt.StringKind,
Func: func(c *internal.CallCtxt) {
- path := c.String(0)
+ path, os := c.String(0), c.String(1)
if c.Do() {
- c.Ret = Clean(path)
+ c.Ret = Clean(path, OS(os))
+ }
+ },
+ }, {
+ Name: "ToSlash",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: osRequired},
+ },
+ Result: adt.StringKind,
+ Func: func(c *internal.CallCtxt) {
+ path, os := c.String(0), c.String(1)
+ if c.Do() {
+ c.Ret = ToSlash(path, OS(os))
+ }
+ },
+ }, {
+ Name: "FromSlash",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: osRequired},
+ },
+ Result: adt.StringKind,
+ Func: func(c *internal.CallCtxt) {
+ path, os := c.String(0), c.String(1)
+ if c.Do() {
+ c.Ret = FromSlash(path, OS(os))
}
},
}, {
Name: "Ext",
Params: []internal.Param{
{Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
},
Result: adt.StringKind,
Func: func(c *internal.CallCtxt) {
- path := c.String(0)
+ path, os := c.String(0), c.String(1)
if c.Do() {
- c.Ret = Ext(path)
+ c.Ret = Ext(path, OS(os))
+ }
+ },
+ }, {
+ Name: "Resolve",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
+ },
+ Result: adt.StringKind,
+ Func: func(c *internal.CallCtxt) {
+ dir, sub, os := c.String(0), c.String(1), c.String(2)
+ if c.Do() {
+ c.Ret = Resolve(dir, sub, OS(os))
+ }
+ },
+ }, {
+ Name: "Rel",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
+ },
+ Result: adt.StringKind,
+ Func: func(c *internal.CallCtxt) {
+ base, target, os := c.String(0), c.String(1), c.String(2)
+ if c.Do() {
+ c.Ret, c.Err = Rel(base, target, OS(os))
}
},
}, {
Name: "Base",
Params: []internal.Param{
{Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
},
Result: adt.StringKind,
Func: func(c *internal.CallCtxt) {
- path := c.String(0)
+ path, os := c.String(0), c.String(1)
if c.Do() {
- c.Ret = Base(path)
- }
- },
- }, {
- Name: "IsAbs",
- Params: []internal.Param{
- {Kind: adt.StringKind},
- },
- Result: adt.BoolKind,
- Func: func(c *internal.CallCtxt) {
- path := c.String(0)
- if c.Do() {
- c.Ret = IsAbs(path)
+ c.Ret = Base(path, OS(os))
}
},
}, {
Name: "Dir",
Params: []internal.Param{
{Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
},
Result: adt.StringKind,
Func: func(c *internal.CallCtxt) {
- path := c.String(0)
+ path, os := c.String(0), c.String(1)
if c.Do() {
- c.Ret = Dir(path)
+ c.Ret = Dir(path, OS(os))
+ }
+ },
+ }, {
+ Name: "IsAbs",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: unixDefault},
+ },
+ Result: adt.BoolKind,
+ Func: func(c *internal.CallCtxt) {
+ path, os := c.String(0), c.String(1)
+ if c.Do() {
+ c.Ret = IsAbs(path, OS(os))
+ }
+ },
+ }, {
+ Name: "VolumeName",
+ Params: []internal.Param{
+ {Kind: adt.StringKind},
+ {Kind: adt.StringKind, Value: windowsDefault},
+ },
+ Result: adt.StringKind,
+ Func: func(c *internal.CallCtxt) {
+ path, os := c.String(0), c.String(1)
+ if c.Do() {
+ c.Ret = VolumeName(path, OS(os))
}
},
}},
diff --git a/pkg/path/testdata/error.txtar b/pkg/path/testdata/error.txtar
new file mode 100644
index 0000000..f53048c
--- /dev/null
+++ b/pkg/path/testdata/error.txtar
@@ -0,0 +1,28 @@
+// Copyright 2020 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.
+
+-- in.cue --
+import "path"
+
+joinOK: path.Join(["a", "b"], "aix")
+joinErr: path.Join(["a", "b"], "foo")
+-- out/path --
+Errors:
+joinErr: cannot use "foo" as *"unix" | "windows" | "plan9" | "aix" | "android" | "darwin" | "dragonfly" | "freebsd" | "hurd" | "illumos" | "ios" | "js" | "linux" | "nacl" | "netbsd" | "openbsd" | "solaris" | "zos" in argument 2 to path.Join:
+ ./in.cue:4:32
+
+Result:
+joinOK: "a/b"
+joinErr: _|_ // joinErr: cannot use "foo" as *"unix" | "windows" | "plan9" | "aix" | "android" | "darwin" | "dragonfly" | "freebsd" | "hurd" | "illumos" | "ios" | "js" | "linux" | "nacl" | "netbsd" | "openbsd" | "solaris" | "zos" in argument 2 to path.Join (and 1 more errors)
+
diff --git a/pkg/path/testdata/join.txtar b/pkg/path/testdata/join.txtar
new file mode 100644
index 0000000..82b6b55
--- /dev/null
+++ b/pkg/path/testdata/join.txtar
@@ -0,0 +1,205 @@
+// Copyright 2020 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.
+
+
+-- in.cue --
+import "path"
+
+joinSingle: path.Join(["a", "b"])
+joinSingle: "a/b"
+
+Join: unix: _
+Join: windows: _
+
+Join: [OS=string]: [...{
+ arg: [...string]
+
+ out: path.Join(arg, OS)
+}]
+
+Join: [_]: [
+ {arg: ["a", "b"]},
+ {arg: ["a/b", "c/d"]},
+
+ {arg: ["/"]},
+ {arg: ["a"]},
+
+ {arg: ["a", "b"]},
+ {arg: ["a", ""]},
+ {arg: ["", "b"]},
+ {arg: ["/", "a"]},
+ {arg: ["/", "a/b"]},
+ {arg: ["/", ""]},
+ {arg: ["//", "a"]},
+
+ {arg: ["directory", "file"]},
+
+ {arg: [#"C:\Windows\"#, #"System32"#]},
+ {arg: [#"C:\Windows\"#, #""#]},
+ {arg: [#"C:\"#, #"Windows"#]},
+ {arg: [#"C:"#, #"a\b"#]},
+ {arg: [#"C:"#, #"a"#, #"b"#]},
+ {arg: [#"C:"#, #""#, #""#, #"b"#]},
+ {arg: [#"C:"#, #""#]},
+ {arg: [#"C:"#, #""#, #""#]},
+ {arg: [#"C:."#, #"a"#]},
+ {arg: [#"C:a"#, #"b"#]},
+ {arg: [#"\\host\share"#, #"foo"#]},
+]
+
+-- out/path --
+joinSingle: "a/b"
+Join: {
+ unix: [{
+ arg: ["a", "b"]
+ out: "a/b"
+ }, {
+ arg: ["a/b", "c/d"]
+ out: "a/b/c/d"
+ }, {
+ arg: ["/"]
+ out: "/"
+ }, {
+ arg: ["a"]
+ out: "a"
+ }, {
+ arg: ["a", "b"]
+ out: "a/b"
+ }, {
+ arg: ["a", ""]
+ out: "a"
+ }, {
+ arg: ["", "b"]
+ out: "b"
+ }, {
+ arg: ["/", "a"]
+ out: "/a"
+ }, {
+ arg: ["/", "a/b"]
+ out: "/a/b"
+ }, {
+ arg: ["/", ""]
+ out: "/"
+ }, {
+ arg: ["//", "a"]
+ out: "/a"
+ }, {
+ arg: ["directory", "file"]
+ out: "directory/file"
+ }, {
+ arg: [#"C:\Windows\"#, #"System32"#]
+ out: "C:\\Windows\\/System32"
+ }, {
+ arg: [#"C:\Windows\"#, #""#]
+ out: "C:\\Windows\\"
+ }, {
+ arg: [#"C:\"#, #"Windows"#]
+ out: "C:\\/Windows"
+ }, {
+ arg: [#"C:"#, #"a\b"#]
+ out: "C:/a\\b"
+ }, {
+ arg: [#"C:"#, #"a"#, #"b"#]
+ out: "C:/a/b"
+ }, {
+ arg: [#"C:"#, #""#, #""#, #"b"#]
+ out: "C:/b"
+ }, {
+ arg: [#"C:"#, #""#]
+ out: "C:"
+ }, {
+ arg: [#"C:"#, #""#, #""#]
+ out: "C:"
+ }, {
+ arg: [#"C:."#, #"a"#]
+ out: "C:./a"
+ }, {
+ arg: [#"C:a"#, #"b"#]
+ out: "C:a/b"
+ }, {
+ arg: [#"\\host\share"#, #"foo"#]
+ out: "\\\\host\\share/foo"
+ }]
+ windows: [{
+ arg: ["a", "b"]
+ out: "a\\b"
+ }, {
+ arg: ["a/b", "c/d"]
+ out: "a\\b\\c\\d"
+ }, {
+ arg: ["/"]
+ out: "\\"
+ }, {
+ arg: ["a"]
+ out: "a"
+ }, {
+ arg: ["a", "b"]
+ out: "a\\b"
+ }, {
+ arg: ["a", ""]
+ out: "a"
+ }, {
+ arg: ["", "b"]
+ out: "b"
+ }, {
+ arg: ["/", "a"]
+ out: "\\a"
+ }, {
+ arg: ["/", "a/b"]
+ out: "\\a\\b"
+ }, {
+ arg: ["/", ""]
+ out: "\\"
+ }, {
+ arg: ["//", "a"]
+ out: "\\a"
+ }, {
+ arg: ["directory", "file"]
+ out: "directory\\file"
+ }, {
+ arg: [#"C:\Windows\"#, #"System32"#]
+ out: "C:\\Windows\\System32"
+ }, {
+ arg: [#"C:\Windows\"#, #""#]
+ out: "C:\\Windows"
+ }, {
+ arg: [#"C:\"#, #"Windows"#]
+ out: "C:\\Windows"
+ }, {
+ arg: [#"C:"#, #"a\b"#]
+ out: "C:a\\b"
+ }, {
+ arg: [#"C:"#, #"a"#, #"b"#]
+ out: "C:a\\b"
+ }, {
+ arg: [#"C:"#, #""#, #""#, #"b"#]
+ out: "C:b"
+ }, {
+ arg: [#"C:"#, #""#]
+ out: "C:."
+ }, {
+ arg: [#"C:"#, #""#, #""#]
+ out: "C:."
+ }, {
+ arg: [#"C:."#, #"a"#]
+ out: "C:a"
+ }, {
+ arg: [#"C:a"#, #"b"#]
+ out: "C:a\\b"
+ }, {
+ arg: [#"\\host\share"#, #"foo"#]
+ out: "\\\\host\\share\\foo"
+ }]
+}
+
diff --git a/pkg/path/testdata/os.txtar b/pkg/path/testdata/os.txtar
new file mode 100644
index 0000000..ba253da
--- /dev/null
+++ b/pkg/path/testdata/os.txtar
@@ -0,0 +1,504 @@
+// Copyright 2020 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.
+
+
+-- in.cue --
+import "path"
+
+#OSes: [path.Unix, path.Windows, path.Plan9]
+#AnyOS: or(#OSes)
+
+// Test these OSes for all tests below.
+{
+ [string]: {
+ unix: _
+ plan9: _
+ windows: _
+ }
+}
+
+Split: [OS=#AnyOS]: [ARG=string]: path.Split(ARG, OS)
+Split: default: [ARG=string]: path.Split(ARG)
+Split: default: Split.unix
+Split: [_]: {
+ "/foo/bar/baz": _
+ "a/b": _
+ "//host/share/foo": _
+ #"\\host\share\foo"#: _
+ "c:/foo/bar": _
+ #"c:\foo\bar"#: _
+}
+
+SplitList: [OS=string]: [ARG=string]: path.SplitList(ARG, OS)
+SplitList: [_]: {
+ "a:b": _
+ "a\u0000b": _
+ "a;b": _
+}
+
+Clean: [OS=#AnyOS]: [ARG=string]: path.Clean(ARG, OS)
+Clean: default: [ARG=string]: path.Clean(ARG)
+Clean: default: Clean.unix
+Clean: [_]: {
+ "abc//def//ghi": _
+ #"c:\abc\def\..\.."#: _
+}
+
+Slash: [OS=string]: [ARG=string]: {
+ to: path.ToSlash(ARG, OS)
+ from: path.FromSlash(ARG, OS)
+
+ // should roundtrip
+ to: path.ToSlash(from, OS)
+ from: path.FromSlash(to, OS)
+}
+Slash: [_]: {
+ "": _
+ "/": _
+ "/a/b": _
+ "/a//b": _
+}
+
+Ext: [OS=#AnyOS]: [ARG=string]: path.Ext(ARG, OS)
+Ext: default: [ARG=string]: path.Ext(ARG)
+Ext: default: Ext.unix
+Ext: [_]: {
+ // Same for all OS-es
+ "path.go": ".go"
+ "path.pb.go": ".go"
+ "a.dir/b": ""
+ "a.dir/b.go": ".go"
+ "a.dir/": ""
+
+ // Differs on Windows.
+ "a.dir\\foo": _
+}
+
+Resolve: [OS=#AnyOS]: [A1=_]: [A2=_]: path.Resolve(A1, A2, OS)
+Resolve: default: [A1=_]: [A2=_]: path.Resolve(A1, A2)
+Resolve: default: Resolve.unix
+Resolve: [_]: {
+ "a/b/c": "d/e": _
+ "/a/b": "/c/d": _
+ "c:/a": #"d:\"#: _
+
+ "//home/user/foo": "bar": _
+ "//home/user/foo": "//other/abs/foo": _
+}
+
+IsAbs: [OS=#AnyOS]: [ARG=string]: path.IsAbs(ARG, OS)
+IsAbs: default: [ARG=string]: path.IsAbs(ARG)
+IsAbs: default: IsAbs.unix
+IsAbs: [_]: {
+ "": _
+ "/a": _
+ "a": _
+ "c:": _
+ "c:/": _
+ "c:\\": _
+
+ "//home/user/foo": _
+}
+
+
+Volume: [OS=string]: [ARG=string]: path.VolumeName(ARG, OS)
+Volume: [!="windows"]: [string]: "" // non-windows is always ""
+Volume: [_]: {
+ "c:/foo/bar": _
+ "c:": _
+ "2:": _
+ "": _
+
+ #"\\\host"#: _
+ #"\\\host\"#: _
+ #"\\\host\share"#: _
+ #"\\\host\\share"#: _
+ #"\\host"#: _
+ #"//host"#: _
+ #"\\host\"#: _
+ #"//host/"#: _
+ #"\\host\share"#: _
+ #"//host/share"#: _
+ #"\\host\share\"#: _
+ #"//host/share/"#: _
+ #"\\host\share\foo"#: _
+ #"//host/share/foo"#: _
+
+ #"\\host\share\\foo\\\bar\\\\baz"#: _
+ #"//host/share//foo///bar////baz"#: _
+ #"\\host\share\foo\..\bar"#: _
+ #"//host/share/foo/../bar"#: _
+}
+
+-- out/path --
+#OSes: ["unix", "windows", "plan9"]
+#AnyOS: "unix" | "windows" | "plan9"
+Split: {
+ unix: {
+ "/foo/bar/baz": ["/foo/bar/", "baz"]
+ "a/b": ["a/", "b"]
+ "//host/share/foo": ["//host/share/", "foo"]
+ "\\\\host\\share\\foo": ["", "\\\\host\\share\\foo"]
+ "c:/foo/bar": ["c:/foo/", "bar"]
+ "c:\\foo\\bar": ["", "c:\\foo\\bar"]
+ }
+ plan9: {
+ "/foo/bar/baz": ["/foo/bar/", "baz"]
+ "a/b": ["a/", "b"]
+ "//host/share/foo": ["//host/share/", "foo"]
+ "\\\\host\\share\\foo": ["", "\\\\host\\share\\foo"]
+ "c:/foo/bar": ["c:/foo/", "bar"]
+ "c:\\foo\\bar": ["", "c:\\foo\\bar"]
+ }
+ default: {
+ "/foo/bar/baz": ["/foo/bar/", "baz"]
+ "a/b": ["a/", "b"]
+ "//host/share/foo": ["//host/share/", "foo"]
+ "\\\\host\\share\\foo": ["", "\\\\host\\share\\foo"]
+ "c:/foo/bar": ["c:/foo/", "bar"]
+ "c:\\foo\\bar": ["", "c:\\foo\\bar"]
+ }
+ windows: {
+ "/foo/bar/baz": ["/foo/bar/", "baz"]
+ "a/b": ["a/", "b"]
+ "//host/share/foo": ["//host/share/", "foo"]
+ "\\\\host\\share\\foo": ["\\\\host\\share\\", "foo"]
+ "c:/foo/bar": ["c:/foo/", "bar"]
+ "c:\\foo\\bar": ["c:\\foo\\", "bar"]
+ }
+}
+SplitList: {
+ unix: {
+ "a:b": ["a", "b"]
+ "a\u0000b": ["a\u0000b"]
+ "a;b": ["a;b"]
+ }
+ plan9: {
+ "a:b": ["a:b"]
+ "a\u0000b": ["a", "b"]
+ "a;b": ["a;b"]
+ }
+ windows: {
+ "a:b": ["a:b"]
+ "a\u0000b": ["a\u0000b"]
+ "a;b": ["a", "b"]
+ }
+}
+Clean: {
+ unix: {
+ "abc//def//ghi": "abc/def/ghi"
+ "c:\\abc\\def\\..\\..": "c:\\abc\\def\\..\\.."
+ }
+ plan9: {
+ "abc//def//ghi": "abc/def/ghi"
+ "c:\\abc\\def\\..\\..": "c:\\abc\\def\\..\\.."
+ }
+ default: {
+ "abc//def//ghi": "abc/def/ghi"
+ "c:\\abc\\def\\..\\..": "c:\\abc\\def\\..\\.."
+ }
+ windows: {
+ "abc//def//ghi": "abc\\def\\ghi"
+ "c:\\abc\\def\\..\\..": "c:\\"
+ }
+}
+Slash: {
+ unix: {
+ "": {
+ // should roundtrip
+ to: ""
+ from: ""
+ }
+ "/": {
+ // should roundtrip
+ to: "/"
+ from: "/"
+ }
+ "/a/b": {
+ // should roundtrip
+ to: "/a/b"
+ from: "/a/b"
+ }
+ "/a//b": {
+ // should roundtrip
+ to: "/a//b"
+ from: "/a//b"
+ }
+ }
+ plan9: {
+ "": {
+ // should roundtrip
+ to: ""
+ from: ""
+ }
+ "/": {
+ // should roundtrip
+ to: "/"
+ from: "/"
+ }
+ "/a/b": {
+ // should roundtrip
+ to: "/a/b"
+ from: "/a/b"
+ }
+ "/a//b": {
+ // should roundtrip
+ to: "/a//b"
+ from: "/a//b"
+ }
+ }
+ windows: {
+ "": {
+ // should roundtrip
+ to: ""
+ from: ""
+ }
+ "/": {
+ // should roundtrip
+ to: "/"
+ from: "\\"
+ }
+ "/a/b": {
+ // should roundtrip
+ to: "/a/b"
+ from: "\\a\\b"
+ }
+ "/a//b": {
+ // should roundtrip
+ to: "/a//b"
+ from: "\\a\\\\b"
+ }
+ }
+}
+Ext: {
+ unix: {
+ // Same for all OS-es
+ "path.go": ".go"
+ "path.pb.go": ".go"
+ "a.dir/b": ""
+ "a.dir/b.go": ".go"
+ "a.dir/": ""
+
+ // Differs on Windows.
+ "a.dir\\foo": ".dir\\foo"
+ }
+ plan9: {
+ // Same for all OS-es
+ "path.go": ".go"
+ "path.pb.go": ".go"
+ "a.dir/b": ""
+ "a.dir/b.go": ".go"
+ "a.dir/": ""
+
+ // Differs on Windows.
+ "a.dir\\foo": ".dir\\foo"
+ }
+ default: {
+ // Same for all OS-es
+ "path.go": ".go"
+ "path.pb.go": ".go"
+ "a.dir/b": ""
+ "a.dir/b.go": ".go"
+ "a.dir/": ""
+
+ // Differs on Windows.
+ "a.dir\\foo": ".dir\\foo"
+ }
+ windows: {
+ // Same for all OS-es
+ "path.go": ".go"
+ "path.pb.go": ".go"
+ "a.dir/b": ""
+ "a.dir/b.go": ".go"
+ "a.dir/": ""
+
+ // Differs on Windows.
+ "a.dir\\foo": ""
+ }
+}
+Resolve: {
+ unix: {
+ "a/b/c": {
+ "d/e": "a/b/c/d/e"
+ }
+ "/a/b": {
+ "/c/d": "/c/d"
+ }
+ "c:/a": {
+ "d:\\": "c:/a/d:\\"
+ }
+ "//home/user/foo": {
+ bar: "/home/user/foo/bar"
+ "//other/abs/foo": "/other/abs/foo"
+ }
+ }
+ plan9: {
+ "a/b/c": {
+ "d/e": "a/b/c/d/e"
+ }
+ "/a/b": {
+ "/c/d": "/c/d"
+ }
+ "c:/a": {
+ "d:\\": "c:/a/d:\\"
+ }
+ "//home/user/foo": {
+ bar: "/home/user/foo/bar"
+ "//other/abs/foo": "/other/abs/foo"
+ }
+ }
+ default: {
+ "a/b/c": {
+ "d/e": "a/b/c/d/e"
+ }
+ "/a/b": {
+ "/c/d": "/c/d"
+ }
+ "c:/a": {
+ "d:\\": "c:/a/d:\\"
+ }
+ "//home/user/foo": {
+ bar: "/home/user/foo/bar"
+ "//other/abs/foo": "/other/abs/foo"
+ }
+ }
+ windows: {
+ "a/b/c": {
+ "d/e": "a\\b\\c\\d\\e"
+ }
+ "/a/b": {
+ "/c/d": "\\a\\b\\c\\d"
+ }
+ "c:/a": {
+ "d:\\": "d:\\"
+ }
+ "//home/user/foo": {
+ bar: "\\\\home\\user\\foo\\bar"
+ "//other/abs/foo": "\\\\other\\abs\\foo"
+ }
+ }
+}
+IsAbs: {
+ unix: {
+ "": false
+ "/a": true
+ a: false
+ "c:": false
+ "c:/": false
+ "c:\\": false
+ "//home/user/foo": true
+ }
+ plan9: {
+ "": false
+ "/a": true
+ a: false
+ "c:": false
+ "c:/": false
+ "c:\\": false
+ "//home/user/foo": true
+ }
+ default: {
+ "": false
+ "/a": true
+ a: false
+ "c:": false
+ "c:/": false
+ "c:\\": false
+ "//home/user/foo": true
+ }
+ windows: {
+ "": false
+ "/a": false
+ a: false
+ "c:": false
+ "c:/": true
+ "c:\\": true
+ "//home/user/foo": true
+ }
+}
+Volume: {
+ unix: {
+ "c:/foo/bar": ""
+ "c:": ""
+ "2:": ""
+ "": ""
+ "\\\\\\host": ""
+ "\\\\\\host\\": ""
+ "\\\\\\host\\share": ""
+ "\\\\\\host\\\\share": ""
+ "\\\\host": ""
+ "//host": ""
+ "\\\\host\\": ""
+ "//host/": ""
+ "\\\\host\\share": ""
+ "//host/share": ""
+ "\\\\host\\share\\": ""
+ "//host/share/": ""
+ "\\\\host\\share\\foo": ""
+ "//host/share/foo": ""
+ "\\\\host\\share\\\\foo\\\\\\bar\\\\\\\\baz": ""
+ "//host/share//foo///bar////baz": ""
+ "\\\\host\\share\\foo\\..\\bar": ""
+ "//host/share/foo/../bar": ""
+ }
+ plan9: {
+ "c:/foo/bar": ""
+ "c:": ""
+ "2:": ""
+ "": ""
+ "\\\\\\host": ""
+ "\\\\\\host\\": ""
+ "\\\\\\host\\share": ""
+ "\\\\\\host\\\\share": ""
+ "\\\\host": ""
+ "//host": ""
+ "\\\\host\\": ""
+ "//host/": ""
+ "\\\\host\\share": ""
+ "//host/share": ""
+ "\\\\host\\share\\": ""
+ "//host/share/": ""
+ "\\\\host\\share\\foo": ""
+ "//host/share/foo": ""
+ "\\\\host\\share\\\\foo\\\\\\bar\\\\\\\\baz": ""
+ "//host/share//foo///bar////baz": ""
+ "\\\\host\\share\\foo\\..\\bar": ""
+ "//host/share/foo/../bar": ""
+ }
+ windows: {
+ "c:/foo/bar": "c:"
+ "c:": "c:"
+ "2:": ""
+ "": ""
+ "\\\\\\host": ""
+ "\\\\\\host\\": ""
+ "\\\\\\host\\share": ""
+ "\\\\\\host\\\\share": ""
+ "\\\\host": ""
+ "//host": ""
+ "\\\\host\\": ""
+ "//host/": ""
+ "\\\\host\\share": "\\\\host\\share"
+ "//host/share": "//host/share"
+ "\\\\host\\share\\": "\\\\host\\share"
+ "//host/share/": "//host/share"
+ "\\\\host\\share\\foo": "\\\\host\\share"
+ "//host/share/foo": "//host/share"
+ "\\\\host\\share\\\\foo\\\\\\bar\\\\\\\\baz": "\\\\host\\share"
+ "//host/share//foo///bar////baz": "//host/share"
+ "\\\\host\\share\\foo\\..\\bar": "\\\\host\\share"
+ "//host/share/foo/../bar": "//host/share"
+ }
+}
+
diff --git a/pkg/path/testdata/path.go b/pkg/path/testdata/path.go
deleted file mode 100644
index b18883d..0000000
--- a/pkg/path/testdata/path.go
+++ /dev/null
@@ -1,414 +0,0 @@
-// Copyright 2020 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.
-
-// Copyright 2009 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 filepath implements utility routines for manipulating filename paths
-// in a way compatible with the target operating system-defined file paths.
-//
-// The filepath package uses either forward slashes or backslashes,
-// depending on the operating system. To process paths such as URLs
-// that always use forward slashes regardless of the operating
-// system, see the path package.
-package path
-
-import (
- "errors"
- "strings"
-)
-
-// A lazybuf is a lazily constructed path buffer.
-// It supports append, reading previously appended bytes,
-// and retrieving the final string. It does not allocate a buffer
-// to hold the output until that output diverges from s.
-type lazybuf struct {
- path string
- buf []byte
- w int
- volAndPath string
- volLen int
-}
-
-func (b *lazybuf) index(i int) byte {
- if b.buf != nil {
- return b.buf[i]
- }
- return b.path[i]
-}
-
-func (b *lazybuf) append(c byte) {
- if b.buf == nil {
- if b.w < len(b.path) && b.path[b.w] == c {
- b.w++
- return
- }
- b.buf = make([]byte, len(b.path))
- copy(b.buf, b.path[:b.w])
- }
- b.buf[b.w] = c
- b.w++
-}
-
-func (b *lazybuf) string() string {
- if b.buf == nil {
- return b.volAndPath[:b.volLen+b.w]
- }
- return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
-}
-
-// const (
-// Separator = os.PathSeparator
-// ListSeparator = os.PathListSeparator
-// )
-
-// Clean returns the shortest path name equivalent to path
-// by purely lexical processing. The default value for os is Unix.
-// It applies the following rules
-// iteratively until no further processing can be done:
-//
-// 1. Replace multiple Separator elements with a single one.
-// 2. Eliminate each . path name element (the current directory).
-// 3. Eliminate each inner .. path name element (the parent directory)
-// along with the non-.. element that precedes it.
-// 4. Eliminate .. elements that begin a rooted path:
-// that is, replace "/.." by "/" at the beginning of a path,
-// assuming Separator is '/'.
-//
-// The returned path ends in a slash only if it represents a root directory,
-// such as "/" on Unix or `C:\` on Windows.
-//
-// Finally, any occurrences of slash are replaced by Separator.
-//
-// If the result of this process is an empty string, Clean
-// returns the string ".".
-//
-// See also Rob Pike, ``Lexical File Names in Plan 9 or
-// Getting Dot-Dot Right,''
-// https://9p.io/sys/doc/lexnames.html
-func Clean(path string, os OS) string {
- return clean(path, getOS(os))
-}
-
-func clean(path string, os os) string {
- originalPath := path
- volLen := os.volumeNameLen(path)
- path = path[volLen:]
- if path == "" {
- if volLen > 1 && originalPath[1] != ':' {
- // should be UNC
- return fromSlash(originalPath, os)
- }
- return originalPath + "."
- }
- rooted := os.IsPathSeparator(path[0])
-
- // Invariants:
- // reading from path; r is index of next byte to process.
- // writing to buf; w is index of next byte to write.
- // dotdot is index in buf where .. must stop, either because
- // it is the leading slash or it is a leading ../../.. prefix.
- n := len(path)
- out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
- r, dotdot := 0, 0
- if rooted {
- out.append(os.Separator)
- r, dotdot = 1, 1
- }
-
- for r < n {
- switch {
- case os.IsPathSeparator(path[r]):
- // empty path element
- r++
- case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
- // . element
- r++
- case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
- // .. element: remove to last separator
- r += 2
- switch {
- case out.w > dotdot:
- // can backtrack
- out.w--
- for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
- out.w--
- }
- case !rooted:
- // cannot backtrack, but not rooted, so append .. element.
- if out.w > 0 {
- out.append(os.Separator)
- }
- out.append('.')
- out.append('.')
- dotdot = out.w
- }
- default:
- // real path element.
- // add slash if needed
- if rooted && out.w != 1 || !rooted && out.w != 0 {
- out.append(os.Separator)
- }
- // copy element
- for ; r < n && !os.IsPathSeparator(path[r]); r++ {
- out.append(path[r])
- }
- }
- }
-
- // Turn empty string into "."
- if out.w == 0 {
- out.append('.')
- }
-
- return fromSlash(out.string(), os)
-}
-
-// ToSlash returns the result of replacing each separator character
-// in path with a slash ('/') character. Multiple separators are
-// replaced by multiple slashes.
-func ToSlash(path string, os OS) string {
- return toSlash(path, getOS(os))
-}
-
-func toSlash(path string, os os) string {
- if os.Separator == '/' {
- return path
- }
- return strings.ReplaceAll(path, string(os.Separator), "/")
-}
-
-// FromSlash returns the result of replacing each slash ('/') character
-// in path with a separator character. Multiple slashes are replaced
-// by multiple separators.
-func FromSlash(path string, os OS) string {
- return fromSlash(path, getOS(os))
-}
-
-func fromSlash(path string, os os) string {
- if os.Separator == '/' {
- return path
- }
- return strings.ReplaceAll(path, "/", string(os.Separator))
-}
-
-// SplitList splits a list of paths joined by the OS-specific ListSeparator,
-// usually found in PATH or GOPATH environment variables.
-// Unlike strings.Split, SplitList returns an empty slice when passed an empty
-// string.
-func SplitList(path string, os OS) []string {
- return getOS(os).splitList(path)
-}
-
-// Split splits path immediately following the final slash and returns them as
-// the list [dir, file], separating it into a directory and file name component.
-// If there is no slash in path, Split returns an empty dir and file set to
-// path. The returned values have the property that path = dir+file.
-// The default value for os is Unix.
-func Split(path string, os OS) []string {
- x := getOS(os)
- vol := volumeName(path, x)
- i := len(path) - 1
- for i >= len(vol) && !x.IsPathSeparator(path[i]) {
- i--
- }
- return []string{path[:i+1], path[i+1:]}
-}
-
-// Join joins any number of path elements into a single path,
-// separating them with an OS specific Separator. Empty elements
-// are ignored. The result is Cleaned. However, if the argument
-// list is empty or all its elements are empty, Join returns
-// an empty string.
-// On Windows, the result will only be a UNC path if the first
-// non-empty element is a UNC path.
-// The default value for os is Unix.
-func Join(elem []string, os OS) string {
- return getOS(os).join(elem)
-}
-
-// Ext returns the file name extension used by path.
-// The extension is the suffix beginning at the final dot
-// in the final element of path; it is empty if there is
-// no dot. The default value for os is Unix.
-func Ext(path string, os OS) string {
- x := getOS(os)
- for i := len(path) - 1; i >= 0 && !x.IsPathSeparator(path[i]); i-- {
- if path[i] == '.' {
- return path[i:]
- }
- }
- return ""
-}
-
-// Resolve reports the path of sub relative to dir. If sub is an absolute path,
-// or if dir is empty, it will return sub. If sub is empty, it will return dir.
-// Resolve calls Clean on the result. The default value for os is Unix.
-func Resolve(dir, sub string, os OS) string {
- x := getOS(os)
- if x.IsAbs(sub) {
- return clean(sub, x)
- }
- dir = clean(dir, x)
- return x.join([]string{dir, sub})
-}
-
-// Rel returns a relative path that is lexically equivalent to targpath when
-// joined to basepath with an intervening separator. That is,
-// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
-// On success, the returned path will always be relative to basepath,
-// even if basepath and targpath share no elements.
-// An error is returned if targpath can't be made relative to basepath or if
-// knowing the current working directory would be necessary to compute it.
-// Rel calls Clean on the result. The default value for os is Unix.
-func Rel(basepath, targpath string, os OS) (string, error) {
- x := getOS(os)
- baseVol := volumeName(basepath, x)
- targVol := volumeName(targpath, x)
- base := clean(basepath, x)
- targ := clean(targpath, x)
- if x.sameWord(targ, base) {
- return ".", nil
- }
- base = base[len(baseVol):]
- targ = targ[len(targVol):]
- if base == "." {
- base = ""
- }
- // Can't use IsAbs - `\a` and `a` are both relative in Windows.
- baseSlashed := len(base) > 0 && base[0] == x.Separator
- targSlashed := len(targ) > 0 && targ[0] == x.Separator
- if baseSlashed != targSlashed || !x.sameWord(baseVol, targVol) {
- return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
- }
- // Position base[b0:bi] and targ[t0:ti] at the first differing elements.
- bl := len(base)
- tl := len(targ)
- var b0, bi, t0, ti int
- for {
- for bi < bl && base[bi] != x.Separator {
- bi++
- }
- for ti < tl && targ[ti] != x.Separator {
- ti++
- }
- if !x.sameWord(targ[t0:ti], base[b0:bi]) {
- break
- }
- if bi < bl {
- bi++
- }
- if ti < tl {
- ti++
- }
- b0 = bi
- t0 = ti
- }
- if base[b0:bi] == ".." {
- return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
- }
- if b0 != bl {
- // Base elements left. Must go up before going down.
- seps := strings.Count(base[b0:bl], string(x.Separator))
- size := 2 + seps*3
- if tl != t0 {
- size += 1 + tl - t0
- }
- buf := make([]byte, size)
- n := copy(buf, "..")
- for i := 0; i < seps; i++ {
- buf[n] = x.Separator
- copy(buf[n+1:], "..")
- n += 3
- }
- if t0 != tl {
- buf[n] = x.Separator
- copy(buf[n+1:], targ[t0:])
- }
- return string(buf), nil
- }
- return targ[t0:], nil
-}
-
-// Base returns the last element of path.
-// Trailing path separators are removed before extracting the last element.
-// If the path is empty, Base returns ".".
-// If the path consists entirely of separators, Base returns a single separator.
-// The default value for os is Unix.
-func Base(path string, os OS) string {
- x := getOS(os)
- if path == "" {
- return "."
- }
- // Strip trailing slashes.
- for len(path) > 0 && x.IsPathSeparator(path[len(path)-1]) {
- path = path[0 : len(path)-1]
- }
- // Throw away volume name
- path = path[x.volumeNameLen(path):]
- // Find the last element
- i := len(path) - 1
- for i >= 0 && !x.IsPathSeparator(path[i]) {
- i--
- }
- if i >= 0 {
- path = path[i+1:]
- }
- // If empty now, it had only slashes.
- if path == "" {
- return string(x.Separator)
- }
- return path
-}
-
-// Dir returns all but the last element of path, typically the path's directory.
-// After dropping the final element, Dir calls Clean on the path and trailing
-// slashes are removed.
-// If the path is empty, Dir returns ".".
-// If the path consists entirely of separators, Dir returns a single separator.
-// The returned path does not end in a separator unless it is the root directory.
-// The default value for os is Unix.
-func Dir(path string, os OS) string {
- x := getOS(os)
- vol := volumeName(path, x)
- i := len(path) - 1
- for i >= len(vol) && !x.IsPathSeparator(path[i]) {
- i--
- }
- dir := clean(path[len(vol):i+1], x)
- if dir == "." && len(vol) > 2 {
- // must be UNC
- return vol
- }
- return vol + dir
-}
-
-// IsAbs reports whether the path is absolute. The default value for os is Unix.
-func IsAbs(path string, os OS) bool {
- return getOS(os).IsAbs(path)
-}
-
-// VolumeName returns leading volume name.
-// Given "C:\foo\bar" it returns "C:" on Windows.
-// Given "\\host\share\foo" it returns "\\host\share".
-// On other platforms it returns "".
-// The default value for os is Windows.
-func VolumeName(path string, os OS) string {
- return volumeName(path, getOS(os))
-}
-
-func volumeName(path string, os os) string {
- return path[:os.volumeNameLen(path)]
-}