Skip to content

Commit

Permalink
Merge pull request #445 from wking/explicit-os-filepath
Browse files Browse the repository at this point in the history
filepath: Add a stand-alone package for explicit-OS path logic
  • Loading branch information
liangchenye authored Sep 30, 2017
2 parents 02b49ac + cf64923 commit 2302d4c
Show file tree
Hide file tree
Showing 11 changed files with 534 additions and 84 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ test: .gofmt .govet .golint .gotest
.golint:
golint -set_exit_status $(PACKAGES)

UTDIRS = ./validate/...
UTDIRS = ./filepath/... ./validate/...
.gotest:
go test $(UTDIRS)
52 changes: 52 additions & 0 deletions filepath/abs.go
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))
}
171 changes: 171 additions & 0 deletions filepath/abs_test.go
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)
}
},
)
}
}
32 changes: 32 additions & 0 deletions filepath/ancestor.go
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
}
113 changes: 113 additions & 0 deletions filepath/ancestor_test.go
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)
}
},
)
}
}
Loading

0 comments on commit 2302d4c

Please sign in to comment.