123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- package os2
- import "base:runtime"
- import "core:strings"
- Path_Separator :: _Path_Separator // OS-Specific
- Path_Separator_String :: _Path_Separator_String // OS-Specific
- Path_List_Separator :: _Path_List_Separator // OS-Specific
- #assert(_Path_Separator <= rune(0x7F), "The system-specific path separator rune is expected to be within the 7-bit ASCII character set.")
- /*
- Return true if `c` is a character used to separate paths into directory and
- file hierarchies on the current system.
- */
- @(require_results)
- is_path_separator :: proc(c: byte) -> bool {
- return _is_path_separator(c)
- }
- mkdir :: make_directory
- /*
- Make a new directory.
- If `path` is relative, it will be relative to the process's current working directory.
- */
- make_directory :: proc(name: string, perm: int = 0o755) -> Error {
- return _mkdir(name, perm)
- }
- mkdir_all :: make_directory_all
- /*
- Make a new directory, creating new intervening directories when needed.
- If `path` is relative, it will be relative to the process's current working directory.
- */
- make_directory_all :: proc(path: string, perm: int = 0o755) -> Error {
- return _mkdir_all(path, perm)
- }
- /*
- Delete `path` and all files and directories inside of `path` if it is a directory.
- If `path` is relative, it will be relative to the process's current working directory.
- */
- remove_all :: proc(path: string) -> Error {
- return _remove_all(path)
- }
- getwd :: get_working_directory
- /*
- Get the working directory of the current process.
- *Allocates Using Provided Allocator*
- */
- @(require_results)
- get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
- return _get_working_directory(allocator)
- }
- setwd :: set_working_directory
- /*
- Change the working directory of the current process.
- *Allocates Using Provided Allocator*
- */
- set_working_directory :: proc(dir: string) -> (err: Error) {
- return _set_working_directory(dir)
- }
- /*
- Get the path for the currently running executable.
- *Allocates Using Provided Allocator*
- */
- @(require_results)
- get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
- return _get_executable_path(allocator)
- }
- /*
- Get the directory for the currently running executable.
- *Allocates Using Provided Allocator*
- */
- @(require_results)
- get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
- path = _get_executable_path(allocator) or_return
- path, _ = split_path(path)
- return
- }
- /*
- Compare two paths for exactness without normalization.
- This procedure takes into account case-sensitivity on differing systems.
- */
- @(require_results)
- are_paths_identical :: proc(a, b: string) -> (identical: bool) {
- return _are_paths_identical(a, b)
- }
- /*
- Normalize a path.
- *Allocates Using Provided Allocator*
- This will remove duplicate separators and unneeded references to the current or
- parent directory.
- */
- @(require_results)
- clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) {
- if path == "" || path == "." {
- return strings.clone(".", allocator)
- }
- temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- // The extra byte is to simplify appending path elements by letting the
- // loop to end each with a separator. We'll trim the last one when we're done.
- buffer := make([]u8, len(path) + 1, temp_allocator) or_return
- // This is the only point where Windows and POSIX differ, as Windows has
- // alphabet-based volumes for root paths.
- rooted, start := _clean_path_handle_start(path, buffer)
- head, buffer_i := start, start
- for i, j := start, start; i <= len(path); i += 1 {
- if i == len(path) || _is_path_separator(path[i]) {
- elem := path[j:i]
- j = i + 1
- switch elem {
- case "", ".":
- // Skip duplicate path separators and current directory references.
- case "..":
- if !rooted && buffer_i == head {
- // Only allow accessing further parent directories when the path is relative.
- buffer[buffer_i] = '.'
- buffer[buffer_i+1] = '.'
- buffer[buffer_i+2] = _Path_Separator
- buffer_i += 3
- head = buffer_i
- } else {
- // Roll back to the last separator or the head of the buffer.
- back_to := head
- // `buffer_i` will be equal to 1 + the last set byte, so
- // skipping two bytes avoids the final separator we just
- // added.
- for k := buffer_i-2; k >= head; k -= 1 {
- if _is_path_separator(buffer[k]) {
- back_to = k + 1
- break
- }
- }
- buffer_i = back_to
- }
- case:
- // Copy the path element verbatim and add a separator.
- copy(buffer[buffer_i:], elem)
- buffer_i += len(elem)
- buffer[buffer_i] = _Path_Separator
- buffer_i += 1
- }
- }
- }
- // Trim the final separator.
- // NOTE: No need to check if the last byte is a separator, as we always add it.
- if buffer_i > start {
- buffer_i -= 1
- }
- if buffer_i == 0 {
- return strings.clone(".", allocator)
- }
- compact := make([]u8, buffer_i, allocator) or_return
- copy(compact, buffer) // NOTE(bill): buffer[:buffer_i] is redundant here
- return string(compact), nil
- }
- /*
- Return true if `path` is an absolute path as opposed to a relative one.
- */
- @(require_results)
- is_absolute_path :: proc(path: string) -> bool {
- return _is_absolute_path(path)
- }
- /*
- Get the absolute path to `path` with respect to the process's current directory.
- *Allocates Using Provided Allocator*
- */
- @(require_results)
- get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
- return _get_absolute_path(path, allocator)
- }
- /*
- Get the relative path needed to change directories from `base` to `target`.
- *Allocates Using Provided Allocator*
- The result is such that `join_path(base, get_relative_path(base, target))` is equivalent to `target`.
- NOTE: This procedure expects both `base` and `target` to be normalized first,
- which can be done by calling `clean_path` on them if needed.
- This procedure will return an `Invalid_Path` error if `base` begins with a
- reference to the parent directory (`".."`). Use `get_working_directory` with
- `join_path` to construct absolute paths for both arguments instead.
- */
- @(require_results)
- get_relative_path :: proc(base, target: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
- if _are_paths_identical(base, target) {
- return strings.clone(".", allocator)
- }
- if base == "." {
- return strings.clone(target, allocator)
- }
- // This is the first point where Windows and POSIX differ, as Windows has
- // alphabet-based volumes for root paths.
- if !_get_relative_path_handle_start(base, target) {
- return "", .Invalid_Path
- }
- if strings.has_prefix(base, "..") && (len(base) == 2 || _is_path_separator(base[2])) {
- // We could do the work for the user of getting absolute paths for both
- // arguments, but that could make something costly (repeatedly
- // normalizing paths) convenient, when it would be better for the user
- // to store already-finalized paths and operate on those instead.
- return "", .Invalid_Path
- }
- // This is the other point where Windows and POSIX differ, as Windows is
- // case-insensitive.
- common := _get_common_path_len(base, target)
- // Get the result of splitting `base` and `target` on _Path_Separator,
- // comparing them up to their most common elements, then count how many
- // unshared parts are in the split `base`.
- seps := 0
- size := 0
- if len(base)-common > 0 {
- seps = 1
- size = 2
- }
- // This range skips separators on the ends of the string.
- for i in common+1..<len(base)-1 {
- if _is_path_separator(base[i]) {
- seps += 1
- size += 3
- }
- }
- // Handle the rest of the size calculations.
- trailing := target[common:]
- if len(trailing) > 0 {
- // Account for leading separators on the target after cutting the common part.
- // (i.e. base == `/home`, target == `/home/a`)
- if _is_path_separator(trailing[0]) {
- trailing = trailing[1:]
- }
- size += len(trailing)
- if seps > 0 {
- size += 1
- }
- }
- if trailing == "." {
- trailing = ""
- size -= 2
- }
- // Build the string.
- buf := make([]u8, size, allocator) or_return
- n := 0
- if seps > 0 {
- buf[0] = '.'
- buf[1] = '.'
- n = 2
- }
- for _ in 1..<seps {
- buf[n] = _Path_Separator
- buf[n+1] = '.'
- buf[n+2] = '.'
- n += 3
- }
- if len(trailing) > 0 {
- if seps > 0 {
- buf[n] = _Path_Separator
- n += 1
- }
- copy(buf[n:], trailing)
- }
- path = string(buf)
- return
- }
- /*
- Split a path into a directory hierarchy and a filename.
- For example, `split_path("/home/foo/bar.tar.gz")` will return `"/home/foo"` and `"bar.tar.gz"`.
- */
- @(require_results)
- split_path :: proc(path: string) -> (dir, filename: string) {
- return _split_path(path)
- }
- /*
- Join all `elems` with the system's path separator and normalize the result.
- *Allocates Using Provided Allocator*
- For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`.
- */
- @(require_results)
- join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
- for e, i in elems {
- if e != "" {
- temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
- return clean_path(p, allocator)
- }
- }
- return "", nil
- }
- /*
- Split a filename from its extension.
- This procedure splits on the last separator.
- If the filename begins with a separator, such as `".readme.txt"`, the separator
- will be included in the filename, resulting in `".readme"` and `"txt"`.
- For example, `split_filename("foo.tar.gz")` will return `"foo.tar"` and `"gz"`.
- */
- @(require_results)
- split_filename :: proc(filename: string) -> (base, ext: string) {
- i := strings.last_index_byte(filename, '.')
- if i <= 0 {
- return filename, ""
- }
- return filename[:i], filename[i+1:]
- }
- /*
- Split a filename from its extension.
- This procedure splits on the first separator.
- If the filename begins with a separator, such as `".readme.txt.gz"`, the separator
- will be included in the filename, resulting in `".readme"` and `"txt.gz"`.
- For example, `split_filename_all("foo.tar.gz")` will return `"foo"` and `"tar.gz"`.
- */
- @(require_results)
- split_filename_all :: proc(filename: string) -> (base, ext: string) {
- i := strings.index_byte(filename, '.')
- if i == 0 {
- j := strings.index_byte(filename[1:], '.')
- if j != -1 {
- j += 1
- }
- i = j
- }
- if i == -1 {
- return filename, ""
- }
- return filename[:i], filename[i+1:]
- }
- /*
- Join `base` and `ext` with the system's filename extension separator.
- *Allocates Using Provided Allocator*
- For example, `join_filename("foo", "tar.gz")` will result in `"foo.tar.gz"`.
- */
- @(require_results)
- join_filename :: proc(base: string, ext: string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
- if len(base) == 0 {
- return strings.clone(ext, allocator)
- } else if len(ext) == 0 {
- return strings.clone(base, allocator)
- }
- buf := make([]u8, len(base) + 1 + len(ext), allocator) or_return
- copy(buf, base)
- buf[len(base)] = '.'
- copy(buf[1+len(base):], ext)
- return string(buf), nil
- }
- /*
- Split a string that is separated by a system-specific separator, typically used
- for environment variables specifying multiple directories.
- *Allocates Using Provided Allocator*
- For example, there is the "PATH" environment variable on POSIX systems which
- this procedure can split into separate entries.
- */
- @(require_results)
- split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []string, err: Error) {
- if path == "" {
- return nil, nil
- }
- start: int
- quote: bool
- start, quote = 0, false
- count := 0
- for i := 0; i < len(path); i += 1 {
- c := path[i]
- switch {
- case c == '"':
- quote = !quote
- case c == Path_List_Separator && !quote:
- count += 1
- }
- }
- start, quote = 0, false
- list = make([]string, count + 1, allocator) or_return
- index := 0
- for i := 0; i < len(path); i += 1 {
- c := path[i]
- switch {
- case c == '"':
- quote = !quote
- case c == Path_List_Separator && !quote:
- list[index] = path[start:i]
- index += 1
- start = i + 1
- }
- }
- assert(index == count)
- list[index] = path[start:]
- for s0, i in list {
- s, new := strings.replace_all(s0, `"`, ``, allocator)
- if !new {
- s = strings.clone(s, allocator) or_return
- }
- list[i] = s
- }
- return list, nil
- }
|