658 lines
19 KiB
Go
658 lines
19 KiB
Go
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()
|
|
}
|