aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/turbopath/absolute_system_path.go
blob: df65827c5cd97dad8495a580c905be6b550b1c52 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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])
}