-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #445 from wking/explicit-os-filepath
filepath: Add a stand-alone package for explicit-OS path logic
- Loading branch information
Showing
11 changed files
with
534 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package filepath | ||
|
||
import ( | ||
"errors" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
var windowsAbs = regexp.MustCompile(`^[a-zA-Z]:\\.*$`) | ||
|
||
// Abs is a version of path/filepath's Abs with an explicit operating | ||
// system and current working directory. | ||
func Abs(os, path, cwd string) (_ string, err error) { | ||
if os == "windows" { | ||
return "", errors.New("Abs() does not support windows yet") | ||
} | ||
if IsAbs(os, path) { | ||
return Clean(os, path), nil | ||
} | ||
return Clean(os, Join(os, cwd, path)), nil | ||
} | ||
|
||
// IsAbs is a version of path/filepath's IsAbs with an explicit | ||
// operating system. | ||
func IsAbs(os, path string) bool { | ||
if os == "windows" { | ||
// FIXME: copy hideous logic from Go's | ||
// src/path/filepath/path_windows.go into somewhere where we can | ||
// put 3-clause BSD licensed code. | ||
return windowsAbs.MatchString(path) | ||
} | ||
sep := Separator(os) | ||
|
||
// POSIX has [1]: | ||
// | ||
// > If a pathname begins with two successive <slash> characters, | ||
// > the first component following the leading <slash> characters | ||
// > may be interpreted in an implementation-defined manner, | ||
// > although more than two leading <slash> characters shall be | ||
// > treated as a single <slash> character. | ||
// | ||
// And Boost treats // as non-absolute [2], but Linux [3,4], Python | ||
// [5] and Go [6] all treat // as absolute. | ||
// | ||
// [1]: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13 | ||
// [2]: https://github.com/boostorg/filesystem/blob/boost-1.64.0/test/path_test.cpp#L861 | ||
// [3]: http://man7.org/linux/man-pages/man7/path_resolution.7.html | ||
// [4]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/path-lookup.md?h=v4.12#n41 | ||
// [5]: https://github.com/python/cpython/blob/v3.6.1/Lib/posixpath.py#L64-L66 | ||
// [6]: https://go.googlesource.com/go/+/go1.8.3/src/path/path.go#199 | ||
return strings.HasPrefix(path, string(sep)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package filepath | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestAbs(t *testing.T) { | ||
for _, test := range []struct { | ||
os string | ||
path string | ||
cwd string | ||
expected string | ||
}{ | ||
{ | ||
os: "linux", | ||
path: "/", | ||
cwd: "/cwd", | ||
expected: "/", | ||
}, | ||
{ | ||
os: "linux", | ||
path: "/a", | ||
cwd: "/cwd", | ||
expected: "/a", | ||
}, | ||
{ | ||
os: "linux", | ||
path: "/a/", | ||
cwd: "/cwd", | ||
expected: "/a", | ||
}, | ||
{ | ||
os: "linux", | ||
path: "//a", | ||
cwd: "/cwd", | ||
expected: "/a", | ||
}, | ||
{ | ||
os: "linux", | ||
path: ".", | ||
cwd: "/cwd", | ||
expected: "/cwd", | ||
}, | ||
{ | ||
os: "linux", | ||
path: "./c", | ||
cwd: "/a/b", | ||
expected: "/a/b/c", | ||
}, | ||
{ | ||
os: "linux", | ||
path: ".//c", | ||
cwd: "/a/b", | ||
expected: "/a/b/c", | ||
}, | ||
{ | ||
os: "linux", | ||
path: "../a", | ||
cwd: "/cwd", | ||
expected: "/a", | ||
}, | ||
{ | ||
os: "linux", | ||
path: "../../b", | ||
cwd: "/cwd", | ||
expected: "/b", | ||
}, | ||
} { | ||
t.Run( | ||
fmt.Sprintf("Abs(%q,%q,%q)", test.os, test.path, test.cwd), | ||
func(t *testing.T) { | ||
abs, err := Abs(test.os, test.path, test.cwd) | ||
if err != nil { | ||
t.Error(err) | ||
} else if abs != test.expected { | ||
t.Errorf("unexpected result: %q (expected %q)\n", abs, test.expected) | ||
} | ||
}, | ||
) | ||
} | ||
} | ||
|
||
func TestIsAbs(t *testing.T) { | ||
for _, test := range []struct { | ||
os string | ||
path string | ||
expected bool | ||
}{ | ||
{ | ||
os: "linux", | ||
path: "/", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
path: "/a", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
path: "//", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
path: "//a", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
path: ".", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
path: "./a", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
path: ".//a", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
path: "../a", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
path: "../../a", | ||
expected: false, | ||
}, | ||
{ | ||
os: "windows", | ||
path: "c:\\", | ||
expected: true, | ||
}, | ||
{ | ||
os: "windows", | ||
path: "c:\\a", | ||
expected: true, | ||
}, | ||
{ | ||
os: "windows", | ||
path: ".", | ||
expected: false, | ||
}, | ||
{ | ||
os: "windows", | ||
path: ".\\a", | ||
expected: false, | ||
}, | ||
{ | ||
os: "windows", | ||
path: "..\\a", | ||
expected: false, | ||
}, | ||
} { | ||
t.Run( | ||
fmt.Sprintf("IsAbs(%q,%q)", test.os, test.path), | ||
func(t *testing.T) { | ||
abs := IsAbs(test.os, test.path) | ||
if abs != test.expected { | ||
t.Errorf("unexpected result: %t (expected %t)\n", abs, test.expected) | ||
} | ||
}, | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package filepath | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// IsAncestor returns true when pathB is an strict ancestor of pathA, | ||
// and false where the paths are equal or pathB is outside of pathA. | ||
// Paths that are not absolute will be made absolute with Abs. | ||
func IsAncestor(os, pathA, pathB, cwd string) (_ bool, err error) { | ||
if pathA == pathB { | ||
return false, nil | ||
} | ||
|
||
pathA, err = Abs(os, pathA, cwd) | ||
if err != nil { | ||
return false, err | ||
} | ||
pathB, err = Abs(os, pathB, cwd) | ||
if err != nil { | ||
return false, err | ||
} | ||
sep := Separator(os) | ||
if !strings.HasSuffix(pathA, string(sep)) { | ||
pathA = fmt.Sprintf("%s%c", pathA, sep) | ||
} | ||
if pathA == pathB { | ||
return false, nil | ||
} | ||
return strings.HasPrefix(pathB, pathA), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package filepath | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestIsAncestor(t *testing.T) { | ||
for _, test := range []struct { | ||
os string | ||
pathA string | ||
pathB string | ||
cwd string | ||
expected bool | ||
}{ | ||
{ | ||
os: "linux", | ||
pathA: "/", | ||
pathB: "/a", | ||
cwd: "/cwd", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "/a", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "/", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "/ab", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a/", | ||
pathB: "/a", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "//a", | ||
pathB: "/a", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "//a", | ||
pathB: "/a/b", | ||
cwd: "/cwd", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "/a/", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: ".", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "b", | ||
cwd: "/a", | ||
expected: true, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "../a", | ||
cwd: "/cwd", | ||
expected: false, | ||
}, | ||
{ | ||
os: "linux", | ||
pathA: "/a", | ||
pathB: "../a/b", | ||
cwd: "/cwd", | ||
expected: true, | ||
}, | ||
} { | ||
t.Run( | ||
fmt.Sprintf("IsAncestor(%q,%q,%q,%q)", test.os, test.pathA, test.pathB, test.cwd), | ||
func(t *testing.T) { | ||
ancestor, err := IsAncestor(test.os, test.pathA, test.pathB, test.cwd) | ||
if err != nil { | ||
t.Error(err) | ||
} else if ancestor != test.expected { | ||
t.Errorf("unexpected result: %t", ancestor) | ||
} | ||
}, | ||
) | ||
} | ||
} |
Oops, something went wrong.