path.odin 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // The slashpath package is only to be used for paths separated by forward slashes,
  2. // e.g. paths in URLs
  3. //
  4. // This package does not deal with Windows/NT paths with volume letters or backslashes
  5. // To manipulate operating system specific paths, use the path/filepath package
  6. package slashpath
  7. import "core:runtime"
  8. import "core:strings"
  9. // is_separator checks whether the byte is a valid separator character
  10. is_separator :: proc(c: byte) -> bool {
  11. return c == '/'
  12. }
  13. // is_abs checks whether the path is absolute
  14. is_abs :: proc(path: string) -> bool {
  15. return len(path) > 0 && path[0] == '/'
  16. }
  17. // base returns the last element of path
  18. // Trailing slashes are removed
  19. // If the path is empty, it returns ".".
  20. // If the path is all slashes, it returns "/"
  21. base :: proc(path: string, new := false, allocator := context.allocator) -> (last_element: string) {
  22. defer if new {
  23. last_element = strings.clone(last_element, allocator)
  24. }
  25. if path == "" {
  26. last_element = "."
  27. return
  28. }
  29. path := path
  30. for len(path) > 0 && is_separator(path[len(path)-1]) {
  31. path = path[:len(path)-1]
  32. }
  33. if i := strings.last_index(path, "/"); i >= 0 {
  34. path = path[i+1:]
  35. }
  36. if path == "" {
  37. last_element = "/"
  38. } else {
  39. last_element = path
  40. }
  41. return
  42. }
  43. // dir returns all but the last element of path, typically the path's directory.
  44. // After dropping the final element using it, the path is cleaned and trailing slashes are removed
  45. // If the path is empty, it returns "."
  46. // If the path consists entirely of slashes followed by non-slash bytes, it returns a single slash
  47. // In any other case, the returned path does not end in a slash
  48. dir :: proc(path: string, allocator := context.allocator) -> string {
  49. directory, _ := split(path)
  50. return clean(directory, allocator)
  51. }
  52. // split splits path immediately following the last slash,
  53. // separating it into a directory and file name component.
  54. // If there is no slash in path, it returns an empty dir and file set to path
  55. // The returned values have the property that path = dir+file
  56. split :: proc(path: string) -> (dir, file: string) {
  57. i := strings.last_index(path, "/")
  58. return path[:i+1], path[i+1:]
  59. }
  60. // split_elements splits the path elements into slices of the original path string
  61. split_elements :: proc(path: string, allocator := context.allocator) -> []string {
  62. return strings.split(path, "/", allocator)
  63. }
  64. // clean returns the shortest path name equivalent to path through lexical analysis only
  65. // It applies the following rules iterative until done:
  66. //
  67. // 1) replace multiple slashes with one
  68. // 2) remove each . path name element
  69. // 3) remove inner .. path name element
  70. // 4) remove .. that begin a rooted path ("/.." becomes "/")
  71. //
  72. clean :: proc(path: string, allocator := context.allocator) -> string {
  73. context.allocator = allocator
  74. if path == "" {
  75. return strings.clone(".")
  76. }
  77. // NOTE(bill): do not use is_separator because window paths do not follow this convention
  78. rooted := path[0] == '/'
  79. n := len(path)
  80. out := &Lazy_Buffer{s = path}
  81. // Check for ../../.. prefixes
  82. r, dot_dot := 0, 0
  83. if rooted {
  84. lazy_buffer_append(out, '/')
  85. r, dot_dot = 1, 1
  86. }
  87. for r < n {
  88. switch {
  89. case is_separator(path[r]):
  90. r += 1
  91. case path[r] == '.' && (r+1 == n || is_separator(path[r+1])):
  92. r += 1
  93. case path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_separator(path[r+2])):
  94. r += 2
  95. switch {
  96. case out.w > dot_dot:
  97. out.w -= 1
  98. for out.w > dot_dot && !is_separator(lazy_buffer_index(out, out.w)) {
  99. out.w -= 1
  100. }
  101. case !rooted:
  102. if out.w > 0 {
  103. lazy_buffer_append(out, '/')
  104. }
  105. lazy_buffer_append(out, '.')
  106. lazy_buffer_append(out, '.')
  107. dot_dot = out.w
  108. }
  109. case:
  110. if rooted && out.w != 1 || !rooted && out.w != 0 {
  111. lazy_buffer_append(out, '/')
  112. }
  113. for ; r < n && !is_separator(path[r]); r += 1 {
  114. lazy_buffer_append(out, path[r])
  115. }
  116. }
  117. }
  118. if out.w == 0 {
  119. delete(out.b)
  120. return strings.clone(".")
  121. }
  122. return lazy_buffer_string(out)
  123. }
  124. // join joins numerous path elements into a single path
  125. join :: proc(elems: []string, allocator := context.allocator) -> string {
  126. context.allocator = allocator
  127. for elem, i in elems {
  128. if elem != "" {
  129. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
  130. s := strings.join(elems[i:], "/", context.temp_allocator)
  131. return clean(s, allocator)
  132. }
  133. }
  134. return ""
  135. }
  136. // ext returns the file name extension used by "path"
  137. // The extension is the suffix beginning at the file fot in the last slash separated element of "path"
  138. // The path is empty if there is no dot
  139. ext :: proc(path: string, new := false, allocator := context.allocator) -> string {
  140. for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 {
  141. if path[i] == '.' {
  142. res := path[i:]
  143. if new {
  144. res = strings.clone(res, allocator)
  145. }
  146. return res
  147. }
  148. }
  149. return ""
  150. }
  151. // name returns the file without the base and without the extension
  152. name :: proc(path: string, new := false, allocator := context.allocator) -> (name: string) {
  153. _, file := split(path)
  154. name = file
  155. defer if new {
  156. name = strings.clone(name, allocator)
  157. }
  158. for i := len(file)-1; i >= 0 && !is_separator(file[i]); i -= 1 {
  159. if file[i] == '.' {
  160. name = file[:i]
  161. return
  162. }
  163. }
  164. return file
  165. }
  166. /*
  167. Lazy_Buffer is a lazily made path buffer
  168. When it does allocate, it uses the context.allocator
  169. */
  170. @(private)
  171. Lazy_Buffer :: struct {
  172. s: string,
  173. b: []byte,
  174. w: int, // write index
  175. }
  176. @(private)
  177. lazy_buffer_index :: proc(lb: ^Lazy_Buffer, i: int) -> byte {
  178. if lb.b != nil {
  179. return lb.b[i]
  180. }
  181. return lb.s[i]
  182. }
  183. @(private)
  184. lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) {
  185. if lb.b == nil {
  186. if lb.w < len(lb.s) && lb.s[lb.w] == c {
  187. lb.w += 1
  188. return
  189. }
  190. lb.b = make([]byte, len(lb.s))
  191. copy(lb.b, lb.s[:lb.w])
  192. }
  193. lb.b[lb.w] = c
  194. lb.w += 1
  195. }
  196. @(private)
  197. lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> string {
  198. if lb.b == nil {
  199. return strings.clone(lb.s[:lb.w])
  200. }
  201. return string(lb.b[:lb.w])
  202. }