path.odin 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. package os2
  2. import "base:runtime"
  3. import "core:strings"
  4. Path_Separator :: _Path_Separator // OS-Specific
  5. Path_Separator_String :: _Path_Separator_String // OS-Specific
  6. Path_List_Separator :: _Path_List_Separator // OS-Specific
  7. #assert(_Path_Separator <= rune(0x7F), "The system-specific path separator rune is expected to be within the 7-bit ASCII character set.")
  8. /*
  9. Return true if `c` is a character used to separate paths into directory and
  10. file hierarchies on the current system.
  11. */
  12. @(require_results)
  13. is_path_separator :: proc(c: byte) -> bool {
  14. return _is_path_separator(c)
  15. }
  16. mkdir :: make_directory
  17. /*
  18. Make a new directory.
  19. If `path` is relative, it will be relative to the process's current working directory.
  20. */
  21. make_directory :: proc(name: string, perm: int = 0o755) -> Error {
  22. return _mkdir(name, perm)
  23. }
  24. mkdir_all :: make_directory_all
  25. /*
  26. Make a new directory, creating new intervening directories when needed.
  27. If `path` is relative, it will be relative to the process's current working directory.
  28. */
  29. make_directory_all :: proc(path: string, perm: int = 0o755) -> Error {
  30. return _mkdir_all(path, perm)
  31. }
  32. /*
  33. Delete `path` and all files and directories inside of `path` if it is a directory.
  34. If `path` is relative, it will be relative to the process's current working directory.
  35. */
  36. remove_all :: proc(path: string) -> Error {
  37. return _remove_all(path)
  38. }
  39. getwd :: get_working_directory
  40. /*
  41. Get the working directory of the current process.
  42. *Allocates Using Provided Allocator*
  43. */
  44. @(require_results)
  45. get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
  46. return _get_working_directory(allocator)
  47. }
  48. setwd :: set_working_directory
  49. /*
  50. Change the working directory of the current process.
  51. *Allocates Using Provided Allocator*
  52. */
  53. set_working_directory :: proc(dir: string) -> (err: Error) {
  54. return _set_working_directory(dir)
  55. }
  56. /*
  57. Get the path for the currently running executable.
  58. *Allocates Using Provided Allocator*
  59. */
  60. @(require_results)
  61. get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
  62. return _get_executable_path(allocator)
  63. }
  64. /*
  65. Get the directory for the currently running executable.
  66. *Allocates Using Provided Allocator*
  67. */
  68. @(require_results)
  69. get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
  70. path = _get_executable_path(allocator) or_return
  71. path, _ = split_path(path)
  72. return
  73. }
  74. /*
  75. Compare two paths for exactness without normalization.
  76. This procedure takes into account case-sensitivity on differing systems.
  77. */
  78. @(require_results)
  79. are_paths_identical :: proc(a, b: string) -> (identical: bool) {
  80. return _are_paths_identical(a, b)
  81. }
  82. /*
  83. Normalize a path.
  84. *Allocates Using Provided Allocator*
  85. This will remove duplicate separators and unneeded references to the current or
  86. parent directory.
  87. */
  88. @(require_results)
  89. clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) {
  90. if path == "" || path == "." {
  91. return strings.clone(".", allocator)
  92. }
  93. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  94. // The extra byte is to simplify appending path elements by letting the
  95. // loop to end each with a separator. We'll trim the last one when we're done.
  96. buffer := make([]u8, len(path) + 1, temp_allocator) or_return
  97. // This is the only point where Windows and POSIX differ, as Windows has
  98. // alphabet-based volumes for root paths.
  99. rooted, start := _clean_path_handle_start(path, buffer)
  100. head, buffer_i := start, start
  101. for i, j := start, start; i <= len(path); i += 1 {
  102. if i == len(path) || _is_path_separator(path[i]) {
  103. elem := path[j:i]
  104. j = i + 1
  105. switch elem {
  106. case "", ".":
  107. // Skip duplicate path separators and current directory references.
  108. case "..":
  109. if !rooted && buffer_i == head {
  110. // Only allow accessing further parent directories when the path is relative.
  111. buffer[buffer_i] = '.'
  112. buffer[buffer_i+1] = '.'
  113. buffer[buffer_i+2] = _Path_Separator
  114. buffer_i += 3
  115. head = buffer_i
  116. } else {
  117. // Roll back to the last separator or the head of the buffer.
  118. back_to := head
  119. // `buffer_i` will be equal to 1 + the last set byte, so
  120. // skipping two bytes avoids the final separator we just
  121. // added.
  122. for k := buffer_i-2; k >= head; k -= 1 {
  123. if _is_path_separator(buffer[k]) {
  124. back_to = k + 1
  125. break
  126. }
  127. }
  128. buffer_i = back_to
  129. }
  130. case:
  131. // Copy the path element verbatim and add a separator.
  132. copy(buffer[buffer_i:], elem)
  133. buffer_i += len(elem)
  134. buffer[buffer_i] = _Path_Separator
  135. buffer_i += 1
  136. }
  137. }
  138. }
  139. // Trim the final separator.
  140. // NOTE: No need to check if the last byte is a separator, as we always add it.
  141. if buffer_i > start {
  142. buffer_i -= 1
  143. }
  144. if buffer_i == 0 {
  145. return strings.clone(".", allocator)
  146. }
  147. compact := make([]u8, buffer_i, allocator) or_return
  148. copy(compact, buffer) // NOTE(bill): buffer[:buffer_i] is redundant here
  149. return string(compact), nil
  150. }
  151. /*
  152. Return true if `path` is an absolute path as opposed to a relative one.
  153. */
  154. @(require_results)
  155. is_absolute_path :: proc(path: string) -> bool {
  156. return _is_absolute_path(path)
  157. }
  158. /*
  159. Get the absolute path to `path` with respect to the process's current directory.
  160. *Allocates Using Provided Allocator*
  161. */
  162. @(require_results)
  163. get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
  164. return _get_absolute_path(path, allocator)
  165. }
  166. /*
  167. Get the relative path needed to change directories from `base` to `target`.
  168. *Allocates Using Provided Allocator*
  169. The result is such that `join_path(base, get_relative_path(base, target))` is equivalent to `target`.
  170. NOTE: This procedure expects both `base` and `target` to be normalized first,
  171. which can be done by calling `clean_path` on them if needed.
  172. This procedure will return an `Invalid_Path` error if `base` begins with a
  173. reference to the parent directory (`".."`). Use `get_working_directory` with
  174. `join_path` to construct absolute paths for both arguments instead.
  175. */
  176. @(require_results)
  177. get_relative_path :: proc(base, target: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
  178. if _are_paths_identical(base, target) {
  179. return strings.clone(".", allocator)
  180. }
  181. if base == "." {
  182. return strings.clone(target, allocator)
  183. }
  184. // This is the first point where Windows and POSIX differ, as Windows has
  185. // alphabet-based volumes for root paths.
  186. if !_get_relative_path_handle_start(base, target) {
  187. return "", .Invalid_Path
  188. }
  189. if strings.has_prefix(base, "..") && (len(base) == 2 || _is_path_separator(base[2])) {
  190. // We could do the work for the user of getting absolute paths for both
  191. // arguments, but that could make something costly (repeatedly
  192. // normalizing paths) convenient, when it would be better for the user
  193. // to store already-finalized paths and operate on those instead.
  194. return "", .Invalid_Path
  195. }
  196. // This is the other point where Windows and POSIX differ, as Windows is
  197. // case-insensitive.
  198. common := _get_common_path_len(base, target)
  199. // Get the result of splitting `base` and `target` on _Path_Separator,
  200. // comparing them up to their most common elements, then count how many
  201. // unshared parts are in the split `base`.
  202. seps := 0
  203. size := 0
  204. if len(base)-common > 0 {
  205. seps = 1
  206. size = 2
  207. }
  208. // This range skips separators on the ends of the string.
  209. for i in common+1..<len(base)-1 {
  210. if _is_path_separator(base[i]) {
  211. seps += 1
  212. size += 3
  213. }
  214. }
  215. // Handle the rest of the size calculations.
  216. trailing := target[common:]
  217. if len(trailing) > 0 {
  218. // Account for leading separators on the target after cutting the common part.
  219. // (i.e. base == `/home`, target == `/home/a`)
  220. if _is_path_separator(trailing[0]) {
  221. trailing = trailing[1:]
  222. }
  223. size += len(trailing)
  224. if seps > 0 {
  225. size += 1
  226. }
  227. }
  228. if trailing == "." {
  229. trailing = ""
  230. size -= 2
  231. }
  232. // Build the string.
  233. buf := make([]u8, size, allocator) or_return
  234. n := 0
  235. if seps > 0 {
  236. buf[0] = '.'
  237. buf[1] = '.'
  238. n = 2
  239. }
  240. for _ in 1..<seps {
  241. buf[n] = _Path_Separator
  242. buf[n+1] = '.'
  243. buf[n+2] = '.'
  244. n += 3
  245. }
  246. if len(trailing) > 0 {
  247. if seps > 0 {
  248. buf[n] = _Path_Separator
  249. n += 1
  250. }
  251. copy(buf[n:], trailing)
  252. }
  253. path = string(buf)
  254. return
  255. }
  256. /*
  257. Split a path into a directory hierarchy and a filename.
  258. For example, `split_path("/home/foo/bar.tar.gz")` will return `"/home/foo"` and `"bar.tar.gz"`.
  259. */
  260. @(require_results)
  261. split_path :: proc(path: string) -> (dir, filename: string) {
  262. return _split_path(path)
  263. }
  264. /*
  265. Join all `elems` with the system's path separator and normalize the result.
  266. *Allocates Using Provided Allocator*
  267. For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`.
  268. */
  269. @(require_results)
  270. join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
  271. for e, i in elems {
  272. if e != "" {
  273. temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
  274. p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
  275. return clean_path(p, allocator)
  276. }
  277. }
  278. return "", nil
  279. }
  280. /*
  281. Split a filename from its extension.
  282. This procedure splits on the last separator.
  283. If the filename begins with a separator, such as `".readme.txt"`, the separator
  284. will be included in the filename, resulting in `".readme"` and `"txt"`.
  285. For example, `split_filename("foo.tar.gz")` will return `"foo.tar"` and `"gz"`.
  286. */
  287. @(require_results)
  288. split_filename :: proc(filename: string) -> (base, ext: string) {
  289. i := strings.last_index_byte(filename, '.')
  290. if i <= 0 {
  291. return filename, ""
  292. }
  293. return filename[:i], filename[i+1:]
  294. }
  295. /*
  296. Split a filename from its extension.
  297. This procedure splits on the first separator.
  298. If the filename begins with a separator, such as `".readme.txt.gz"`, the separator
  299. will be included in the filename, resulting in `".readme"` and `"txt.gz"`.
  300. For example, `split_filename_all("foo.tar.gz")` will return `"foo"` and `"tar.gz"`.
  301. */
  302. @(require_results)
  303. split_filename_all :: proc(filename: string) -> (base, ext: string) {
  304. i := strings.index_byte(filename, '.')
  305. if i == 0 {
  306. j := strings.index_byte(filename[1:], '.')
  307. if j != -1 {
  308. j += 1
  309. }
  310. i = j
  311. }
  312. if i == -1 {
  313. return filename, ""
  314. }
  315. return filename[:i], filename[i+1:]
  316. }
  317. /*
  318. Join `base` and `ext` with the system's filename extension separator.
  319. *Allocates Using Provided Allocator*
  320. For example, `join_filename("foo", "tar.gz")` will result in `"foo.tar.gz"`.
  321. */
  322. @(require_results)
  323. join_filename :: proc(base: string, ext: string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
  324. if len(base) == 0 {
  325. return strings.clone(ext, allocator)
  326. } else if len(ext) == 0 {
  327. return strings.clone(base, allocator)
  328. }
  329. buf := make([]u8, len(base) + 1 + len(ext), allocator) or_return
  330. copy(buf, base)
  331. buf[len(base)] = '.'
  332. copy(buf[1+len(base):], ext)
  333. return string(buf), nil
  334. }
  335. /*
  336. Split a string that is separated by a system-specific separator, typically used
  337. for environment variables specifying multiple directories.
  338. *Allocates Using Provided Allocator*
  339. For example, there is the "PATH" environment variable on POSIX systems which
  340. this procedure can split into separate entries.
  341. */
  342. @(require_results)
  343. split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []string, err: Error) {
  344. if path == "" {
  345. return nil, nil
  346. }
  347. start: int
  348. quote: bool
  349. start, quote = 0, false
  350. count := 0
  351. for i := 0; i < len(path); i += 1 {
  352. c := path[i]
  353. switch {
  354. case c == '"':
  355. quote = !quote
  356. case c == Path_List_Separator && !quote:
  357. count += 1
  358. }
  359. }
  360. start, quote = 0, false
  361. list = make([]string, count + 1, allocator) or_return
  362. index := 0
  363. for i := 0; i < len(path); i += 1 {
  364. c := path[i]
  365. switch {
  366. case c == '"':
  367. quote = !quote
  368. case c == Path_List_Separator && !quote:
  369. list[index] = path[start:i]
  370. index += 1
  371. start = i + 1
  372. }
  373. }
  374. assert(index == count)
  375. list[index] = path[start:]
  376. for s0, i in list {
  377. s, new := strings.replace_all(s0, `"`, ``, allocator)
  378. if !new {
  379. s = strings.clone(s, allocator) or_return
  380. }
  381. list[i] = s
  382. }
  383. return list, nil
  384. }