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.
5 // The path package implements utility routines for manipulating
6 // slash-separated filename paths.
15 // Clean returns the shortest path name equivalent to path
16 // by purely lexical processing. It applies the following rules
17 // iteratively until no further processing can be done:
19 // 1. Replace multiple slashes with a single slash.
20 // 2. Eliminate each . path name element (the current directory).
21 // 3. Eliminate each inner .. path name element (the parent directory)
22 // along with the non-.. element that precedes it.
23 // 4. Eliminate .. elements that begin a rooted path:
24 // that is, replace "/.." by "/" at the beginning of a path.
26 // If the result of this process is an empty string, Clean
27 // returns the string ".".
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 {
37 rooted := path[0] == '/'
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.
46 r, w, dotdot := 0, 0, 0
48 r, w, dotdot = 1, 1, 1
56 case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
59 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
60 // .. element: remove to last /
66 for w > dotdot && buf[w] != '/' {
70 // cannot backtrack, but not rooted, so append .. element.
83 // add slash if needed
84 if rooted && w != 1 || !rooted && w != 0 {
89 for ; r < n && path[r] != '/'; r++ {
96 // Turn empty string into "."
102 return string(buf[0:w])
105 // Split splits path immediately following the final path separator,
106 // separating it into a directory and file name component.
107 // If there is no separator in path, Split returns an empty dir and
109 func Split(path string) (dir, file string) {
110 i := strings.LastIndexAny(path, PathSeps)
111 return path[:i+1], path[i+1:]
114 // Join joins any number of path elements into a single path, adding a
115 // separating slash if necessary. All empty strings are ignored.
116 func Join(elem ...string) string {
117 for i, e := range elem {
119 return Clean(strings.Join(elem[i:], "/"))
125 // Ext returns the file name extension used by path.
126 // The extension is the suffix beginning at the final dot
127 // in the final slash-separated element of path;
128 // it is empty if there is no dot.
129 func Ext(path string) string {
130 for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
138 // Visitor methods are invoked for corresponding file tree entries
139 // visited by Walk. The parameter path is the full path of f relative
141 type Visitor interface {
142 VisitDir(path string, f *os.FileInfo) bool
143 VisitFile(path string, f *os.FileInfo)
146 func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) {
147 if !f.IsDirectory() {
152 if !v.VisitDir(path, f) {
153 return // skip directory entries
156 list, err := ioutil.ReadDir(path)
163 for _, e := range list {
164 walk(Join(path, e.Name), e, v, errors)
168 // Walk walks the file tree rooted at root, calling v.VisitDir or
169 // v.VisitFile for each directory or file in the tree, including root.
170 // If v.VisitDir returns false, Walk skips the directory's entries;
171 // otherwise it invokes itself for each directory entry in sorted order.
172 // An error reading a directory does not abort the Walk.
173 // If errors != nil, Walk sends each directory read error
174 // to the channel. Otherwise Walk discards the error.
175 func Walk(root string, v Visitor, errors chan<- os.Error) {
176 f, err := os.Lstat(root)
181 return // can't progress
183 walk(root, f, v, errors)
186 // Base returns the last path element of the slash-separated name.
187 // Trailing slashes are removed before extracting the last element. If the name is
188 // empty, "." is returned. If it consists entirely of slashes, "/" is returned.
189 func Base(name string) string {
193 // Strip trailing slashes.
194 for len(name) > 0 && name[len(name)-1] == '/' {
195 name = name[0 : len(name)-1]
197 // Find the last element
198 if i := strings.LastIndex(name, "/"); i >= 0 {
201 // If empty now, it had only slashes.
208 // IsAbs returns true if the path is absolute.
209 func IsAbs(path string) bool {
210 // TODO: Add Windows support
211 return strings.HasPrefix(path, "/")