From bd96075e15cfd6fc4afea7af742f50f9077236c9 Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Thu, 16 Jul 2020 21:52:39 -0500 Subject: [PATCH 1/5] Breaking out stat into separate functions We break out the IsSymlink/IsFile calls into standalone functions so that users may be able to reuse calls to Stat/Lstat. --- path.go | 59 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/path.go b/path.go index e9cd1db..3b108d4 100644 --- a/path.go +++ b/path.go @@ -71,7 +71,11 @@ func (p *Path) Fs() afero.Fs { } func (p *Path) doesNotImplementErr(interfaceName string) error { - return errors.Wrapf(ErrDoesNotImplement, "Path's afero filesystem %s does not implement %s", getFsName(p.fs), interfaceName) + return doesNotImplementErr(interfaceName, p.Fs()) +} + +func doesNotImplementErr(interfaceName string, fs afero.Fs) error { + return errors.Wrapf(ErrDoesNotImplement, "Path's afero filesystem %s does not implement %s", getFsName(fs), interfaceName) } // ******************************* @@ -396,6 +400,24 @@ func (p *Path) RelativeTo(other *Path) (*Path, error) { return NewPathAfero(strings.Join(relativePath, "/"), p.Fs()), nil } +// Lstat lstat's the path if the underlying afero filesystem supports it. If +// the filesystem does not support afero.Lstater, an error will be returned. +// A nil os.FileInfo is returned on errors. +func (p *Path) Lstat() (os.FileInfo, error) { + lStater, ok := p.Fs().(afero.Lstater) + if !ok { + return nil, p.doesNotImplementErr("afero.Lstater") + } + fileInfo, lstatCalled, err := lStater.LstatIfPossible(p.Path()) + if err != nil || !lstatCalled { + // If lstat wasn't called then the filesystem doesn't implement it. + // Thus, it isn't a symlink + return nil, err + } + + return fileInfo, nil +} + // ********************************* // * filesystem-specific functions * // ********************************* @@ -422,32 +444,36 @@ func (p *Path) String() string { // IsFile returns true if the given path is a file. func (p *Path) IsFile() (bool, error) { - fileInfo, err := p.Fs().Stat(p.Path()) + fileInfo, err := p.Stat() if err != nil { return false, err } + return IsFile(fileInfo) +} + +// IsFile returns whether or not the file described by the given +// os.FileInfo is a regular file. +func IsFile(fileInfo os.FileInfo) (bool, error) { return fileInfo.Mode().IsRegular(), nil } // IsSymlink returns true if the given path is a symlink. // Fails if the filesystem doesn't implement afero.Lstater. func (p *Path) IsSymlink() (bool, error) { - lStater, ok := p.Fs().(afero.Lstater) - if !ok { - return false, p.doesNotImplementErr("afero.Lstater") - } - fileInfo, lstatCalled, err := lStater.LstatIfPossible(p.Path()) - if err != nil || !lstatCalled { - // If lstat wasn't called then the filesystem doesn't implement it. - // Thus, it isn't a symlink + fileInfo, err := p.Lstat() + if err != nil { return false, err } + return IsSymlink(fileInfo) +} - isSymlink := false +// IsSymlink returns true if the file described by the given +// os.FileInfo describes a symlink. +func IsSymlink(fileInfo os.FileInfo) (bool, error) { if fileInfo.Mode()&os.ModeSymlink != 0 { - isSymlink = true + return true, nil } - return isSymlink, nil + return false, nil } // Path returns the string representation of the path @@ -516,5 +542,10 @@ func (p *Path) Mtime() (time.Time, error) { if err != nil { return time.Time{}, err } - return stat.ModTime(), nil + return Mtime(stat) +} + +// Mtime returns the mtime described in the given os.FileInfo object +func Mtime(fileInfo os.FileInfo) (time.Time, error) { + return fileInfo.ModTime(), nil } From cf19d79f91c616dedd2c1ad25337cfe75ba19bfd Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Thu, 16 Jul 2020 22:02:12 -0500 Subject: [PATCH 2/5] Fixing Lstat There is an extra parameter in Lstat that needs to be added. It is supposed to return a bool describing whether or not Lstat was actually called. --- path.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/path.go b/path.go index 3b108d4..2558111 100644 --- a/path.go +++ b/path.go @@ -402,20 +402,16 @@ func (p *Path) RelativeTo(other *Path) (*Path, error) { // Lstat lstat's the path if the underlying afero filesystem supports it. If // the filesystem does not support afero.Lstater, an error will be returned. -// A nil os.FileInfo is returned on errors. -func (p *Path) Lstat() (os.FileInfo, error) { +// A nil os.FileInfo is returned on errors. Also returned is a boolean describing +// whether or not Lstat was called (in cases where the filesystem is an OS filesystem) +// or not called (in cases where only Stat is supported). See +// https://godoc.org/github.com/spf13/afero#Lstater for more info. +func (p *Path) Lstat() (os.FileInfo, bool, error) { lStater, ok := p.Fs().(afero.Lstater) if !ok { - return nil, p.doesNotImplementErr("afero.Lstater") + return nil, false, p.doesNotImplementErr("afero.Lstater") } - fileInfo, lstatCalled, err := lStater.LstatIfPossible(p.Path()) - if err != nil || !lstatCalled { - // If lstat wasn't called then the filesystem doesn't implement it. - // Thus, it isn't a symlink - return nil, err - } - - return fileInfo, nil + return lStater.LstatIfPossible(p.Path()) } // ********************************* @@ -460,7 +456,7 @@ func IsFile(fileInfo os.FileInfo) (bool, error) { // IsSymlink returns true if the given path is a symlink. // Fails if the filesystem doesn't implement afero.Lstater. func (p *Path) IsSymlink() (bool, error) { - fileInfo, err := p.Lstat() + fileInfo, _, err := p.Lstat() if err != nil { return false, err } From 1092248c6759f54679a3c6d792e052c16ed774af Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Thu, 16 Jul 2020 22:08:07 -0500 Subject: [PATCH 3/5] Adding Size() function. --- path.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/path.go b/path.go index 2558111..39fa123 100644 --- a/path.go +++ b/path.go @@ -545,3 +545,19 @@ func (p *Path) Mtime() (time.Time, error) { func Mtime(fileInfo os.FileInfo) (time.Time, error) { return fileInfo.ModTime(), nil } + +// Size returns the size of the object. Fails if the object doesn't exist. +func (p *Path) Size() (int64, error) { + stat, err := p.Stat() + if err != nil { + return 0, err + } + return Size(stat), nil +} + +// Size returns the size described by the os.FileInfo. Before you say anything, +// yes... you could just do fileInfo.Size(). This is purely a convenience function +// to create API consistency. +func Size(fileInfo os.FileInfo) int64 { + return fileInfo.Size() +} From d18b04444c2d37acf9a8c324fdafa59d4a23dfaa Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Thu, 16 Jul 2020 22:13:16 -0500 Subject: [PATCH 4/5] Adding IsDir function --- path.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/path.go b/path.go index 39fa123..e4f8734 100644 --- a/path.go +++ b/path.go @@ -188,6 +188,12 @@ func (p *Path) IsDir() (bool, error) { return afero.IsDir(p.Fs(), p.Path()) } +// IsDir returns whether or not the os.FileInfo object represents a +// directory. +func IsDir(fileInfo os.FileInfo) bool { + return fileInfo.IsDir() +} + // IsEmpty checks if a given file or directory is empty. func (p *Path) IsEmpty() (bool, error) { return afero.IsEmpty(p.Fs(), p.Path()) From cd0e2cccaa50bdbeee70a5c96c7b19aa021f0123 Mon Sep 17 00:00:00 2001 From: LandonTClipp Date: Thu, 16 Jul 2020 22:29:39 -0500 Subject: [PATCH 5/5] Adding tests --- path_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/path_test.go b/path_test.go index add1a78..f929f4d 100644 --- a/path_test.go +++ b/path_test.go @@ -135,6 +135,39 @@ func (p *PathSuite) TestRenamePath() { assert.False(p.T(), oldFileExists) } +func (p *PathSuite) TestSizeZero() { + file := p.tmpdir.Join("file.txt") + require.NoError(p.T(), file.WriteFile([]byte{}, 0o644)) + size, err := file.Size() + require.NoError(p.T(), err) + p.Zero(size) +} + +func (p *PathSuite) TestSizeNonZero() { + msg := "oh, it's you" + file := p.tmpdir.Join("file.txt") + require.NoError(p.T(), file.WriteFile([]byte(msg), 0o644)) + size, err := file.Size() + require.NoError(p.T(), err) + p.Equal(len(msg), int(size)) +} + +func (p *PathSuite) TestIsDir() { + dir := p.tmpdir.Join("dir") + require.NoError(p.T(), dir.Mkdir(0o755)) + isDir, err := dir.IsDir() + require.NoError(p.T(), err) + p.True(isDir) +} + +func (p *PathSuite) TestIsntDir() { + file := p.tmpdir.Join("file.txt") + require.NoError(p.T(), file.WriteFile([]byte("hello world!"), 0o644)) + isDir, err := file.IsDir() + require.NoError(p.T(), err) + p.False(isDir) +} + func (p *PathSuite) TestGetLatest() { now := time.Now() for i := 0; i < 5; i++ {