package turbopath import ( "io/ioutil" "os" "path/filepath" "strings" ) // AbsoluteSystemPath is a root-relative path using system separators. type AbsoluteSystemPath string // _dirPermissions are the default permission bits we apply to directories. const _dirPermissions = os.ModeDir | 0775 // _nonRelativeSentinel is the leading sentinel that indicates traversal. const _nonRelativeSentinel = ".." // ToString returns a string represenation of this Path. // Used for interfacing with APIs that require a string. func (p AbsoluteSystemPath) ToString() string { return string(p) } // RelativeTo calculates the relative path between two `AbsoluteSystemPath`s. func (p AbsoluteSystemPath) RelativeTo(basePath AbsoluteSystemPath) (AnchoredSystemPath, error) { processed, err := filepath.Rel(basePath.ToString(), p.ToString()) return AnchoredSystemPath(processed), err } // Join appends relative path segments to this AbsoluteSystemPath. func (p AbsoluteSystemPath) Join(additional ...RelativeSystemPath) AbsoluteSystemPath { cast := RelativeSystemPathArray(additional) return AbsoluteSystemPath(filepath.Join(p.ToString(), filepath.Join(cast.ToStringArray()...))) } // ToStringDuringMigration returns a string representation of this path. // These instances should eventually be removed. func (p AbsoluteSystemPath) ToStringDuringMigration() string { return p.ToString() } // UntypedJoin is a Join that does not constrain the type of the arguments. // This enables you to pass in strings, but does not protect you from garbage in. func (p AbsoluteSystemPath) UntypedJoin(args ...string) AbsoluteSystemPath { return AbsoluteSystemPath(filepath.Join(p.ToString(), filepath.Join(args...))) } // Dir implements filepath.Dir() for an AbsoluteSystemPath func (p AbsoluteSystemPath) Dir() AbsoluteSystemPath { return AbsoluteSystemPath(filepath.Dir(p.ToString())) } // Mkdir implements os.Mkdir(p, perm) func (p AbsoluteSystemPath) Mkdir(perm os.FileMode) error { return os.Mkdir(p.ToString(), perm) } // MkdirAll implements os.MkdirAll(p, perm) func (p AbsoluteSystemPath) MkdirAll(perm os.FileMode) error { return os.MkdirAll(p.ToString(), perm) } // Open implements os.Open(p) for an AbsoluteSystemPath func (p AbsoluteSystemPath) Open() (*os.File, error) { return os.Open(p.ToString()) } // OpenFile implements os.OpenFile for an absolute path func (p AbsoluteSystemPath) OpenFile(flags int, mode os.FileMode) (*os.File, error) { return os.OpenFile(p.ToString(), flags, mode) } // Lstat implements os.Lstat for absolute path func (p AbsoluteSystemPath) Lstat() (os.FileInfo, error) { return os.Lstat(p.ToString()) } // Stat implements os.Stat for absolute path func (p AbsoluteSystemPath) Stat() (os.FileInfo, error) { return os.Stat(p.ToString()) } // Findup checks all parent directories for a file. func (p AbsoluteSystemPath) Findup(name RelativeSystemPath) (AbsoluteSystemPath, error) { path, err := FindupFrom(name.ToString(), p.ToString()) return AbsoluteSystemPath(path), err } // Exists returns true if the given path exists. func (p AbsoluteSystemPath) Exists() bool { _, err := p.Lstat() return err == nil } // DirExists returns true if the given path exists and is a directory. func (p AbsoluteSystemPath) DirExists() bool { info, err := p.Lstat() return err == nil && info.IsDir() } // FileExists returns true if the given path exists and is a file. func (p AbsoluteSystemPath) FileExists() bool { info, err := os.Lstat(p.ToString()) return err == nil && !info.IsDir() } // ContainsPath returns true if this absolute path is a parent of the // argument. func (p AbsoluteSystemPath) ContainsPath(other AbsoluteSystemPath) (bool, error) { // In Go, filepath.Rel can return a path that starts with "../" or equivalent. // Checking filesystem-level contains can get extremely complicated // (see https://github.com/golang/dep/blob/f13583b555deaa6742f141a9c1185af947720d60/internal/fs/fs.go#L33) // As a compromise, rely on the stdlib to generate a relative path and then check // if the first step is "../". rel, err := filepath.Rel(p.ToString(), other.ToString()) if err != nil { return false, err } return !strings.HasPrefix(rel, _nonRelativeSentinel), nil } // ReadFile reads the contents of the specified file func (p AbsoluteSystemPath) ReadFile() ([]byte, error) { return ioutil.ReadFile(p.ToString()) } // VolumeName returns the volume of the specified path func (p AbsoluteSystemPath) VolumeName() string { return filepath.VolumeName(p.ToString()) } // WriteFile writes the contents of the specified file func (p AbsoluteSystemPath) WriteFile(contents []byte, mode os.FileMode) error { return ioutil.WriteFile(p.ToString(), contents, mode) } // EnsureDir ensures that the directory containing this file exists func (p AbsoluteSystemPath) EnsureDir() error { dir := p.Dir() err := os.MkdirAll(dir.ToString(), _dirPermissions) if err != nil && dir.FileExists() { // It looks like this is a file and not a directory. Attempt to remove it; this can // happen in some cases if you change a rule from outputting a file to a directory. if err2 := dir.Remove(); err2 == nil { err = os.MkdirAll(dir.ToString(), _dirPermissions) } else { return err } } return err } // MkdirAllMode Create directory at path and all necessary parents ensuring that path has the correct mode set func (p AbsoluteSystemPath) MkdirAllMode(mode os.FileMode) error { info, err := p.Lstat() if err == nil { if info.IsDir() && info.Mode() == mode { // Dir exists with the correct mode return nil } else if info.IsDir() { // Dir exists with incorrect mode return os.Chmod(p.ToString(), mode) } else { // Path exists as file, remove it if err := p.Remove(); err != nil { return err } } } if err := os.MkdirAll(p.ToString(), mode); err != nil { return err } // This is necessary only when umask results in creating a directory with permissions different than the one passed by the user return os.Chmod(p.ToString(), mode) } // Create is the AbsoluteSystemPath wrapper for os.Create func (p AbsoluteSystemPath) Create() (*os.File, error) { return os.Create(p.ToString()) } // Ext implements filepath.Ext(p) for an absolute path func (p AbsoluteSystemPath) Ext() string { return filepath.Ext(p.ToString()) } // RelativePathString returns the relative path from this AbsoluteSystemPath to another absolute path in string form as a string func (p AbsoluteSystemPath) RelativePathString(path string) (string, error) { return filepath.Rel(p.ToString(), path) } // PathTo returns the relative path between two absolute paths // This should likely eventually return an AnchoredSystemPath func (p AbsoluteSystemPath) PathTo(other AbsoluteSystemPath) (string, error) { return p.RelativePathString(other.ToString()) } // Symlink implements os.Symlink(target, p) for absolute path func (p AbsoluteSystemPath) Symlink(target string) error { return os.Symlink(target, p.ToString()) } // Readlink implements os.Readlink(p) for an absolute path func (p AbsoluteSystemPath) Readlink() (string, error) { return os.Readlink(p.ToString()) } // Remove removes the file or (empty) directory at the given path func (p AbsoluteSystemPath) Remove() error { return os.Remove(p.ToString()) } // RemoveAll implements os.RemoveAll for absolute paths. func (p AbsoluteSystemPath) RemoveAll() error { return os.RemoveAll(p.ToString()) } // Base implements filepath.Base for an absolute path func (p AbsoluteSystemPath) Base() string { return filepath.Base(p.ToString()) } // Rename implements os.Rename(p, dest) for absolute paths func (p AbsoluteSystemPath) Rename(dest AbsoluteSystemPath) error { return os.Rename(p.ToString(), dest.ToString()) } // EvalSymlinks implements filepath.EvalSymlinks for absolute path func (p AbsoluteSystemPath) EvalSymlinks() (AbsoluteSystemPath, error) { result, err := filepath.EvalSymlinks(p.ToString()) if err != nil { return "", err } return AbsoluteSystemPath(result), nil } // HasPrefix is strings.HasPrefix for paths, ensuring that it matches on separator boundaries. // This does NOT perform Clean in advance. func (p AbsoluteSystemPath) HasPrefix(prefix AbsoluteSystemPath) bool { prefixLen := len(prefix) pathLen := len(p) if prefixLen > pathLen { // Can't be a prefix if longer. return false } else if prefixLen == pathLen { // Can be a prefix if they're equal, but otherwise no. return p == prefix } // otherPath is definitely shorter than p. // We need to confirm that p[len(otherPath)] is a system separator. return strings.HasPrefix(p.ToString(), prefix.ToString()) && os.IsPathSeparator(p[prefixLen]) }