path.odin 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. package path
  2. import "core:strings"
  3. import "core:unicode/utf8"
  4. // returns everything preceding the last path element
  5. dir :: proc(path: string, new := false, allocator := context.allocator) -> string {
  6. if path == "" do return "";
  7. for i := len(path) - 1; i >= 0; i -= 1 {
  8. if path[i] == '/' || path[i] == '\\' {
  9. if path[:i] == "" {
  10. // path is root
  11. return new ? strings.clone(SEPARATOR_STRING, allocator) : SEPARATOR_STRING;
  12. } else {
  13. return new ? strings.clone(path[:i], allocator) : path[:i];
  14. }
  15. }
  16. }
  17. // path doesn't contain any folder structure
  18. return "";
  19. }
  20. // returns the final path element
  21. base :: proc(path: string, new := false, allocator := context.allocator) -> string {
  22. if path == "" do return "";
  23. end := len(path) - 1;
  24. for i := end; i >= 0; i -= 1 {
  25. switch path[i] {
  26. case '/', '\\':
  27. if i != end {
  28. return new ? strings.clone(path[i+1:], allocator) : path[i+1:];
  29. } else {
  30. end = i; // we don't want trailing slashes
  31. }
  32. }
  33. }
  34. // path doesn't contain any folder structure, return entire path
  35. return new ? strings.clone(path, allocator) : path;
  36. }
  37. // returns the final path element, excluding the file extension if there is one
  38. name :: proc(path: string, new := false, allocator := context.allocator) -> string {
  39. if path == "" do return "";
  40. end := len(path) - 1;
  41. dot := end;
  42. for i := end; i >= 0; i -= 1 {
  43. switch path[i] {
  44. case '.': dot = (dot == end ? i : dot);
  45. case '/', '\\': return new ? strings.clone(path[i+1:dot], allocator) : path[i+1:dot];
  46. }
  47. }
  48. // path doesn't contain any folder structure or file extensions; assumed to be a valid file name
  49. return new ? strings.clone(path, allocator) : path;
  50. }
  51. // returns the file extension, if there is one
  52. ext :: proc(path: string, new := false, allocator := context.allocator) -> string {
  53. if path == "" do return "";
  54. for i := len(path)-1; i >= 0; i -= 1 {
  55. switch path[i] {
  56. case '/', '\\': return "";
  57. case '.': return new ? strings.clone(path[i+1:], allocator) : path[i+1:];
  58. }
  59. }
  60. // path does not include a file extension
  61. return "";
  62. }
  63. rel :: proc{rel_between, rel_current};
  64. // returns the relative path from one path to another
  65. rel_between :: proc(from, to: string, allocator := context.allocator) -> string {
  66. if from == "" || to == "" do return "";
  67. from, to := from, to;
  68. from = full(from, context.temp_allocator);
  69. to = full(to, context.temp_allocator);
  70. from_is_dir := is_dir(from);
  71. to_is_dir := is_dir(to);
  72. index, slash := 0, 0;
  73. for {
  74. if index >= len(from) {
  75. if index >= len(to) || (from_is_dir && index < len(to) && (to[index] == '/' || to[index] == '\\')) {
  76. slash = index;
  77. }
  78. break;
  79. }
  80. else if index >= len(to) {
  81. if index >= len(from) || (to_is_dir && index < len(from) && (from[index] == '/' || from[index] == '\\')) {
  82. slash = index;
  83. }
  84. break;
  85. }
  86. lchar, skip := utf8.decode_rune_in_string(from[index:]);
  87. rchar, _ := utf8.decode_rune_in_string(to[index:]);
  88. if (lchar == '/' || lchar == '\\') && (rchar == '/' || lchar == '\\') {
  89. slash = index;
  90. }
  91. else if lchar != rchar {
  92. break;
  93. }
  94. index += skip;
  95. }
  96. if slash < 1 {
  97. // there is no common path, use the absolute `to` path
  98. return strings.clone(to, allocator);
  99. }
  100. from_slashes, to_slashes := 0, 0;
  101. if slash < len(from) {
  102. from = from[slash+1:];
  103. if from_is_dir {
  104. from_slashes += 1;
  105. }
  106. }
  107. else {
  108. from = "";
  109. }
  110. if slash < len(to) {
  111. to = to[slash+1:];
  112. if to_is_dir {
  113. to_slashes += 1;
  114. }
  115. }
  116. else {
  117. to = "";
  118. }
  119. for char in from {
  120. if char == '/' || char == '\\' {
  121. from_slashes += 1;
  122. }
  123. }
  124. for char in to {
  125. if char == '/' || char == '\\' {
  126. to_slashes += 1;
  127. }
  128. }
  129. if from_slashes == 0 {
  130. buffer := make([]byte, 2 + len(to), allocator);
  131. buffer[0] = '.';
  132. buffer[1] = SEPARATOR;
  133. copy(buffer[2:], to);
  134. return string(buffer);
  135. }
  136. else {
  137. buffer := make([]byte, from_slashes*3 + len(to), allocator);
  138. for i in 0..<from_slashes {
  139. buffer[i*3+0] = '.';
  140. buffer[i*3+1] = '.';
  141. buffer[i*3+2] = SEPARATOR;
  142. }
  143. copy(buffer[from_slashes*3:], to);
  144. return string(buffer);
  145. }
  146. return "";
  147. }
  148. // returns the relative path from the current directory to another path
  149. rel_current :: proc(to: string, allocator := context.allocator) -> string {
  150. return rel_between(current(context.allocator), to, allocator);
  151. }
  152. // splits the path elements into slices of the original path string
  153. split :: proc(s: string, allocator := context.allocator) -> []string {
  154. return strings.split_multi(s, []string{"\\", "/"}, true, allocator);
  155. }