aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/chigopher/pathlib/path.go
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2024-05-08 14:17:02 +0200
committerTaras Madan <tarasmadan@google.com>2024-05-08 12:48:31 +0000
commit0ac372459159c5632ae7ce9778e9be771708a9b3 (patch)
tree9c6a979cada5c22b46dd3c5ad4b0d706f2252272 /vendor/github.com/chigopher/pathlib/path.go
parent69141a688b25b2d33daf570507dbb13a3c80e5d9 (diff)
vendor: go mod vendor
We don't want to download mockery every time.
Diffstat (limited to 'vendor/github.com/chigopher/pathlib/path.go')
-rw-r--r--vendor/github.com/chigopher/pathlib/path.go657
1 files changed, 657 insertions, 0 deletions
diff --git a/vendor/github.com/chigopher/pathlib/path.go b/vendor/github.com/chigopher/pathlib/path.go
new file mode 100644
index 000000000..2f802e159
--- /dev/null
+++ b/vendor/github.com/chigopher/pathlib/path.go
@@ -0,0 +1,657 @@
+package pathlib
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/spf13/afero"
+)
+
+// Path is an object that represents a path
+type Path struct {
+ 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
+ // DefaultDirMode is the mode that will be used when creating new directories
+ DefaultDirMode os.FileMode
+ // Sep is the seperator used in path calculations. By default this is set to
+ // os.PathSeparator.
+ Sep string
+}
+
+type PathOpts func(p *Path)
+
+func PathWithAfero(fs afero.Fs) PathOpts {
+ return func(p *Path) {
+ p.fs = fs
+ }
+}
+
+func PathWithSeperator(sep string) PathOpts {
+ return func(p *Path) {
+ p.Sep = sep
+ }
+}
+
+// NewPath returns a new OS path
+func NewPath(path string, opts ...PathOpts) *Path {
+ p := &Path{
+ path: path,
+ fs: afero.NewOsFs(),
+ DefaultFileMode: DefaultFileMode,
+ DefaultDirMode: DefaultDirMode,
+ Sep: string(os.PathSeparator),
+ }
+ for _, opt := range opts {
+ opt(p)
+ }
+ return p
+}
+
+// NewPathAfero returns a Path object with the given Afero object
+//
+// Deprecated: Use the PathWithAfero option in Newpath instead.
+func NewPathAfero(path string, fs afero.Fs) *Path {
+ return NewPath(path, PathWithAfero(fs))
+}
+
+// Glob returns all of the path objects matched by the given pattern
+// inside of the afero filesystem.
+func Glob(fs afero.Fs, pattern string) ([]*Path, error) {
+ matches, err := afero.Glob(fs, pattern)
+ if err != nil {
+ return nil, fmt.Errorf("failed to glob: %w", err)
+ }
+
+ pathMatches := []*Path{}
+ for _, match := range matches {
+ pathMatches = append(pathMatches, NewPathAfero(match, fs))
+ }
+ return pathMatches, nil
+}
+
+type namer interface {
+ Name() string
+}
+
+func getFsName(fs afero.Fs) string {
+ if name, ok := fs.(namer); ok {
+ return name.Name()
+ }
+ return ""
+}
+
+// Fs returns the internal afero.Fs object.
+func (p *Path) Fs() afero.Fs {
+ return p.fs
+}
+
+func (p *Path) doesNotImplementErr(interfaceName string) error {
+ return doesNotImplementErr(interfaceName, p.Fs())
+}
+
+func doesNotImplementErr(interfaceName string, fs afero.Fs) error {
+ return fmt.Errorf("%w: Path's afero filesystem %s does not implement %s", ErrDoesNotImplement, getFsName(fs), interfaceName)
+}
+
+func (p *Path) lstatNotPossible() error {
+ return lstatNotPossible(p.Fs())
+}
+
+func lstatNotPossible(fs afero.Fs) error {
+ return fmt.Errorf("%w: Path's afero filesystem %s does not support lstat", ErrLstatNotPossible, getFsName(fs))
+}
+
+// *******************************
+// * afero.Fs wrappers *
+// *******************************
+
+// Create creates a file if possible, returning the file and an error, if any happens.
+func (p *Path) Create() (File, error) {
+ file, err := p.Fs().Create(p.String())
+ return File{file}, err
+}
+
+// Mkdir makes the current dir. If the parents don't exist, an error
+// is returned.
+func (p *Path) Mkdir() error {
+ return p.Fs().Mkdir(p.String(), p.DefaultDirMode)
+}
+
+// MkdirMode makes the current dir. If the parents don't exist, an error
+// is returned.
+func (p *Path) MkdirMode(perm os.FileMode) error {
+ return p.Fs().Mkdir(p.String(), perm)
+}
+
+// MkdirAll makes all of the directories up to, and including, the given path.
+func (p *Path) MkdirAll() error {
+ return p.Fs().MkdirAll(p.String(), p.DefaultDirMode)
+}
+
+// MkdirAllMode makes all of the directories up to, and including, the given path.
+func (p *Path) MkdirAllMode(perm os.FileMode) error {
+ return p.Fs().MkdirAll(p.String(), perm)
+}
+
+// Open opens a file for read-only, returning it or an error, if any happens.
+func (p *Path) Open() (*File, error) {
+ handle, err := p.Fs().Open(p.String())
+ return &File{
+ File: handle,
+ }, err
+}
+
+// OpenFile opens a file using the given flags.
+// See the list of flags at: https://golang.org/pkg/os/#pkg-constants
+func (p *Path) OpenFile(flag int) (*File, error) {
+ handle, err := p.Fs().OpenFile(p.String(), flag, p.DefaultFileMode)
+ return &File{
+ File: handle,
+ }, err
+}
+
+// OpenFileMode opens a file using the given flags and the given mode.
+// See the list of flags at: https://golang.org/pkg/os/#pkg-constants
+func (p *Path) OpenFileMode(flag int, perm os.FileMode) (*File, error) {
+ handle, err := p.Fs().OpenFile(p.String(), flag, perm)
+ return &File{
+ File: handle,
+ }, err
+}
+
+// Remove removes a file, returning an error, if any
+// happens.
+func (p *Path) Remove() error {
+ return p.Fs().Remove(p.String())
+}
+
+// RemoveAll removes the given path and all of its children.
+func (p *Path) RemoveAll() error {
+ return p.Fs().RemoveAll(p.String())
+}
+
+// RenameStr renames a file
+func (p *Path) RenameStr(newname string) error {
+ if err := p.Fs().Rename(p.String(), newname); err != nil {
+ return err
+ }
+
+ // Rename succeeded. Set our path to the newname.
+ p.path = newname
+ return nil
+}
+
+// Rename renames a file
+func (p *Path) Rename(target *Path) error {
+ return p.RenameStr(target.String())
+}
+
+// Stat returns the os.FileInfo of the given path
+func (p *Path) Stat() (os.FileInfo, error) {
+ return p.Fs().Stat(p.String())
+}
+
+// Chmod changes the file mode of the given path
+func (p *Path) Chmod(mode os.FileMode) error {
+ return p.Fs().Chmod(p.String(), 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.String(), 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.String())
+}
+
+// Exists returns whether the path exists
+func (p *Path) Exists() (bool, error) {
+ return afero.Exists(p.Fs(), p.String())
+}
+
+// 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.String(), 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.String(), subslice)
+}
+
+// IsDir checks if a given path is a directory.
+func (p *Path) IsDir() (bool, error) {
+ return afero.IsDir(p.Fs(), p.String())
+}
+
+// IsDir returns whether or not the os.FileMode object represents a
+// directory.
+func IsDir(mode os.FileMode) bool {
+ return mode.IsDir()
+}
+
+// IsEmpty checks if a given file or directory is empty.
+func (p *Path) IsEmpty() (bool, error) {
+ return afero.IsEmpty(p.Fs(), p.String())
+}
+
+// ReadDir reads the current path and returns a list of the corresponding
+// Path objects. This function differs from os.Readdir in that it does
+// not call Stat() on the files. Instead, it calls Readdirnames which
+// is less expensive and does not force the caller to make expensive
+// Stat calls.
+func (p *Path) ReadDir() ([]*Path, error) {
+ paths := []*Path{}
+ handle, err := p.Open()
+ if err != nil {
+ return paths, err
+ }
+ children, err := handle.Readdirnames(-1)
+ if err != nil {
+ return paths, err
+ }
+ for _, child := range children {
+ paths = append(paths, p.Join(child))
+ }
+ return paths, err
+}
+
+// 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.String())
+}
+
+// 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.String(), r)
+}
+
+// WriteFileMode 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) WriteFileMode(data []byte, perm os.FileMode) error {
+ return afero.WriteFile(p.Fs(), p.String(), data, perm)
+}
+
+// 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) error {
+ return afero.WriteFile(p.Fs(), p.String(), data, p.DefaultFileMode)
+}
+
+// WriteReader takes a reader and writes the content
+func (p *Path) WriteReader(r io.Reader) error {
+ return afero.WriteReader(p.Fs(), p.String(), r)
+}
+
+// *************************************
+// * pathlib.Path-like implementations *
+// *************************************
+
+// Name returns the string representing the final path component
+func (p *Path) Name() string {
+ return filepath.Base(p.path)
+}
+
+// Parent returns the Path object of the parent directory
+func (p *Path) Parent() *Path {
+ return NewPathAfero(filepath.Dir(p.String()), p.Fs())
+}
+
+// Readlink returns the target path of a symlink.
+//
+// This will fail if the underlying afero filesystem does not implement
+// afero.LinkReader.
+func (p *Path) Readlink() (*Path, error) {
+ linkReader, ok := p.Fs().(afero.LinkReader)
+ if !ok {
+ return nil, p.doesNotImplementErr("afero.LinkReader")
+ }
+
+ resolvedPathStr, err := linkReader.ReadlinkIfPossible(p.path)
+ if err != nil {
+ return nil, err
+ }
+ return NewPathAfero(resolvedPathStr, p.fs), nil
+}
+
+func resolveIfSymlink(path *Path) (*Path, bool, error) {
+ isSymlink, err := path.IsSymlink()
+ if err != nil {
+ return path, isSymlink, err
+ }
+ if isSymlink {
+ resolvedPath, err := path.Readlink()
+ if err != nil {
+ // Return the path unchanged on errors
+ return path, isSymlink, err
+ }
+ return resolvedPath, isSymlink, nil
+ }
+ return path, isSymlink, nil
+}
+
+func resolveAllHelper(path *Path) (*Path, error) {
+ parts := path.Parts()
+
+ for i := 0; i < len(parts); i++ {
+ rightOfComponent := parts[i+1:]
+ upToComponent := parts[:i+1]
+
+ componentPath := NewPathAfero(strings.Join(upToComponent, path.Sep), path.Fs())
+ resolved, isSymlink, err := resolveIfSymlink(componentPath)
+ if err != nil {
+ return path, err
+ }
+
+ if isSymlink {
+ if resolved.IsAbsolute() {
+ return resolveAllHelper(resolved.Join(strings.Join(rightOfComponent, path.Sep)))
+ }
+ return resolveAllHelper(componentPath.Parent().JoinPath(resolved).Join(rightOfComponent...))
+ }
+ }
+
+ // If we get through the entire iteration above, that means no component was a symlink.
+ // Return the argument.
+ return path, nil
+}
+
+// ResolveAll canonicalizes the path by following every symlink in
+// every component of the given path recursively. The behavior
+// should be identical to the `readlink -f` command from POSIX OSs.
+// This will fail if the underlying afero filesystem does not implement
+// afero.LinkReader. The path will be returned unchanged on errors.
+func (p *Path) ResolveAll() (*Path, error) {
+ return resolveAllHelper(p)
+}
+
+// Parts returns the individual components of a path
+func (p *Path) Parts() []string {
+ parts := []string{}
+ if p.IsAbsolute() {
+ parts = append(parts, p.Sep)
+ }
+ normalizedPathStr := normalizePathString(p.String())
+ normalizedParts := normalizePathParts(strings.Split(normalizedPathStr, p.Sep))
+ return append(parts, normalizedParts...)
+}
+
+// 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, "/")
+}
+
+// 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}
+ paths = append(paths, elems...)
+ return NewPathAfero(strings.Join(paths, p.Sep), p.Fs())
+}
+
+// JoinPath is the same as Join() except it accepts a path object
+func (p *Path) JoinPath(path *Path) *Path {
+ return p.Join(path.Parts()...)
+}
+
+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 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)
+ normalized := []string{}
+ for i := 0; i < len(path); i++ {
+ if path[i] != "" {
+ normalized = append(normalized, path[i])
+ }
+ }
+ return normalized
+}
+
+// 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) {
+ thisPathNormalized := normalizePathString(p.String())
+ otherPathNormalized := normalizePathString(other.String())
+
+ thisParts := p.Parts()
+ otherParts := other.Parts()
+
+ var relativeBase int
+ for idx, part := range otherParts {
+ if idx >= len(thisParts) || thisParts[idx] != part {
+ return p, fmt.Errorf("%s does not start with %s: %w", thisPathNormalized, otherPathNormalized, ErrRelativeTo)
+ }
+ relativeBase = idx
+ }
+
+ relativePath := thisParts[relativeBase+1:]
+
+ if len(relativePath) == 0 || (len(relativePath) == 1 && relativePath[0] == "") {
+ relativePath = []string{"."}
+ }
+
+ return NewPathAfero(strings.Join(relativePath, "/"), p.Fs()), nil
+}
+
+// RelativeToStr 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) RelativeToStr(other string) (*Path, error) {
+ return p.RelativeTo(NewPathAfero(other, p.Fs()))
+}
+
+// Lstat lstat's the path if the underlying afero filesystem supports it. If
+// the filesystem does not support afero.Lstater, or if the filesystem implements
+// afero.Lstater but returns false for the "lstat called" return value.
+//
+// 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")
+ }
+ stat, lstatCalled, err := lStater.LstatIfPossible(p.String())
+ if !lstatCalled && err == nil {
+ return nil, p.lstatNotPossible()
+ }
+ return stat, err
+}
+
+// SymlinkStr symlinks to the target location. This will fail if the underlying
+// afero filesystem does not implement afero.Linker.
+func (p *Path) SymlinkStr(target string) error {
+ return p.Symlink(NewPathAfero(target, p.Fs()))
+}
+
+// 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)
+}
+
+// String returns the string representation of the path
+func (p *Path) String() string {
+ return p.path
+}
+
+// IsFile returns true if the given path is a file.
+func (p *Path) IsFile() (bool, error) {
+ fileInfo, err := p.Stat()
+ if err != nil {
+ return false, err
+ }
+ return IsFile(fileInfo.Mode()), nil
+}
+
+// IsFile returns whether or not the file described by the given
+// os.FileMode is a regular file.
+func IsFile(mode os.FileMode) bool {
+ return mode.IsRegular()
+}
+
+// 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()
+ if err != nil {
+ return false, err
+ }
+ return IsSymlink(fileInfo.Mode()), nil
+}
+
+// IsSymlink returns true if the file described by the given
+// os.FileMode describes a symlink.
+func IsSymlink(mode os.FileMode) bool {
+ return mode&os.ModeSymlink != 0
+}
+
+// DeepEquals returns whether or not the path pointed to by other
+// has the same resolved filepath as self.
+func (p *Path) DeepEquals(other *Path) (bool, error) {
+ selfResolved, err := p.ResolveAll()
+ if err != nil {
+ return false, err
+ }
+ otherResolved, err := other.ResolveAll()
+ if err != nil {
+ return false, err
+ }
+
+ return selfResolved.Clean().Equals(otherResolved.Clean()), nil
+}
+
+// Equals returns whether or not the object's path is identical
+// to other's, in a shallow sense. It simply checks for equivalence
+// in the unresolved Paths() of each object.
+func (p *Path) Equals(other *Path) bool {
+ return p.String() == other.String()
+}
+
+// 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.
+func (p *Path) GetLatest() (*Path, error) {
+ files, err := p.ReadDir()
+ if err != nil {
+ return nil, err
+ }
+
+ var greatestFileSeen *Path
+ for _, file := range files {
+ if greatestFileSeen == nil {
+ greatestFileSeen = p.Join(file.Name())
+ }
+
+ greatestMtime, err := greatestFileSeen.Mtime()
+ if err != nil {
+ return nil, err
+ }
+
+ thisMtime, err := file.Mtime()
+ // There is a possible race condition where the file is deleted after
+ // our call to ReadDir. We throw away the error if it isn't
+ // os.ErrNotExist
+ if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ if thisMtime.After(greatestMtime) {
+ greatestFileSeen = p.Join(file.Name())
+ }
+ }
+
+ return greatestFileSeen, nil
+}
+
+// Glob returns all matches of pattern relative to this object's path.
+func (p *Path) Glob(pattern string) ([]*Path, error) {
+ return Glob(p.Fs(), p.Join(pattern).String())
+}
+
+// Clean returns a new object that is a lexically-cleaned
+// version of Path.
+func (p *Path) Clean() *Path {
+ return NewPathAfero(filepath.Clean(p.String()), p.Fs())
+}
+
+// Mtime returns the modification time of the given path.
+func (p *Path) Mtime() (time.Time, error) {
+ stat, err := p.Stat()
+ if err != nil {
+ return time.Time{}, err
+ }
+ return Mtime(stat)
+}
+
+// Copy copies the path to another path using io.Copy.
+// Returned is the number of bytes copied and any error values.
+// The destination file is truncated if it exists, and is created
+// if it does not exist.
+func (p *Path) Copy(other *Path) (int64, error) {
+ srcFile, err := p.Open()
+ if err != nil {
+ return 0, fmt.Errorf("opening source file: %w", err)
+ }
+ defer srcFile.Close()
+ dstFile, err := other.OpenFile(os.O_TRUNC | os.O_CREATE | os.O_WRONLY)
+ if err != nil {
+ return 0, fmt.Errorf("opening destination file: %w", err)
+ }
+ defer dstFile.Close()
+ return io.Copy(dstFile, srcFile)
+}
+
+// Mtime returns the mtime described in the given os.FileInfo object
+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()
+}