diff --git a/path.go b/path.go index daffe8c..4dd7077 100644 --- a/path.go +++ b/path.go @@ -1,6 +1,7 @@ package pathlib import ( + "io" "os" "path/filepath" "strings" @@ -12,8 +13,12 @@ import ( // Path is an object that represents a path type Path struct { - path string - afero afero.Afero + path string + fs afero.Fs + + // DefaultFileMode is the mode that is used when creating new files in functions + // that do not accept os.FileMode as a parameter. + DefaultFileMode os.FileMode } // NewPath returns a new OS path @@ -24,8 +29,9 @@ func NewPath(path string) *Path { // NewPathAfero returns a Path object with the given Afero object func NewPathAfero(path string, fs afero.Fs) *Path { return &Path{ - path: path, - afero: afero.Afero{Fs: fs}, + path: path, + fs: fs, + DefaultFileMode: 0o644, } } @@ -55,203 +61,291 @@ func getFsName(fs afero.Fs) string { return "" } -// Afero returns the internal afero object. -func (p *Path) Afero() afero.Afero { - return p.afero +// Fs returns the internal afero.Fs object. +func (p *Path) Fs() afero.Fs { + return p.fs } func (p *Path) doesNotImplementErr(interfaceName string) error { - return errors.Wrapf(ErrDoesNotImplement, "Path's afero filesystem %s does not implement %s", getFsName(p.afero.Fs), interfaceName) + return errors.Wrapf(ErrDoesNotImplement, "Path's afero filesystem %s does not implement %s", getFsName(p.fs), interfaceName) } -// Resolve resolves the path to a real path -func (p *Path) Resolve() (*Path, error) { - linkReader, ok := p.afero.Fs.(afero.LinkReader) - if !ok { - return nil, p.doesNotImplementErr("afero.LinkReader") - } +// ******************************* +// * afero.Fs wrappers * +// ******************************* - resolvedPathStr, err := linkReader.ReadlinkIfPossible(p.path) - if err != nil { - return nil, errors.Wrapf(err, "failed to readlink") - } - return NewPathAfero(resolvedPathStr, p.afero.Fs), nil +// Create creates a file if possible, returning the file and an error, if any happens. +func (p *Path) Create() (afero.File, error) { + return p.Fs().Create(p.Path()) } -// Symlink symlinks to the target location -func (p *Path) Symlink(target *Path) error { - symlinker, ok := p.afero.Fs.(afero.Linker) - if !ok { - return p.doesNotImplementErr("afero.Linker") - } +// Mkdir makes the current dir. If the parents don't exist, an error +// is returned. +func (p *Path) Mkdir(perm os.FileMode) error { + return p.Fs().Mkdir(p.Path(), perm) +} - return errors.Wrapf(symlinker.SymlinkIfPossible(target.path, p.path), "failed to symlink %s to %s", p.path, target.path) +// MkdirAll makes all of the directories up to, and including, the given path. +func (p *Path) MkdirAll(perm os.FileMode) error { + return p.Fs().MkdirAll(p.Path(), perm) } -// IsAbsolute returns whether or not the path is an absolute path. This is -// determined by checking if the path starts with a slash. -func (p *Path) IsAbsolute() bool { - return strings.HasPrefix(p.path, "/") +// Open opens a file, returning it or an error, if any happens. +func (p *Path) Open() (afero.File, error) { + return p.Fs().Open(p.Path()) } -// Name returns the string representing the final path component -func (p *Path) Name() string { - return filepath.Base(p.path) +// OpenFile opens a file using the given flags and the given mode. +func (p *Path) OpenFile(flag int, perm os.FileMode) (afero.File, error) { + return p.Fs().OpenFile(p.Path(), flag, perm) } -// Join joins the current object's path with the given elements and returns -// the resulting Path object. -func (p *Path) Join(elems ...string) *Path { - paths := []string{p.path} - for _, path := range elems { - paths = append(paths, path) +// Remove removes a file, returning an error, if any +// happens. +func (p *Path) Remove() error { + return p.Fs().Remove(p.Path()) +} + +// RemoveAll removes the given path and all of its children. +func (p *Path) RemoveAll() error { + return p.Fs().RemoveAll(p.Path()) +} + +// Rename renames a file +func (p *Path) Rename(newname string) error { + if err := p.Fs().Rename(p.Path(), newname); err != nil { + return err } - return NewPathAfero(filepath.Join(paths...), p.afero.Fs) + + // Rename succeeded. Set our path to the newname. + p.path = newname + return nil } -// WriteFile writes the given data to the path (if possible). If the file exists, -// the file is truncated. If the file is a directory, or the path doesn't exist, -// an error is returned. -func (p *Path) WriteFile(data []byte, perm os.FileMode) error { - return errors.Wrapf(p.afero.WriteFile(p.Path(), data, perm), "Failed to write file") +// RenamePath is the same as Rename except the argument is a Path object. The attributes +// of the path object is retained and does not inherit anything from target. +func (p *Path) RenamePath(target *Path) error { + return p.Rename(target.Path()) } -// ReadFile reads the given path and returns the data. If the file doesn't exist -// or is a directory, an error is returned. -func (p *Path) ReadFile() ([]byte, error) { - bytes, err := p.afero.ReadFile(p.Path()) - return bytes, errors.Wrapf(err, "failed to read file") +// Stat returns the os.FileInfo of the given path +func (p *Path) Stat() (os.FileInfo, error) { + return p.Fs().Stat(p.Path()) +} + +// Chmod changes the file mode of the given path +func (p *Path) Chmod(mode os.FileMode) error { + return p.Fs().Chmod(p.Path(), mode) +} + +// Chtimes changes the modification and access time of the given path. +func (p *Path) Chtimes(atime time.Time, mtime time.Time) error { + return p.Fs().Chtimes(p.Path(), atime, mtime) +} + +// ************************ +// * afero.Afero wrappers * +// ************************ + +// DirExists returns whether or not the path represents a directory that exists +func (p *Path) DirExists() (bool, error) { + return afero.DirExists(p.Fs(), p.Path()) +} + +// Exists returns whether the path exists +func (p *Path) Exists() (bool, error) { + return afero.Exists(p.Fs(), p.Path()) +} + +// FileContainsAnyBytes returns whether or not the path contains +// any of the listed bytes. +func (p *Path) FileContainsAnyBytes(subslices [][]byte) (bool, error) { + return afero.FileContainsAnyBytes(p.Fs(), p.Path(), subslices) +} + +// FileContainsBytes returns whether or not the given file contains the bytes +func (p *Path) FileContainsBytes(subslice []byte) (bool, error) { + return afero.FileContainsBytes(p.Fs(), p.Path(), subslice) +} + +// IsDir checks if a given path is a directory. +func (p *Path) IsDir() (bool, error) { + return afero.IsDir(p.Fs(), p.Path()) +} + +// IsEmpty checks if a given file or directory is empty. +func (p *Path) IsEmpty() (bool, error) { + return afero.IsEmpty(p.Fs(), p.Path()) } // ReadDir reads the current path and returns a list of the corresponding // Path objects. func (p *Path) ReadDir() ([]*Path, error) { var paths []*Path - fileInfos, err := p.afero.ReadDir(p.Path()) + fileInfos, err := afero.ReadDir(p.Fs(), p.Path()) for _, fileInfo := range fileInfos { paths = append(paths, p.Join(fileInfo.Name())) } - return paths, errors.Wrapf(err, "failed to read directory") + return paths, err } -// chmoder should really be part of afero. TODO: Send a PR to upstream -type chmoder interface { - Chmod(name string, mode os.FileMode) error +// ReadFile reads the given path and returns the data. If the file doesn't exist +// or is a directory, an error is returned. +func (p *Path) ReadFile() ([]byte, error) { + return afero.ReadFile(p.Fs(), p.Path()) } -// Chmod changes the file mode of the given path -func (p *Path) Chmod(mode os.FileMode) error { - chmodCaller, ok := p.afero.Fs.(chmoder) - if !ok { - return p.doesNotImplementErr("Chmod") - } - - return errors.Wrapf(chmodCaller.Chmod(p.path, mode), "Failed to chmod") +// SafeWriteReader is the same as WriteReader but checks to see if file/directory already exists. +func (p *Path) SafeWriteReader(r io.Reader) error { + return afero.SafeWriteReader(p.Fs(), p.Path(), r) } -type mkdir interface { - Mkdir(name string, perm os.FileMode) error +// Walk walks path, using the given filepath.WalkFunc to handle each +func (p *Path) Walk(walkFn filepath.WalkFunc) error { + return afero.Walk(p.Fs(), p.Path(), walkFn) } -// Mkdir makes the current dir. If the parents don't exist, an error -// is returned. -func (p *Path) Mkdir(perm os.FileMode) error { - mkdirCaller, ok := p.afero.Fs.(mkdir) - if !ok { - return p.doesNotImplementErr("Mkdir") - } - return errors.Wrapf(mkdirCaller.Mkdir(p.path, perm), "failed to Mkdir") +// WriteFile writes the given data to the path (if possible). If the file exists, +// the file is truncated. If the file is a directory, or the path doesn't exist, +// an error is returned. +func (p *Path) WriteFile(data []byte, perm os.FileMode) error { + return afero.WriteFile(p.Fs(), p.Path(), data, perm) } -type mkdirAll interface { - MkdirAll(name string, perm os.FileMode) error +// WriteReader takes a reader and writes the content +func (p *Path) WriteReader(r io.Reader) error { + return afero.WriteReader(p.Fs(), p.Path(), r) } -// MkdirAll makes all of the directories up to, and including, the given path. -func (p *Path) MkdirAll(perm os.FileMode) error { - mkdirCaller, ok := p.afero.Fs.(mkdirAll) - if !ok { - return p.doesNotImplementErr("MkdirAll") - } - return errors.Wrapf(mkdirCaller.MkdirAll(p.path, perm), "failed to Mkdir") +// ************************************* +// * pathlib.Path-like implementations * +// ************************************* + +// Name returns the string representing the final path component +func (p *Path) Name() string { + return filepath.Base(p.path) } -type rename interface { - Rename(oldname, newname string) error +// Parent returns the Path object of the parent directory +func (p *Path) Parent() *Path { + return NewPathAfero(filepath.Dir(p.Path()), p.Fs()) } -// Rename this path to the given target and return the corresponding -// Path object. -func (p *Path) Rename(target string) (*Path, error) { - renameCaller, ok := p.afero.Fs.(rename) +// Resolve resolves the path to a real path +func (p *Path) Resolve() (*Path, error) { + linkReader, ok := p.Fs().(afero.LinkReader) if !ok { - return nil, p.doesNotImplementErr("Rename") + return nil, p.doesNotImplementErr("afero.LinkReader") } - err := errors.Wrapf(renameCaller.Rename(p.path, target), "failed to rename") + resolvedPathStr, err := linkReader.ReadlinkIfPossible(p.path) if err != nil { return nil, err } - return NewPathAfero(target, p.afero.Fs), nil + return NewPathAfero(resolvedPathStr, p.fs), nil } -// RenamePath is the same as Rename except target is a Path object -func (p *Path) RenamePath(target *Path) (*Path, error) { - return p.Rename(target.path) +// IsAbsolute returns whether or not the path is an absolute path. This is +// determined by checking if the path starts with a slash. +func (p *Path) IsAbsolute() bool { + return strings.HasPrefix(p.path, "/") } -type remover interface { - Remove(name string) error +// Join joins the current object's path with the given elements and returns +// the resulting Path object. +func (p *Path) Join(elems ...string) *Path { + paths := []string{p.path} + for _, path := range elems { + paths = append(paths, path) + } + return NewPathAfero(filepath.Join(paths...), p.fs) } -// Remove deletes/unlinks/destroys the given path -func (p *Path) Remove() error { - removeCaller, ok := p.afero.Fs.(remover) - if !ok { - return p.doesNotImplementErr("Remove") +func normalizePathString(path string) string { + path = strings.TrimSpace(path) + path = strings.TrimPrefix(path, "./") + path = strings.TrimRight(path, " ") + if len(path) > 1 { + path = strings.TrimSuffix(path, "/") } - - return errors.Wrapf(removeCaller.Remove(p.path), "failed to remove") + return path +} + +func normalizePathParts(path []string) []string { + // We might encounter cases where path represents a split of the path + // "///" etc. We will get a bunch of erroneous empty strings in such a split, + // so remove all of the trailing empty strings except for the first one (if any) + for i := len(path) - 1; i > 0; i-- { + if path[i] == "" { + path = path[0:i] + } else { + break + } + } + return path } -type removeAll interface { - RemoveAll(name string) error -} +// RelativeTo computes a relative version of path to the other path. For instance, +// if the object is /path/to/foo.txt and you provide /path/ as the argment, the +// returned Path object will represent to/foo.txt. +func (p *Path) RelativeTo(other *Path) (*Path, error) { + thisPath := normalizePathString(p.Path()) + otherPath := normalizePathString(other.Path()) -// RemoveAll removes the given path and all of its children. -func (p *Path) RemoveAll() error { - removeAllCaller, ok := p.afero.Fs.(removeAll) - if !ok { - return p.doesNotImplementErr("RemoveAll") + thisParts := normalizePathParts(strings.Split(thisPath, string(filepath.Separator))) + otherParts := normalizePathParts(strings.Split(otherPath, string(filepath.Separator))) + + relativePath := []string{} + var relativeBase int + for idx, part := range otherParts { + if thisParts[idx] != part { + return p, errors.Errorf("%s does not start with %s", p.path, otherPath) + } + relativeBase = idx } - return errors.Wrapf(removeAllCaller.RemoveAll(p.path), "failed to remove all") -} + relativePath = thisParts[relativeBase+1:] -// Exists returns whether the path exists -func (p *Path) Exists() (bool, error) { - return p.afero.Exists(p.path) + if len(relativePath) == 0 || (len(relativePath) == 1 && relativePath[0] == "") { + relativePath = []string{"."} + } + + return NewPathAfero(strings.Join(relativePath, "/"), p.Fs()), nil } -// IsDir returns whether the path is a directory -func (p *Path) IsDir() (bool, error) { - return p.afero.IsDir(p.path) +// ********************************* +// * filesystem-specific functions * +// ********************************* + +// Symlink symlinks to the target location. This will fail if the underlying +// afero filesystem does not implement afero.Linker. +func (p *Path) Symlink(target *Path) error { + symlinker, ok := p.fs.(afero.Linker) + if !ok { + return p.doesNotImplementErr("afero.Linker") + } + + return symlinker.SymlinkIfPossible(target.path, p.path) } +// **************************************** +// * chigopher/pathlib-specific functions * +// **************************************** + // IsFile returns true if the given path is a file. func (p *Path) IsFile() (bool, error) { - fileInfo, err := p.afero.Stat(p.path) + fileInfo, err := p.Fs().Stat(p.Path()) if err != nil { - return false, errors.Wrapf(err, "failed to stat") + return false, err } return fileInfo.Mode().IsRegular(), nil } // IsSymlink returns true if the given path is a symlink. func (p *Path) IsSymlink() (bool, error) { - fileInfo, err := p.afero.Stat(p.path) + fileInfo, err := p.Fs().Stat(p.Path()) if err != nil { - return false, errors.Wrapf(err, "failed to stat") + return false, err } isSymlink := false @@ -261,16 +355,6 @@ func (p *Path) IsSymlink() (bool, error) { return isSymlink, nil } -// Stat returns the os.FileInfo of the given path -func (p *Path) Stat() (os.FileInfo, error) { - return p.afero.Stat(p.path) -} - -// Parent returns the Path object of the parent directory -func (p *Path) Parent() *Path { - return NewPathAfero(filepath.Dir(p.path), p.afero.Fs) -} - // Path returns the string representation of the path func (p *Path) Path() string { return p.path @@ -291,50 +375,6 @@ func (p *Path) Equals(other *Path) (bool, error) { return selfResolved.Path() == otherResolved.Path(), nil } -// RelativeTo computes a relative version of path to the other path. For instance, -// if the object is /path/to/foo.txt and you provide /path/ as the argment, the -// returned Path object will represent to/foo.txt. -func (p *Path) RelativeTo(other *Path) (*Path, error) { - thisParts := strings.Split(p.path, "/") - // Normalize - if thisParts[len(thisParts)-1] == "" { - thisParts = thisParts[:len(thisParts)-1] - } - if thisParts[0] == "." { - thisParts = thisParts[1:] - } - - otherParts := strings.Split(other.path, "/") - // Normalize - if len(otherParts) > 1 && otherParts[len(otherParts)-1] == "" { - otherParts = otherParts[:len(otherParts)-1] - } - if otherParts[0] == "." { - otherParts = otherParts[1:] - } - - if !strings.HasPrefix(p.path, other.path) { - errors.Errorf("%s does not start with %s", p.path, other.path) - } - - relativePath := []string{} - var relativeBase int - for idx, part := range otherParts { - if thisParts[idx] != part { - return nil, errors.Errorf("%s does not start with %s", p.path, strings.Join(otherParts[:idx], "/")) - } - relativeBase = idx - } - - relativePath = thisParts[relativeBase+1:] - - if len(relativePath) == 0 || (len(relativePath) == 1 && relativePath[0] == "") { - relativePath = []string{"."} - } - - return NewPathAfero(strings.Join(relativePath, "/"), p.afero.Fs), nil -} - // GetLatest returns the file or directory that has the most recent mtime. Only // works if this path is a directory and it exists. If the directory is empty, // the returned Path object will be nil. @@ -372,12 +412,7 @@ func (p *Path) GetLatest() (*Path, error) { // Glob returns all matches of pattern relative to this object's path. func (p *Path) Glob(pattern string) ([]*Path, error) { - return Glob(p.afero.Fs, p.Join(pattern).Path()) -} - -// Chtimes changes the modification and access time of the given path. -func (p *Path) Chtimes(atime time.Time, mtime time.Time) error { - return p.afero.Chtimes(p.Path(), atime, mtime) + return Glob(p.Fs(), p.Join(pattern).Path()) } // Mtime returns the modification time of the given path. diff --git a/path_test.go b/path_test.go index e903a26..7e072ea 100644 --- a/path_test.go +++ b/path_test.go @@ -41,7 +41,7 @@ func (p *PathSuite) TestSymlink() { func (p *PathSuite) TestSymlinkBadFs() { symlink := p.tmpdir.Join("symlink") - symlink.afero.Fs = afero.NewMemMapFs() + symlink.fs = afero.NewMemMapFs() assert.Error(p.T(), symlink.Symlink(p.tmpdir)) } @@ -112,15 +112,19 @@ func (p *PathSuite) TestRenamePath() { newPath := p.tmpdir.Join("file2.txt") - newFile, err := file.RenamePath(newPath) + err := file.RenamePath(newPath) assert.NoError(p.T(), err) - assert.Equal(p.T(), newFile.Name(), "file2.txt") + assert.Equal(p.T(), file.Path(), p.tmpdir.Join("file2.txt").Path()) - newBytes, err := newFile.ReadFile() + newBytes, err := file.ReadFile() require.NoError(p.T(), err) assert.Equal(p.T(), []byte("hello world!"), newBytes) - oldFileExists, err := file.Exists() + newFileExists, err := file.Exists() + require.NoError(p.T(), err) + assert.True(p.T(), newFileExists) + + oldFileExists, err := p.tmpdir.Join("file.txt").Exists() require.NoError(p.T(), err) assert.False(p.T(), oldFileExists) } @@ -150,55 +154,52 @@ func TestPathSuite(t *testing.T) { suite.Run(t, new(PathSuite)) } -func TestPath_Join(t *testing.T) { +func TestPath_IsAbsolute(t *testing.T) { type fields struct { path string } - type args struct { - elems []string - } tests := []struct { name string fields fields - args args - want *Path + want bool }{ - {"join absolute root", fields{"/"}, args{[]string{"foo", "bar"}}, &Path{"/foo/bar", afero.Afero{}}}, - {"join relative root", fields{"./"}, args{[]string{"foo", "bar"}}, &Path{"foo/bar", afero.Afero{}}}, - {"join with existing path", fields{"./foo"}, args{[]string{"bar", "baz"}}, &Path{"foo/bar/baz", afero.Afero{}}}, + {"root path", fields{"/"}, true}, + {"absolute path", fields{"./"}, false}, + {"absolute path", fields{"."}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Path{ path: tt.fields.path, } - if got := p.Join(tt.args.elems...); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Path.Join() = %v, want %v", got, tt.want) + if got := p.IsAbsolute(); got != tt.want { + t.Errorf("Path.IsAbsolute() = %v, want %v", got, tt.want) } }) } } -func TestPath_IsAbsolute(t *testing.T) { - type fields struct { - path string +func TestPath_Join(t *testing.T) { + type args struct { + elems []string } tests := []struct { name string - fields fields - want bool + fields string + args args + want string }{ - {"root path", fields{"/"}, true}, - {"absolute path", fields{"./"}, false}, - {"absolute path", fields{"."}, false}, + {"join absolute root", "/", args{[]string{"foo", "bar"}}, "/foo/bar"}, + {"join relative root", "./", args{[]string{"foo", "bar"}}, "foo/bar"}, + {"join with existing path", "./foo", args{[]string{"bar", "baz"}}, "foo/bar/baz"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := &Path{ - path: tt.fields.path, - } - if got := p.IsAbsolute(); got != tt.want { - t.Errorf("Path.IsAbsolute() = %v, want %v", got, tt.want) + a := afero.NewMemMapFs() + p := NewPathAfero(tt.fields, a) + want := NewPathAfero(tt.want, a) + if got := p.Join(tt.args.elems...); !reflect.DeepEqual(got, want) { + t.Errorf("Path.Join() = %v, want %v", got, want) } }) } @@ -206,72 +207,69 @@ func TestPath_IsAbsolute(t *testing.T) { func TestPath_Parent(t *testing.T) { type fields struct { - path string - afero afero.Afero + path string + fs afero.Fs + DefaultFileMode os.FileMode } tests := []struct { name string - fields fields - want *Path + fields string + want string }{ - {"absolute path", fields{path: "/path/to/foo.txt"}, &Path{"/path/to", afero.Afero{}}}, - {"relative path", fields{path: "foo.txt"}, &Path{".", afero.Afero{}}}, - {"root of relative", fields{path: "."}, &Path{".", afero.Afero{}}}, - {"root of relative with slash", fields{path: "./"}, &Path{".", afero.Afero{}}}, - {"absolute root", fields{path: "/"}, &Path{"/", afero.Afero{}}}, + {"absolute path", "/path/to/foo.txt", "/path/to"}, + {"relative path", "foo.txt", "."}, + {"root of relative", ".", "."}, + {"root of relative with slash", "./", "."}, + {"absolute root", "/", "/"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := &Path{ - path: tt.fields.path, - afero: tt.fields.afero, - } - if got := p.Parent(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Path.Parent() = %v, want %v", got, tt.want) + a := afero.NewMemMapFs() + p := NewPathAfero(tt.fields, a) + want := NewPathAfero(tt.want, a) + if got := p.Parent(); !reflect.DeepEqual(got, want) { + t.Errorf("Path.Parent() = %v, want %v", got, want) } }) } } -func TestPath_RelativeTo(t *testing.T) { +func TestPathPosix_RelativeTo(t *testing.T) { a := afero.NewMemMapFs() type fields struct { - path string - afero afero.Afero - } - type args struct { - other *Path + path string + fs afero.Fs + DefaultFileMode os.FileMode } tests := []struct { - name string - fields fields - args args - want *Path - wantErr bool + name string + fieldPath string + args string + want string + wantErr bool }{ - {"1", fields{"/etc/passwd", afero.Afero{a}}, args{NewPathAfero("/", a)}, NewPathAfero("etc/passwd", a), false}, - {"2", fields{"/etc/passwd", afero.Afero{a}}, args{NewPathAfero("/etc", a)}, NewPathAfero("passwd", a), false}, - {"3", fields{"/etc/passwd/", afero.Afero{a}}, args{NewPathAfero("/etc", a)}, NewPathAfero("passwd", a), false}, - {"4", fields{"/etc/passwd", afero.Afero{a}}, args{NewPathAfero("/etc/", a)}, NewPathAfero("passwd", a), false}, - {"5", fields{"/etc/passwd/", afero.Afero{a}}, args{NewPathAfero("/etc/", a)}, NewPathAfero("passwd", a), false}, - {"6", fields{"/etc/passwd/", afero.Afero{a}}, args{NewPathAfero("/usr/", a)}, nil, true}, - {"7", fields{"/", afero.Afero{a}}, args{NewPathAfero("/", a)}, NewPathAfero(".", a), false}, - {"8", fields{"./foo/bar", afero.Afero{a}}, args{NewPathAfero("foo", a)}, NewPathAfero("bar", a), false}, - {"9", fields{"/a/b/c/d/file.txt", afero.Afero{a}}, args{NewPathAfero("/a/b/c/d/", a)}, NewPathAfero("file.txt", a), false}, - {"10", fields{"/cool/cats/write/cool/code/file.csv", afero.Afero{a}}, args{NewPathAfero("/cool/cats/write", a)}, NewPathAfero("cool/code/file.csv", a), false}, + {"0", "/etc/passwd", "/", "etc/passwd", false}, + {"1", "/etc/passwd", "/etc", "passwd", false}, + {"2", "/etc/passwd/", "/etc", "passwd", false}, + {"3", "/etc/passwd", "/etc/", "passwd", false}, + {"4", "/etc/passwd/", "/etc/", "passwd", false}, + {"5", "/etc/passwd/", "/usr/", "/etc/passwd/", true}, + {"6", "/", "/", ".", false}, + {"7", "./foo/bar", "foo", "bar", false}, + {"8", "/a/b/c/d/file.txt", "/a/b/c/d/", "file.txt", false}, + {"9", "/cool/cats/write/cool/code/file.csv", "/cool/cats/write", "cool/code/file.csv", false}, + {"10", "/etc/passwd", "////////////", "etc/passwd", false}, + {"10", "/etc/passwd/////", "/", "etc/passwd", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := &Path{ - path: tt.fields.path, - afero: tt.fields.afero, - } - got, err := p.RelativeTo(tt.args.other) + p := NewPathAfero(tt.fieldPath, a) + got, err := p.RelativeTo(NewPathAfero(tt.args, a)) if (err != nil) != tt.wantErr { t.Errorf("Path.RelativeTo() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { + if !reflect.DeepEqual(got, NewPathAfero(tt.want, a)) { t.Errorf("Path.RelativeTo() = %v, want %v", got, tt.want) } })