path.go 5.3 KB

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