path.odin 6.0 KB

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