Source file src/pkg/path/filepath/path.go
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package filepath implements utility routines for manipulating filename paths
6 // in a way compatible with the target operating system-defined file paths.
7 package filepath
8
9 import (
10 "errors"
11 "os"
12 "sort"
13 "strings"
14 )
15
16 const (
17 Separator = os.PathSeparator
18 ListSeparator = os.PathListSeparator
19 )
20
21 // Clean returns the shortest path name equivalent to path
22 // by purely lexical processing. It applies the following rules
23 // iteratively until no further processing can be done:
24 //
25 // 1. Replace multiple Separator elements with a single one.
26 // 2. Eliminate each . path name element (the current directory).
27 // 3. Eliminate each inner .. path name element (the parent directory)
28 // along with the non-.. element that precedes it.
29 // 4. Eliminate .. elements that begin a rooted path:
30 // that is, replace "/.." by "/" at the beginning of a path,
31 // assuming Separator is '/'.
32 //
33 // The returned path ends in a slash only if it represents a root directory,
34 // such as "/" on Unix or `C:\` on Windows.
35 //
36 // If the result of this process is an empty string, Clean
37 // returns the string ".".
38 //
39 // See also Rob Pike, ``Lexical File Names in Plan 9 or
40 // Getting Dot-Dot Right,''
41 // http://plan9.bell-labs.com/sys/doc/lexnames.html
42 func Clean(path string) string {
43 vol := VolumeName(path)
44 path = path[len(vol):]
45 if path == "" {
46 if len(vol) > 1 && vol[1] != ':' {
47 // should be UNC
48 return FromSlash(vol)
49 }
50 return vol + "."
51 }
52 rooted := os.IsPathSeparator(path[0])
53
54 // Invariants:
55 // reading from path; r is index of next byte to process.
56 // writing to buf; w is index of next byte to write.
57 // dotdot is index in buf where .. must stop, either because
58 // it is the leading slash or it is a leading ../../.. prefix.
59 n := len(path)
60 buf := []byte(path)
61 r, w, dotdot := 0, 0, 0
62 if rooted {
63 buf[0] = Separator
64 r, w, dotdot = 1, 1, 1
65 }
66
67 for r < n {
68 switch {
69 case os.IsPathSeparator(path[r]):
70 // empty path element
71 r++
72 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
73 // . element
74 r++
75 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
76 // .. element: remove to last separator
77 r += 2
78 switch {
79 case w > dotdot:
80 // can backtrack
81 w--
82 for w > dotdot && !os.IsPathSeparator(buf[w]) {
83 w--
84 }
85 case !rooted:
86 // cannot backtrack, but not rooted, so append .. element.
87 if w > 0 {
88 buf[w] = Separator
89 w++
90 }
91 buf[w] = '.'
92 w++
93 buf[w] = '.'
94 w++
95 dotdot = w
96 }
97 default:
98 // real path element.
99 // add slash if needed
100 if rooted && w != 1 || !rooted && w != 0 {
101 buf[w] = Separator
102 w++
103 }
104 // copy element
105 for ; r < n && !os.IsPathSeparator(path[r]); r++ {
106 buf[w] = path[r]
107 w++
108 }
109 }
110 }
111
112 // Turn empty string into "."
113 if w == 0 {
114 buf[w] = '.'
115 w++
116 }
117
118 return FromSlash(vol + string(buf[0:w]))
119 }
120
121 // ToSlash returns the result of replacing each separator character
122 // in path with a slash ('/') character. Multiple separators are
123 // replaced by multiple slashes.
124 func ToSlash(path string) string {
125 if Separator == '/' {
126 return path
127 }
128 return strings.Replace(path, string(Separator), "/", -1)
129 }
130
131 // FromSlash returns the result of replacing each slash ('/') character
132 // in path with a separator character. Multiple slashes are replaced
133 // by multiple separators.
134 func FromSlash(path string) string {
135 if Separator == '/' {
136 return path
137 }
138 return strings.Replace(path, "/", string(Separator), -1)
139 }
140
141 // SplitList splits a list of paths joined by the OS-specific ListSeparator,
142 // usually found in PATH or GOPATH environment variables.
143 // Unlike strings.Split, SplitList returns an empty slice when passed an empty string.
144 func SplitList(path string) []string {
145 if path == "" {
146 return []string{}
147 }
148 return strings.Split(path, string(ListSeparator))
149 }
150
151 // Split splits path immediately following the final Separator,
152 // separating it into a directory and file name component.
153 // If there is no Separator in path, Split returns an empty dir
154 // and file set to path.
155 // The returned values have the property that path = dir+file.
156 func Split(path string) (dir, file string) {
157 vol := VolumeName(path)
158 i := len(path) - 1
159 for i >= len(vol) && !os.IsPathSeparator(path[i]) {
160 i--
161 }
162 return path[:i+1], path[i+1:]
163 }
164
165 // Join joins any number of path elements into a single path, adding
166 // a Separator if necessary. The result is Cleaned, in particular
167 // all empty strings are ignored.
168 func Join(elem ...string) string {
169 for i, e := range elem {
170 if e != "" {
171 return Clean(strings.Join(elem[i:], string(Separator)))
172 }
173 }
174 return ""
175 }
176
177 // Ext returns the file name extension used by path.
178 // The extension is the suffix beginning at the final dot
179 // in the final element of path; it is empty if there is
180 // no dot.
181 func Ext(path string) string {
182 for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- {
183 if path[i] == '.' {
184 return path[i:]
185 }
186 }
187 return ""
188 }
189
190 // EvalSymlinks returns the path name after the evaluation of any symbolic
191 // links.
192 // If path is relative the result will be relative to the current directory,
193 // unless one of the components is an absolute symbolic link.
194 func EvalSymlinks(path string) (string, error) {
195 return evalSymlinks(path)
196 }
197
198 // Abs returns an absolute representation of path.
199 // If the path is not absolute it will be joined with the current
200 // working directory to turn it into an absolute path. The absolute
201 // path name for a given file is not guaranteed to be unique.
202 func Abs(path string) (string, error) {
203 if IsAbs(path) {
204 return Clean(path), nil
205 }
206 wd, err := os.Getwd()
207 if err != nil {
208 return "", err
209 }
210 return Join(wd, path), nil
211 }
212
213 // Rel returns a relative path that is lexically equivalent to targpath when
214 // joined to basepath with an intervening separator. That is,
215 // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
216 // On success, the returned path will always be relative to basepath,
217 // even if basepath and targpath share no elements.
218 // An error is returned if targpath can't be made relative to basepath or if
219 // knowing the current working directory would be necessary to compute it.
220 func Rel(basepath, targpath string) (string, error) {
221 baseVol := VolumeName(basepath)
222 targVol := VolumeName(targpath)
223 base := Clean(basepath)
224 targ := Clean(targpath)
225 if targ == base {
226 return ".", nil
227 }
228 base = base[len(baseVol):]
229 targ = targ[len(targVol):]
230 if base == "." {
231 base = ""
232 }
233 // Can't use IsAbs - `\a` and `a` are both relative in Windows.
234 baseSlashed := len(base) > 0 && base[0] == Separator
235 targSlashed := len(targ) > 0 && targ[0] == Separator
236 if baseSlashed != targSlashed || baseVol != targVol {
237 return "", errors.New("Rel: can't make " + targ + " relative to " + base)
238 }
239 // Position base[b0:bi] and targ[t0:ti] at the first differing elements.
240 bl := len(base)
241 tl := len(targ)
242 var b0, bi, t0, ti int
243 for {
244 for bi < bl && base[bi] != Separator {
245 bi++
246 }
247 for ti < tl && targ[ti] != Separator {
248 ti++
249 }
250 if targ[t0:ti] != base[b0:bi] {
251 break
252 }
253 if bi < bl {
254 bi++
255 }
256 if ti < tl {
257 ti++
258 }
259 b0 = bi
260 t0 = ti
261 }
262 if base[b0:bi] == ".." {
263 return "", errors.New("Rel: can't make " + targ + " relative to " + base)
264 }
265 if b0 != bl {
266 // Base elements left. Must go up before going down.
267 seps := strings.Count(base[b0:bl], string(Separator))
268 size := 2 + seps*3
269 if tl != t0 {
270 size += 1 + tl - t0
271 }
272 buf := make([]byte, size)
273 n := copy(buf, "..")
274 for i := 0; i < seps; i++ {
275 buf[n] = Separator
276 copy(buf[n+1:], "..")
277 n += 3
278 }
279 if t0 != tl {
280 buf[n] = Separator
281 copy(buf[n+1:], targ[t0:])
282 }
283 return string(buf), nil
284 }
285 return targ[t0:], nil
286 }
287
288 // SkipDir is used as a return value from WalkFuncs to indicate that
289 // the directory named in the call is to be skipped. It is not returned
290 // as an error by any function.
291 var SkipDir = errors.New("skip this directory")
292
293 // WalkFunc is the type of the function called for each file or directory
294 // visited by Walk. If there was a problem walking to the file or directory
295 // named by path, the incoming error will describe the problem and the
296 // function can decide how to handle that error (and Walk will not descend
297 // into that directory). If an error is returned, processing stops. The
298 // sole exception is that if path is a directory and the function returns the
299 // special value SkipDir, the contents of the directory are skipped
300 // and processing continues as usual on the next file.
301 type WalkFunc func(path string, info os.FileInfo, err error) error
302
303 // walk recursively descends path, calling w.
304 func walk(path string, info os.FileInfo, walkFn WalkFunc) error {
305 err := walkFn(path, info, nil)
306 if err != nil {
307 if info.IsDir() && err == SkipDir {
308 return nil
309 }
310 return err
311 }
312
313 if !info.IsDir() {
314 return nil
315 }
316
317 list, err := readDir(path)
318 if err != nil {
319 return walkFn(path, info, err)
320 }
321
322 for _, fileInfo := range list {
323 if err = walk(Join(path, fileInfo.Name()), fileInfo, walkFn); err != nil {
324 return err
325 }
326 }
327 return nil
328 }
329
330 // Walk walks the file tree rooted at root, calling walkFn for each file or
331 // directory in the tree, including root. All errors that arise visiting files
332 // and directories are filtered by walkFn. The files are walked in lexical
333 // order, which makes the output deterministic but means that for very
334 // large directories Walk can be inefficient.
335 func Walk(root string, walkFn WalkFunc) error {
336 info, err := os.Lstat(root)
337 if err != nil {
338 return walkFn(root, nil, err)
339 }
340 return walk(root, info, walkFn)
341 }
342
343 // readDir reads the directory named by dirname and returns
344 // a sorted list of directory entries.
345 // Copied from io/ioutil to avoid the circular import.
346 func readDir(dirname string) ([]os.FileInfo, error) {
347 f, err := os.Open(dirname)
348 if err != nil {
349 return nil, err
350 }
351 list, err := f.Readdir(-1)
352 f.Close()
353 if err != nil {
354 return nil, err
355 }
356 sort.Sort(byName(list))
357 return list, nil
358 }
359
360 // byName implements sort.Interface.
361 type byName []os.FileInfo
362
363 func (f byName) Len() int { return len(f) }
364 func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
365 func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
366
367 // Base returns the last element of path.
368 // Trailing path separators are removed before extracting the last element.
369 // If the path is empty, Base returns ".".
370 // If the path consists entirely of separators, Base returns a single separator.
371 func Base(path string) string {
372 if path == "" {
373 return "."
374 }
375 // Strip trailing slashes.
376 for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
377 path = path[0 : len(path)-1]
378 }
379 // Throw away volume name
380 path = path[len(VolumeName(path)):]
381 // Find the last element
382 i := len(path) - 1
383 for i >= 0 && !os.IsPathSeparator(path[i]) {
384 i--
385 }
386 if i >= 0 {
387 path = path[i+1:]
388 }
389 // If empty now, it had only slashes.
390 if path == "" {
391 return string(Separator)
392 }
393 return path
394 }
395
396 // Dir returns all but the last element of path, typically the path's directory.
397 // Trailing path separators are removed before processing.
398 // If the path is empty, Dir returns ".".
399 // If the path consists entirely of separators, Dir returns a single separator.
400 // The returned path does not end in a separator unless it is the root directory.
401 func Dir(path string) string {
402 vol := VolumeName(path)
403 i := len(path) - 1
404 for i >= len(vol) && !os.IsPathSeparator(path[i]) {
405 i--
406 }
407 dir := Clean(path[len(vol) : i+1])
408 last := len(dir) - 1
409 if last > 0 && os.IsPathSeparator(dir[last]) {
410 dir = dir[:last]
411 }
412 if dir == "" {
413 dir = "."
414 }
415 return vol + dir
416 }