aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/turbopath
diff options
context:
space:
mode:
author简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
committer简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
commitdd84b9d64fb98746a230cd24233ff50a562c39c9 (patch)
treeb583261ef00b3afe72ec4d6dacb31e57779a6faf /cli/internal/turbopath
parent0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff)
downloadHydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz
HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip
Diffstat (limited to 'cli/internal/turbopath')
-rw-r--r--cli/internal/turbopath/absolute_system_path.go258
-rw-r--r--cli/internal/turbopath/absolute_system_path_darwin.go23
-rw-r--r--cli/internal/turbopath/absolute_system_path_notdarwin.go13
-rw-r--r--cli/internal/turbopath/absolute_system_path_test.go174
-rw-r--r--cli/internal/turbopath/anchored_system_path.go75
-rw-r--r--cli/internal/turbopath/anchored_unix_path.go31
-rw-r--r--cli/internal/turbopath/find_up.go50
-rw-r--r--cli/internal/turbopath/relative_system_path.go44
-rw-r--r--cli/internal/turbopath/relative_unix_path.go31
-rw-r--r--cli/internal/turbopath/turbopath.go112
10 files changed, 811 insertions, 0 deletions
diff --git a/cli/internal/turbopath/absolute_system_path.go b/cli/internal/turbopath/absolute_system_path.go
new file mode 100644
index 0000000..df65827
--- /dev/null
+++ b/cli/internal/turbopath/absolute_system_path.go
@@ -0,0 +1,258 @@
+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])
+}
diff --git a/cli/internal/turbopath/absolute_system_path_darwin.go b/cli/internal/turbopath/absolute_system_path_darwin.go
new file mode 100644
index 0000000..e2c3bff
--- /dev/null
+++ b/cli/internal/turbopath/absolute_system_path_darwin.go
@@ -0,0 +1,23 @@
+//go:build darwin
+// +build darwin
+
+// Adapted from https://github.com/containerd/continuity/blob/b4ca35286886296377de39e6eafd1affae019fc3/driver/lchmod_unix.go
+// Copyright The containerd Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package turbopath
+
+import (
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+// Lchmod changes the mode of a file not following symlinks.
+func (p AbsoluteSystemPath) Lchmod(mode os.FileMode) error {
+ err := unix.Fchmodat(unix.AT_FDCWD, p.ToString(), uint32(mode), unix.AT_SYMLINK_NOFOLLOW)
+ if err != nil {
+ err = &os.PathError{Op: "lchmod", Path: p.ToString(), Err: err}
+ }
+ return err
+}
diff --git a/cli/internal/turbopath/absolute_system_path_notdarwin.go b/cli/internal/turbopath/absolute_system_path_notdarwin.go
new file mode 100644
index 0000000..1195888
--- /dev/null
+++ b/cli/internal/turbopath/absolute_system_path_notdarwin.go
@@ -0,0 +1,13 @@
+//go:build !darwin
+// +build !darwin
+
+package turbopath
+
+import (
+ "os"
+)
+
+// Lchmod changes the mode of a file not following symlinks.
+func (p AbsoluteSystemPath) Lchmod(mode os.FileMode) error {
+ return nil
+}
diff --git a/cli/internal/turbopath/absolute_system_path_test.go b/cli/internal/turbopath/absolute_system_path_test.go
new file mode 100644
index 0000000..4ca36f9
--- /dev/null
+++ b/cli/internal/turbopath/absolute_system_path_test.go
@@ -0,0 +1,174 @@
+package turbopath
+
+import (
+ "os"
+ "runtime"
+ "testing"
+
+ "gotest.tools/v3/assert"
+ "gotest.tools/v3/fs"
+)
+
+func Test_Mkdir(t *testing.T) {
+ type Case struct {
+ name string
+ isDir bool
+ exists bool
+ mode os.FileMode
+ expectedMode os.FileMode
+ }
+
+ cases := []Case{
+ {
+ name: "dir doesn't exist",
+ exists: false,
+ expectedMode: os.ModeDir | 0777,
+ },
+ {
+ name: "path exists as file",
+ exists: true,
+ isDir: false,
+ mode: 0666,
+ expectedMode: os.ModeDir | 0755,
+ },
+ {
+ name: "dir exists with incorrect mode",
+ exists: true,
+ isDir: false,
+ mode: os.ModeDir | 0755,
+ expectedMode: os.ModeDir | 0655,
+ },
+ {
+ name: "dir exists with correct mode",
+ exists: true,
+ isDir: false,
+ mode: os.ModeDir | 0755,
+ expectedMode: os.ModeDir | 0755,
+ },
+ }
+
+ for _, testCase := range cases {
+ testDir := fs.NewDir(t, "system-path-mkdir-test")
+ testName := testCase.name
+ path := testDir.Join("foo")
+ if testCase.isDir {
+ err := os.Mkdir(path, testCase.mode)
+ assert.NilError(t, err, "%s: Mkdir", testName)
+ } else if testCase.exists {
+ file, err := os.Create(path)
+ assert.NilError(t, err, "%s: Create", testName)
+ err = file.Chmod(testCase.mode)
+ assert.NilError(t, err, "%s: Chmod", testName)
+ err = file.Close()
+ assert.NilError(t, err, "%s: Close", testName)
+ }
+
+ testPath := AbsoluteSystemPath(path)
+ err := testPath.MkdirAllMode(testCase.expectedMode)
+ assert.NilError(t, err, "%s: Mkdir", testName)
+
+ stat, err := testPath.Lstat()
+ assert.NilError(t, err, "%s: Lstat", testName)
+ assert.Assert(t, stat.IsDir(), testName)
+
+ assert.Assert(t, stat.IsDir(), testName)
+
+ if runtime.GOOS == "windows" {
+ // For windows os.Chmod will only change the writable bit so that's all we check
+ assert.Equal(t, stat.Mode().Perm()&0200, testCase.expectedMode.Perm()&0200, testName)
+ } else {
+ assert.Equal(t, stat.Mode(), testCase.expectedMode, testName)
+ }
+
+ }
+}
+
+func TestAbsoluteSystemPath_Findup(t *testing.T) {
+ tests := []struct {
+ name string
+ fs []AnchoredSystemPath
+ executionDirectory AnchoredSystemPath
+ fileName RelativeSystemPath
+ want AnchoredSystemPath
+ wantErr bool
+ }{
+ {
+ name: "hello world",
+ fs: []AnchoredSystemPath{
+ AnchoredUnixPath("one/two/three/four/.file").ToSystemPath(),
+ AnchoredUnixPath("one/two/three/four/.target").ToSystemPath(),
+ },
+ executionDirectory: AnchoredUnixPath("one/two/three/four").ToSystemPath(),
+ fileName: RelativeUnixPath(".target").ToSystemPath(),
+ want: AnchoredUnixPath("one/two/three/four/.target").ToSystemPath(),
+ },
+ {
+ name: "parent",
+ fs: []AnchoredSystemPath{
+ AnchoredUnixPath("one/two/three/four/.file").ToSystemPath(),
+ AnchoredUnixPath("one/two/three/.target").ToSystemPath(),
+ },
+ executionDirectory: AnchoredUnixPath("one/two/three/four").ToSystemPath(),
+ fileName: RelativeUnixPath(".target").ToSystemPath(),
+ want: AnchoredUnixPath("one/two/three/.target").ToSystemPath(),
+ },
+ {
+ name: "gets the closest",
+ fs: []AnchoredSystemPath{
+ AnchoredUnixPath("one/two/three/four/.file").ToSystemPath(),
+ AnchoredUnixPath("one/two/three/.target").ToSystemPath(),
+ AnchoredUnixPath("one/two/.target").ToSystemPath(),
+ },
+ executionDirectory: AnchoredUnixPath("one/two/three/four").ToSystemPath(),
+ fileName: RelativeUnixPath(".target").ToSystemPath(),
+ want: AnchoredUnixPath("one/two/three/.target").ToSystemPath(),
+ },
+ {
+ name: "nonexistent",
+ fs: []AnchoredSystemPath{
+ AnchoredUnixPath("one/two/three/four/.file").ToSystemPath(),
+ },
+ executionDirectory: AnchoredUnixPath("one/two/three/four").ToSystemPath(),
+ fileName: RelativeUnixPath(".nonexistent").ToSystemPath(),
+ want: "",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fsRoot := AbsoluteSystemPath(t.TempDir())
+ for _, file := range tt.fs {
+ path := file.RestoreAnchor(fsRoot)
+ assert.NilError(t, path.Dir().MkdirAll(0777))
+ assert.NilError(t, path.WriteFile(nil, 0777))
+ }
+
+ got, err := tt.executionDirectory.RestoreAnchor(fsRoot).Findup(tt.fileName)
+ if tt.wantErr {
+ assert.ErrorIs(t, err, os.ErrNotExist)
+ return
+ }
+ if got != "" && got != tt.want.RestoreAnchor(fsRoot) {
+ t.Errorf("AbsoluteSystemPath.Findup() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestJoin(t *testing.T) {
+ rawRoot, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("cwd %v", err)
+ }
+ root := AbsoluteSystemPathFromUpstream(rawRoot)
+ testRoot := root.Join("a", "b", "c")
+ dot := testRoot.Join(".")
+ if dot != testRoot {
+ t.Errorf(". path got %v, want %v", dot, testRoot)
+ }
+
+ doubleDot := testRoot.Join("..")
+ expectedDoubleDot := root.Join("a", "b")
+ if doubleDot != expectedDoubleDot {
+ t.Errorf(".. path got %v, want %v", doubleDot, expectedDoubleDot)
+ }
+}
diff --git a/cli/internal/turbopath/anchored_system_path.go b/cli/internal/turbopath/anchored_system_path.go
new file mode 100644
index 0000000..0957ead
--- /dev/null
+++ b/cli/internal/turbopath/anchored_system_path.go
@@ -0,0 +1,75 @@
+package turbopath
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// AnchoredSystemPath is a path stemming from a specified root using system separators.
+type AnchoredSystemPath string
+
+// ToString returns a string represenation of this Path.
+// Used for interfacing with APIs that require a string.
+func (p AnchoredSystemPath) ToString() string {
+ return string(p)
+}
+
+// ToStringDuringMigration returns the string representation of this path, and is for
+// use in situations where we expect a future path migration to remove the need for the
+// string representation
+func (p AnchoredSystemPath) ToStringDuringMigration() string {
+ return string(p)
+}
+
+// ToSystemPath returns itself.
+func (p AnchoredSystemPath) ToSystemPath() AnchoredSystemPath {
+ return p
+}
+
+// ToUnixPath converts a AnchoredSystemPath to a AnchoredUnixPath.
+func (p AnchoredSystemPath) ToUnixPath() AnchoredUnixPath {
+ return AnchoredUnixPath(filepath.ToSlash(p.ToString()))
+}
+
+// RelativeTo calculates the relative path between two AnchoredSystemPath`s.
+func (p AnchoredSystemPath) RelativeTo(basePath AnchoredSystemPath) (AnchoredSystemPath, error) {
+ processed, err := filepath.Rel(basePath.ToString(), p.ToString())
+ return AnchoredSystemPath(processed), err
+}
+
+// RestoreAnchor prefixes the AnchoredSystemPath with its anchor to return an AbsoluteSystemPath.
+func (p AnchoredSystemPath) RestoreAnchor(anchor AbsoluteSystemPath) AbsoluteSystemPath {
+ return AbsoluteSystemPath(filepath.Join(anchor.ToString(), p.ToString()))
+}
+
+// Dir returns filepath.Dir for the path.
+func (p AnchoredSystemPath) Dir() AnchoredSystemPath {
+ return AnchoredSystemPath(filepath.Dir(p.ToString()))
+}
+
+// Join appends relative path segments to this AnchoredSystemPath.
+func (p AnchoredSystemPath) Join(additional ...RelativeSystemPath) AnchoredSystemPath {
+ cast := RelativeSystemPathArray(additional)
+ return AnchoredSystemPath(filepath.Join(p.ToString(), filepath.Join(cast.ToStringArray()...)))
+}
+
+// HasPrefix is strings.HasPrefix for paths, ensuring that it matches on separator boundaries.
+// This does NOT perform Clean in advance.
+func (p AnchoredSystemPath) HasPrefix(prefix AnchoredSystemPath) 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])
+}
diff --git a/cli/internal/turbopath/anchored_unix_path.go b/cli/internal/turbopath/anchored_unix_path.go
new file mode 100644
index 0000000..23e371a
--- /dev/null
+++ b/cli/internal/turbopath/anchored_unix_path.go
@@ -0,0 +1,31 @@
+package turbopath
+
+import (
+ "path"
+ "path/filepath"
+)
+
+// AnchoredUnixPath is a path stemming from a specified root using Unix `/` separators.
+type AnchoredUnixPath string
+
+// ToString returns a string represenation of this Path.
+// Used for interfacing with APIs that require a string.
+func (p AnchoredUnixPath) ToString() string {
+ return string(p)
+}
+
+// ToSystemPath converts a AnchoredUnixPath to a AnchoredSystemPath.
+func (p AnchoredUnixPath) ToSystemPath() AnchoredSystemPath {
+ return AnchoredSystemPath(filepath.FromSlash(p.ToString()))
+}
+
+// ToUnixPath returns itself.
+func (p AnchoredUnixPath) ToUnixPath() AnchoredUnixPath {
+ return p
+}
+
+// Join appends relative path segments to this RelativeUnixPath.
+func (p AnchoredUnixPath) Join(additional ...RelativeUnixPath) AnchoredUnixPath {
+ cast := RelativeUnixPathArray(additional)
+ return AnchoredUnixPath(path.Join(p.ToString(), path.Join(cast.ToStringArray()...)))
+}
diff --git a/cli/internal/turbopath/find_up.go b/cli/internal/turbopath/find_up.go
new file mode 100644
index 0000000..bf7c39c
--- /dev/null
+++ b/cli/internal/turbopath/find_up.go
@@ -0,0 +1,50 @@
+package turbopath
+
+import (
+ "os"
+ "path/filepath"
+)
+
+func hasFile(name, dir string) (bool, error) {
+ files, err := os.ReadDir(dir)
+
+ if err != nil {
+ return false, err
+ }
+
+ for _, f := range files {
+ if name == f.Name() {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func findupFrom(name, dir string) (string, error) {
+ for {
+ found, err := hasFile(name, dir)
+
+ if err != nil {
+ return "", err
+ }
+
+ if found {
+ return filepath.Join(dir, name), nil
+ }
+
+ parent := filepath.Dir(dir)
+
+ if parent == dir {
+ return "", nil
+ }
+
+ dir = parent
+ }
+}
+
+// FindupFrom Recursively finds a file by walking up parents in the file tree
+// starting from a specific directory.
+func FindupFrom(name, dir string) (string, error) {
+ return findupFrom(name, dir)
+}
diff --git a/cli/internal/turbopath/relative_system_path.go b/cli/internal/turbopath/relative_system_path.go
new file mode 100644
index 0000000..d6115db
--- /dev/null
+++ b/cli/internal/turbopath/relative_system_path.go
@@ -0,0 +1,44 @@
+package turbopath
+
+import (
+ "fmt"
+ "path/filepath"
+)
+
+// RelativeSystemPath is a relative path using system separators.
+type RelativeSystemPath string
+
+// CheckedToRelativeSystemPath inspects a string and determines if it is a relative path.
+func CheckedToRelativeSystemPath(s string) (RelativeSystemPath, error) {
+ if filepath.IsAbs(s) {
+ return "", fmt.Errorf("%v is not a relative path", s)
+ }
+ return RelativeSystemPath(filepath.Clean(s)), nil
+}
+
+// MakeRelativeSystemPath joins the given segments in a system-appropriate way
+func MakeRelativeSystemPath(segments ...string) RelativeSystemPath {
+ return RelativeSystemPath(filepath.Join(segments...))
+}
+
+// ToString returns a string represenation of this Path.
+// Used for interfacing with APIs that require a string.
+func (p RelativeSystemPath) ToString() string {
+ return string(p)
+}
+
+// ToSystemPath returns itself.
+func (p RelativeSystemPath) ToSystemPath() RelativeSystemPath {
+ return p
+}
+
+// ToUnixPath converts from RelativeSystemPath to RelativeUnixPath.
+func (p RelativeSystemPath) ToUnixPath() RelativeUnixPath {
+ return RelativeUnixPath(filepath.ToSlash(p.ToString()))
+}
+
+// Join appends relative path segments to this RelativeSystemPath.
+func (p RelativeSystemPath) Join(additional ...RelativeSystemPath) RelativeSystemPath {
+ cast := RelativeSystemPathArray(additional)
+ return RelativeSystemPath(filepath.Join(p.ToString(), filepath.Join(cast.ToStringArray()...)))
+}
diff --git a/cli/internal/turbopath/relative_unix_path.go b/cli/internal/turbopath/relative_unix_path.go
new file mode 100644
index 0000000..05829e2
--- /dev/null
+++ b/cli/internal/turbopath/relative_unix_path.go
@@ -0,0 +1,31 @@
+package turbopath
+
+import (
+ "path"
+ "path/filepath"
+)
+
+// RelativeUnixPath is a relative path using Unix `/` separators.
+type RelativeUnixPath string
+
+// ToString returns a string represenation of this Path.
+// Used for interfacing with APIs that require a string.
+func (p RelativeUnixPath) ToString() string {
+ return string(p)
+}
+
+// ToSystemPath converts a RelativeUnixPath to a RelativeSystemPath.
+func (p RelativeUnixPath) ToSystemPath() RelativeSystemPath {
+ return RelativeSystemPath(filepath.FromSlash(p.ToString()))
+}
+
+// ToUnixPath converts a RelativeUnixPath to a RelativeSystemPath.
+func (p RelativeUnixPath) ToUnixPath() RelativeUnixPath {
+ return p
+}
+
+// Join appends relative path segments to this RelativeUnixPath.
+func (p RelativeUnixPath) Join(additional ...RelativeUnixPath) RelativeUnixPath {
+ cast := RelativeUnixPathArray(additional)
+ return RelativeUnixPath(path.Join(p.ToString(), path.Join(cast.ToStringArray()...)))
+}
diff --git a/cli/internal/turbopath/turbopath.go b/cli/internal/turbopath/turbopath.go
new file mode 100644
index 0000000..f50b75f
--- /dev/null
+++ b/cli/internal/turbopath/turbopath.go
@@ -0,0 +1,112 @@
+// Package turbopath teaches the Go type system about six
+// different types of paths:
+// - AbsoluteSystemPath
+// - RelativeSystemPath
+// - AnchoredSystemPath
+// - AbsoluteUnixPath
+// - RelativeUnixPath
+// - AnchoredUnixPath
+//
+// Between these two things it is assumed that we will be able to
+// reasonably describe file paths being used within the system and
+// have the type system enforce correctness instead of relying upon
+// runtime code to accomplish the task.
+//
+// Absolute paths are, "absolute, including volume root." They are not
+// portable between System and Unix.
+//
+// Relative paths are simply arbitrary path segments using a particular
+// path delimiter. They are portable between System and Unix.
+//
+// Anchored paths are, "absolute, starting at a particular root."
+// They are not aware of *what* their anchor is. It could be a repository,
+// an `os.dirFS`, a package, `cwd`, or more. They are stored *without*
+// a preceding delimiter for compatibility with `io/fs`. They are portable
+// between System and Unix.
+//
+// In some future world everything in here can be optimized out at compile time.
+// Everything is either `string` or `[]string`
+//
+// Much of this is dreadfully repetitive because of intentional
+// limitations in the Go type system.
+package turbopath
+
+// AnchoredUnixPathArray is a type used to enable transform operations on arrays of paths.
+type AnchoredUnixPathArray []AnchoredUnixPath
+
+// RelativeSystemPathArray is a type used to enable transform operations on arrays of paths.
+type RelativeSystemPathArray []RelativeSystemPath
+
+// RelativeUnixPathArray is a type used to enable transform operations on arrays of paths.
+type RelativeUnixPathArray []RelativeUnixPath
+
+// ToStringArray enables ergonomic operations on arrays of RelativeSystemPath
+func (source RelativeSystemPathArray) ToStringArray() []string {
+ output := make([]string, len(source))
+ for index, path := range source {
+ output[index] = path.ToString()
+ }
+ return output
+}
+
+// ToStringArray enables ergonomic operations on arrays of RelativeUnixPath
+func (source RelativeUnixPathArray) ToStringArray() []string {
+ output := make([]string, len(source))
+ for index, path := range source {
+ output[index] = path.ToString()
+ }
+ return output
+}
+
+// ToSystemPathArray enables ergonomic operations on arrays of AnchoredUnixPath
+func (source AnchoredUnixPathArray) ToSystemPathArray() []AnchoredSystemPath {
+ output := make([]AnchoredSystemPath, len(source))
+ for index, path := range source {
+ output[index] = path.ToSystemPath()
+ }
+ return output
+}
+
+// The following methods exist to import a path string and cast it to the appropriate
+// type. They exist to communicate intent and make it explicit that this is an
+// intentional action, not a "helpful" insertion by the IDE.
+//
+// This is intended to map closely to the `unsafe` keyword, without the denotative
+// meaning of `unsafe` in English. These are "trust me, I've checkex it" places, and
+// intend to mark the places where we smuggle paths from outside the world of safe
+// path handling into the world where we carefully consider the path to ensure safety.
+
+// AbsoluteSystemPathFromUpstream takes a path string and casts it to an
+// AbsoluteSystemPath without checking. If the input to this function is
+// not an AbsoluteSystemPath it will result in downstream errors.
+func AbsoluteSystemPathFromUpstream(path string) AbsoluteSystemPath {
+ return AbsoluteSystemPath(path)
+}
+
+// AnchoredSystemPathFromUpstream takes a path string and casts it to an
+// AnchoredSystemPath without checking. If the input to this function is
+// not an AnchoredSystemPath it will result in downstream errors.
+func AnchoredSystemPathFromUpstream(path string) AnchoredSystemPath {
+ return AnchoredSystemPath(path)
+}
+
+// AnchoredUnixPathFromUpstream takes a path string and casts it to an
+// AnchoredUnixPath without checking. If the input to this function is
+// not an AnchoredUnixPath it will result in downstream errors.
+func AnchoredUnixPathFromUpstream(path string) AnchoredUnixPath {
+ return AnchoredUnixPath(path)
+}
+
+// RelativeSystemPathFromUpstream takes a path string and casts it to an
+// RelativeSystemPath without checking. If the input to this function is
+// not an RelativeSystemPath it will result in downstream errors.
+func RelativeSystemPathFromUpstream(path string) RelativeSystemPath {
+ return RelativeSystemPath(path)
+}
+
+// RelativeUnixPathFromUpstream takes a path string and casts it to an
+// RelativeUnixPath without checking. If the input to this function is
+// not an RelativeUnixPath it will result in downstream errors.
+func RelativeUnixPathFromUpstream(path string) RelativeUnixPath {
+ return RelativeUnixPath(path)
+}