diff options
| author | Taras Madan <tarasmadan@google.com> | 2024-05-08 14:17:02 +0200 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2024-05-08 12:48:31 +0000 |
| commit | 0ac372459159c5632ae7ce9778e9be771708a9b3 (patch) | |
| tree | 9c6a979cada5c22b46dd3c5ad4b0d706f2252272 /vendor/github.com/chigopher/pathlib/path.go | |
| parent | 69141a688b25b2d33daf570507dbb13a3c80e5d9 (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.go | 657 |
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() +} |
