Source file src/pkg/path/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 path implements utility routines for manipulating slash-separated
6 // paths.
7 package path
8
9 import (
10 "strings"
11 )
12
13 // Clean returns the shortest path name equivalent to path
14 // by purely lexical processing. It applies the following rules
15 // iteratively until no further processing can be done:
16 //
17 // 1. Replace multiple slashes with a single slash.
18 // 2. Eliminate each . path name element (the current directory).
19 // 3. Eliminate each inner .. path name element (the parent directory)
20 // along with the non-.. element that precedes it.
21 // 4. Eliminate .. elements that begin a rooted path:
22 // that is, replace "/.." by "/" at the beginning of a path.
23 //
24 // The returned path ends in a slash only if it is the root "/".
25 //
26 // If the result of this process is an empty string, Clean
27 // returns the string ".".
28 //
29 // See also Rob Pike, ``Lexical File Names in Plan 9 or
30 // Getting Dot-Dot Right,''
31 // http://plan9.bell-labs.com/sys/doc/lexnames.html
32 func Clean(path string) string {
33 if path == "" {
34 return "."
35 }
36
37 rooted := path[0] == '/'
38 n := len(path)
39
40 // Invariants:
41 // reading from path; r is index of next byte to process.
42 // writing to buf; w is index of next byte to write.
43 // dotdot is index in buf where .. must stop, either because
44 // it is the leading slash or it is a leading ../../.. prefix.
45 buf := []byte(path)
46 r, w, dotdot := 0, 0, 0
47 if rooted {
48 r, w, dotdot = 1, 1, 1
49 }
50
51 for r < n {
52 switch {
53 case path[r] == '/':
54 // empty path element
55 r++
56 case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
57 // . element
58 r++
59 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
60 // .. element: remove to last /
61 r += 2
62 switch {
63 case w > dotdot:
64 // can backtrack
65 w--
66 for w > dotdot && buf[w] != '/' {
67 w--
68 }
69 case !rooted:
70 // cannot backtrack, but not rooted, so append .. element.
71 if w > 0 {
72 buf[w] = '/'
73 w++
74 }
75 buf[w] = '.'
76 w++
77 buf[w] = '.'
78 w++
79 dotdot = w
80 }
81 default:
82 // real path element.
83 // add slash if needed
84 if rooted && w != 1 || !rooted && w != 0 {
85 buf[w] = '/'
86 w++
87 }
88 // copy element
89 for ; r < n && path[r] != '/'; r++ {
90 buf[w] = path[r]
91 w++
92 }
93 }
94 }
95
96 // Turn empty string into "."
97 if w == 0 {
98 buf[w] = '.'
99 w++
100 }
101
102 return string(buf[0:w])
103 }
104
105 // Split splits path immediately following the final slash.
106 // separating it into a directory and file name component.
107 // If there is no slash path, Split returns an empty dir and
108 // file set to path.
109 // The returned values have the property that path = dir+file.
110 func Split(path string) (dir, file string) {
111 i := strings.LastIndex(path, "/")
112 return path[:i+1], path[i+1:]
113 }
114
115 // Join joins any number of path elements into a single path, adding a
116 // separating slash if necessary. The result is Cleaned; in particular,
117 // all empty strings are ignored.
118 func Join(elem ...string) string {
119 for i, e := range elem {
120 if e != "" {
121 return Clean(strings.Join(elem[i:], "/"))
122 }
123 }
124 return ""
125 }
126
127 // Ext returns the file name extension used by path.
128 // The extension is the suffix beginning at the final dot
129 // in the final slash-separated element of path;
130 // it is empty if there is no dot.
131 func Ext(path string) string {
132 for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
133 if path[i] == '.' {
134 return path[i:]
135 }
136 }
137 return ""
138 }
139
140 // Base returns the last element of path.
141 // Trailing slashes are removed before extracting the last element.
142 // If the path is empty, Base returns ".".
143 // If the path consists entirely of slashes, Base returns "/".
144 func Base(path string) string {
145 if path == "" {
146 return "."
147 }
148 // Strip trailing slashes.
149 for len(path) > 0 && path[len(path)-1] == '/' {
150 path = path[0 : len(path)-1]
151 }
152 // Find the last element
153 if i := strings.LastIndex(path, "/"); i >= 0 {
154 path = path[i+1:]
155 }
156 // If empty now, it had only slashes.
157 if path == "" {
158 return "/"
159 }
160 return path
161 }
162
163 // IsAbs returns true if the path is absolute.
164 func IsAbs(path string) bool {
165 return len(path) > 0 && path[0] == '/'
166 }
167
168 // Dir returns all but the last element of path, typically the path's directory.
169 // The path is Cleaned and trailing slashes are removed before processing.
170 // If the path is empty, Dir returns ".".
171 // If the path consists entirely of slashes followed by non-slash bytes, Dir
172 // returns a single slash. In any other case, the returned path does not end in a
173 // slash.
174 func Dir(path string) string {
175 dir, _ := Split(path)
176 dir = Clean(dir)
177 last := len(dir) - 1
178 if last > 0 && dir[last] == '/' {
179 dir = dir[:last]
180 }
181 if dir == "" {
182 dir = "."
183 }
184 return dir
185 }