mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-20 21:35:19 +00:00
Merge pull request #4954 from Feoramund/os2-path
Add new path API for `os2`
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
package os2
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
|
||||
import "core:path/filepath"
|
||||
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)
|
||||
@@ -15,22 +22,42 @@ is_path_separator :: proc(c: byte) -> bool {
|
||||
|
||||
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)
|
||||
@@ -38,16 +65,400 @@ get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err
|
||||
|
||||
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, _ = filepath.split(path)
|
||||
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_GUARD()
|
||||
|
||||
// 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.
|
||||
intrinsics.mem_copy_non_overlapping(raw_data(buffer[buffer_i:]), raw_data(elem), len(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
|
||||
intrinsics.mem_copy_non_overlapping(raw_data(compact), raw_data(buffer), buffer_i)
|
||||
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
|
||||
}
|
||||
runtime.mem_copy_non_overlapping(raw_data(buf[n:]), raw_data(trailing), len(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_GUARD()
|
||||
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) {
|
||||
len_base := len(base)
|
||||
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
|
||||
intrinsics.mem_copy_non_overlapping(raw_data(buf), raw_data(base), len_base)
|
||||
buf[len_base] = '.'
|
||||
intrinsics.mem_copy_non_overlapping(raw_data(buf[1+len_base:]), raw_data(ext), len(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
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ _Path_List_Separator :: ':'
|
||||
_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC}
|
||||
|
||||
_is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '/'
|
||||
return c == _Path_Separator
|
||||
}
|
||||
|
||||
_mkdir :: proc(path: string, perm: int) -> Error {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:path/filepath"
|
||||
|
||||
import "core:sys/posix"
|
||||
|
||||
@@ -35,11 +34,11 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
return .Exist
|
||||
}
|
||||
|
||||
clean_path := filepath.clean(path, temp_allocator())
|
||||
clean_path := clean_path(path, temp_allocator()) or_return
|
||||
return internal_mkdir_all(clean_path, perm)
|
||||
|
||||
internal_mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
dir, file := filepath.split(path)
|
||||
dir, file := split_path(path)
|
||||
if file != path && dir != "/" {
|
||||
if len(dir) > 1 && dir[len(dir) - 1] == '/' {
|
||||
dir = dir[:len(dir) - 1]
|
||||
|
||||
78
core/os/os2/path_posixfs.odin
Normal file
78
core/os/os2/path_posixfs.odin
Normal file
@@ -0,0 +1,78 @@
|
||||
#+private
|
||||
#+build linux, darwin, netbsd, freebsd, openbsd, wasi
|
||||
package os2
|
||||
|
||||
// This implementation is for all systems that have POSIX-compliant filesystem paths.
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:sys/posix"
|
||||
|
||||
_are_paths_identical :: proc(a, b: string) -> (identical: bool) {
|
||||
return a == b
|
||||
}
|
||||
|
||||
_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) {
|
||||
// Preserve rooted paths.
|
||||
if _is_path_separator(path[0]) {
|
||||
rooted = true
|
||||
buffer[0] = _Path_Separator
|
||||
start = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_is_absolute_path :: proc(path: string) -> bool {
|
||||
return len(path) > 0 && _is_path_separator(path[0])
|
||||
}
|
||||
|
||||
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
|
||||
rel := path
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
|
||||
path_ptr := posix.realpath(rel_cstr, nil)
|
||||
if path_ptr == nil {
|
||||
return "", Platform_Error(posix.errno())
|
||||
}
|
||||
defer posix.free(path_ptr)
|
||||
|
||||
path_str := strings.clone(string(path_ptr), allocator)
|
||||
return path_str, nil
|
||||
}
|
||||
|
||||
_get_relative_path_handle_start :: proc(base, target: string) -> bool {
|
||||
base_rooted := len(base) > 0 && _is_path_separator(base[0])
|
||||
target_rooted := len(target) > 0 && _is_path_separator(target[0])
|
||||
return base_rooted == target_rooted
|
||||
}
|
||||
|
||||
_get_common_path_len :: proc(base, target: string) -> int {
|
||||
i := 0
|
||||
end := min(len(base), len(target))
|
||||
for j in 0..=end {
|
||||
if j == end || _is_path_separator(base[j]) {
|
||||
if base[i:j] == target[i:j] {
|
||||
i = j
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
_split_path :: proc(path: string) -> (dir, file: string) {
|
||||
i := len(path) - 1
|
||||
for i >= 0 && !_is_path_separator(path[i]) {
|
||||
i -= 1
|
||||
}
|
||||
if i == 0 {
|
||||
return path[:i+1], path[i+1:]
|
||||
} else if i > 0 {
|
||||
return path[:i], path[i+1:]
|
||||
}
|
||||
return "", path
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:path/filepath"
|
||||
import "core:sync"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
@@ -35,11 +34,11 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
return .Exist
|
||||
}
|
||||
|
||||
clean_path := filepath.clean(path, temp_allocator())
|
||||
clean_path := clean_path(path, temp_allocator())
|
||||
return internal_mkdir_all(clean_path)
|
||||
|
||||
internal_mkdir_all :: proc(path: string) -> Error {
|
||||
dir, file := filepath.split(path)
|
||||
dir, file := split_path(path)
|
||||
if file != path && dir != "/" {
|
||||
if len(dir) > 1 && dir[len(dir) - 1] == '/' {
|
||||
dir = dir[:len(dir) - 1]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_Path_Separator :: '\\'
|
||||
_Path_Separator_String :: "\\"
|
||||
@@ -217,7 +219,7 @@ _fix_long_path_internal :: proc(path: string) -> string {
|
||||
return path
|
||||
}
|
||||
|
||||
if !_is_abs(path) { // relative path
|
||||
if !_is_absolute_path(path) { // relative path
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -257,3 +259,93 @@ _fix_long_path_internal :: proc(path: string) -> string {
|
||||
|
||||
return string(path_buf[:w])
|
||||
}
|
||||
|
||||
_are_paths_identical :: strings.equal_fold
|
||||
|
||||
_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) {
|
||||
// Preserve rooted paths.
|
||||
start = _volume_name_len(path)
|
||||
if start > 0 {
|
||||
rooted = true
|
||||
if len(path) > start && _is_path_separator(path[start]) {
|
||||
// Take `C:` to `C:\`.
|
||||
start += 1
|
||||
}
|
||||
intrinsics.mem_copy_non_overlapping(raw_data(buffer), raw_data(path), start)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_is_absolute_path :: proc(path: string) -> bool {
|
||||
if _is_reserved_name(path) {
|
||||
return true
|
||||
}
|
||||
l := _volume_name_len(path)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
path := path
|
||||
path = path[l:]
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
return _is_path_separator(path[0])
|
||||
}
|
||||
|
||||
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
|
||||
rel := path
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator())
|
||||
n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
|
||||
if n == 0 {
|
||||
return "", Platform_Error(win32.GetLastError())
|
||||
}
|
||||
|
||||
buf := make([]u16, n, temp_allocator()) or_return
|
||||
n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
|
||||
if n == 0 {
|
||||
return "", Platform_Error(win32.GetLastError())
|
||||
}
|
||||
|
||||
return win32.utf16_to_utf8(buf, allocator)
|
||||
}
|
||||
|
||||
_get_relative_path_handle_start :: proc(base, target: string) -> bool {
|
||||
base_root := base[:_volume_name_len(base)]
|
||||
target_root := target[:_volume_name_len(target)]
|
||||
return strings.equal_fold(base_root, target_root)
|
||||
}
|
||||
|
||||
_get_common_path_len :: proc(base, target: string) -> int {
|
||||
i := 0
|
||||
end := min(len(base), len(target))
|
||||
for j in 0..=end {
|
||||
if j == end || _is_path_separator(base[j]) {
|
||||
if strings.equal_fold(base[i:j], target[i:j]) {
|
||||
i = j
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
_split_path :: proc(path: string) -> (dir, file: string) {
|
||||
vol_len := _volume_name_len(path)
|
||||
|
||||
i := len(path) - 1
|
||||
for i >= vol_len && !_is_path_separator(path[i]) {
|
||||
i -= 1
|
||||
}
|
||||
if i == vol_len {
|
||||
return path[:i+1], path[i+1:]
|
||||
} else if i > vol_len {
|
||||
return path[:i], path[i+1:]
|
||||
}
|
||||
return "", path
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import "core:slice"
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "core:sys/linux"
|
||||
import "core:path/filepath"
|
||||
|
||||
PIDFD_UNASSIGNED :: ~uintptr(0)
|
||||
|
||||
@@ -205,7 +204,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
|
||||
info.fields += {.Executable_Path}
|
||||
} else if cwd_err == nil {
|
||||
info.executable_path = filepath.join({ cwd, cmdline[:terminator] }, allocator) or_return
|
||||
info.executable_path = join_path({ cwd, cmdline[:terminator] }, allocator) or_return
|
||||
info.fields += {.Executable_Path}
|
||||
} else {
|
||||
break cmdline_if
|
||||
@@ -407,7 +406,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
executable_name := desc.command[0]
|
||||
if strings.index_byte(executable_name, '/') < 0 {
|
||||
path_env := get_env("PATH", temp_allocator())
|
||||
path_dirs := filepath.split_list(path_env, temp_allocator()) or_return
|
||||
path_dirs := split_path_list(path_env, temp_allocator()) or_return
|
||||
|
||||
exe_builder := strings.builder_make(temp_allocator()) or_return
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import "base:runtime"
|
||||
|
||||
import "core:time"
|
||||
import "core:strings"
|
||||
import "core:path/filepath"
|
||||
|
||||
import kq "core:sys/kqueue"
|
||||
import "core:sys/posix"
|
||||
@@ -62,7 +61,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
exe_name := desc.command[0]
|
||||
if strings.index_byte(exe_name, '/') < 0 {
|
||||
path_env := get_env("PATH", temp_allocator())
|
||||
path_dirs := filepath.split_list(path_env, temp_allocator())
|
||||
path_dirs := split_path_list(path_env, temp_allocator()) or_return
|
||||
|
||||
found: bool
|
||||
for dir in path_dirs {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
|
||||
@@ -25,7 +24,7 @@ File_Info :: struct {
|
||||
file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) {
|
||||
cloned = fi
|
||||
cloned.fullpath = strings.clone(fi.fullpath, allocator) or_return
|
||||
cloned.name = filepath.base(cloned.fullpath)
|
||||
_, cloned.name = split_path(cloned.fullpath)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ package os2
|
||||
import "core:time"
|
||||
import "base:runtime"
|
||||
import "core:sys/linux"
|
||||
import "core:path/filepath"
|
||||
|
||||
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
|
||||
impl := (^File_Impl)(f.impl)
|
||||
@@ -42,7 +41,7 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
|
||||
creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
|
||||
}
|
||||
fi.creation_time = fi.modification_time
|
||||
fi.name = filepath.base(fi.fullpath)
|
||||
_, fi.name = split_path(fi.fullpath)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:path/filepath"
|
||||
import "core:sys/posix"
|
||||
import "core:time"
|
||||
|
||||
internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) {
|
||||
fi.fullpath = fullpath
|
||||
fi.name = filepath.base(fi.fullpath)
|
||||
_, fi.name = split_path(fi.fullpath)
|
||||
|
||||
fi.inode = u128(stat.st_ino)
|
||||
fi.size = i64(stat.st_size)
|
||||
@@ -104,7 +103,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
|
||||
// NOTE: This might not be correct when given "/symlink/foo.txt",
|
||||
// you would want that to resolve "/symlink", but not resolve "foo.txt".
|
||||
|
||||
fullpath := filepath.clean(name, temp_allocator())
|
||||
fullpath := clean_path(name, temp_allocator()) or_return
|
||||
assert(len(fullpath) > 0)
|
||||
switch {
|
||||
case fullpath[0] == '/':
|
||||
|
||||
@@ -3,13 +3,12 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:path/filepath"
|
||||
import "core:sys/wasm/wasi"
|
||||
import "core:time"
|
||||
|
||||
internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) {
|
||||
fi.fullpath = fullpath
|
||||
fi.name = filepath.base(fi.fullpath)
|
||||
_, fi.name = split_path(fi.fullpath)
|
||||
|
||||
fi.inode = u128(stat.ino)
|
||||
fi.size = i64(stat.size)
|
||||
|
||||
@@ -315,57 +315,37 @@ _is_UNC :: proc(path: string) -> bool {
|
||||
}
|
||||
|
||||
_volume_name_len :: proc(path: string) -> int {
|
||||
if ODIN_OS == .Windows {
|
||||
if len(path) < 2 {
|
||||
return 0
|
||||
}
|
||||
c := path[0]
|
||||
if path[1] == ':' {
|
||||
switch c {
|
||||
case 'a'..='z', 'A'..='Z':
|
||||
return 2
|
||||
}
|
||||
if len(path) < 2 {
|
||||
return 0
|
||||
}
|
||||
c := path[0]
|
||||
if path[1] == ':' {
|
||||
switch c {
|
||||
case 'a'..='z', 'A'..='Z':
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
|
||||
!_is_path_separator(path[2]) && path[2] != '.' {
|
||||
for n := 3; n < l-1; n += 1 {
|
||||
if _is_path_separator(path[n]) {
|
||||
n += 1
|
||||
if !_is_path_separator(path[n]) {
|
||||
if path[n] == '.' {
|
||||
break
|
||||
}
|
||||
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
|
||||
!_is_path_separator(path[2]) && path[2] != '.' {
|
||||
for n := 3; n < l-1; n += 1 {
|
||||
if _is_path_separator(path[n]) {
|
||||
n += 1
|
||||
if !_is_path_separator(path[n]) {
|
||||
if path[n] == '.' {
|
||||
break
|
||||
}
|
||||
for ; n < l; n += 1 {
|
||||
if _is_path_separator(path[n]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
break
|
||||
for ; n < l; n += 1 {
|
||||
if _is_path_separator(path[n]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
_is_abs :: proc(path: string) -> bool {
|
||||
if _is_reserved_name(path) {
|
||||
return true
|
||||
}
|
||||
l := _volume_name_len(path)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
path := path
|
||||
path = path[l:]
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
return is_path_separator(path[0])
|
||||
}
|
||||
|
||||
|
||||
@@ -2,27 +2,27 @@ package tests_core_os_os2
|
||||
|
||||
import os "core:os/os2"
|
||||
import "core:log"
|
||||
import "core:path/filepath"
|
||||
import "core:slice"
|
||||
import "core:testing"
|
||||
import "core:strings"
|
||||
|
||||
@(test)
|
||||
test_read_dir :: proc(t: ^testing.T) {
|
||||
path := filepath.join({#directory, "../dir"})
|
||||
path, err_join := os.join_path({#directory, "../dir"}, context.allocator)
|
||||
defer delete(path)
|
||||
|
||||
fis, err := os.read_all_directory_by_path(path, context.allocator)
|
||||
fis, err_read := os.read_all_directory_by_path(path, context.allocator)
|
||||
defer os.file_info_slice_delete(fis, context.allocator)
|
||||
|
||||
slice.sort_by_key(fis, proc(fi: os.File_Info) -> string { return fi.name })
|
||||
|
||||
if err == .Unsupported {
|
||||
if err_read == .Unsupported {
|
||||
log.warn("os2 directory functionality is unsupported, skipping test")
|
||||
return
|
||||
}
|
||||
|
||||
testing.expect_value(t, err, nil)
|
||||
testing.expect_value(t, err_join, nil)
|
||||
testing.expect_value(t, err_read, nil)
|
||||
testing.expect_value(t, len(fis), 2)
|
||||
|
||||
testing.expect_value(t, fis[0].name, "b.txt")
|
||||
@@ -34,8 +34,9 @@ test_read_dir :: proc(t: ^testing.T) {
|
||||
|
||||
@(test)
|
||||
test_walker :: proc(t: ^testing.T) {
|
||||
path := filepath.join({#directory, "../dir"})
|
||||
path, err := os.join_path({#directory, "../dir"}, context.allocator)
|
||||
defer delete(path)
|
||||
testing.expect_value(t, err, nil)
|
||||
|
||||
w := os.walker_create(path)
|
||||
defer os.walker_destroy(&w)
|
||||
@@ -45,11 +46,12 @@ test_walker :: proc(t: ^testing.T) {
|
||||
|
||||
@(test)
|
||||
test_walker_file :: proc(t: ^testing.T) {
|
||||
path := filepath.join({#directory, "../dir"})
|
||||
path, err_join := os.join_path({#directory, "../dir"}, context.allocator)
|
||||
defer delete(path)
|
||||
testing.expect_value(t, err_join, nil)
|
||||
|
||||
f, err := os.open(path)
|
||||
testing.expect_value(t, err, nil)
|
||||
f, err_open := os.open(path)
|
||||
testing.expect_value(t, err_open, nil)
|
||||
defer os.close(f)
|
||||
|
||||
w := os.walker_create(f)
|
||||
@@ -64,10 +66,18 @@ test_walker_internal :: proc(t: ^testing.T, w: ^os.Walker) {
|
||||
path: string,
|
||||
}
|
||||
|
||||
joined_1, err_joined_1 := os.join_path({"dir", "b.txt"}, context.allocator)
|
||||
joined_2, err_joined_2 := os.join_path({"dir", "sub"}, context.allocator)
|
||||
joined_3, err_joined_3 := os.join_path({"dir", "sub", ".gitkeep"}, context.allocator)
|
||||
|
||||
testing.expect_value(t, err_joined_1, nil)
|
||||
testing.expect_value(t, err_joined_2, nil)
|
||||
testing.expect_value(t, err_joined_3, nil)
|
||||
|
||||
expected := [?]Seen{
|
||||
{.Regular, filepath.join({"dir", "b.txt"})},
|
||||
{.Directory, filepath.join({"dir", "sub"})},
|
||||
{.Regular, filepath.join({"dir", "sub", ".gitkeep"})},
|
||||
{.Regular, joined_1},
|
||||
{.Directory, joined_2},
|
||||
{.Regular, joined_3},
|
||||
}
|
||||
|
||||
seen: [dynamic]Seen
|
||||
|
||||
@@ -2,11 +2,13 @@ package tests_core_os_os2
|
||||
|
||||
import os "core:os/os2"
|
||||
import "core:testing"
|
||||
import "core:path/filepath"
|
||||
|
||||
@(test)
|
||||
test_clone :: proc(t: ^testing.T) {
|
||||
f, err := os.open(filepath.join({#directory, "file.odin"}, context.temp_allocator))
|
||||
joined, err := os.join_path({#directory, "file.odin"}, context.temp_allocator)
|
||||
testing.expect_value(t, err, nil)
|
||||
f: ^os.File
|
||||
f, err = os.open(joined)
|
||||
testing.expect_value(t, err, nil)
|
||||
testing.expect(t, f != nil)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package tests_core_os_os2
|
||||
|
||||
import os "core:os/os2"
|
||||
import "core:log"
|
||||
import "core:path/filepath"
|
||||
import "core:testing"
|
||||
import "core:strings"
|
||||
|
||||
@@ -17,6 +16,351 @@ test_executable :: proc(t: ^testing.T) {
|
||||
|
||||
testing.expect_value(t, err, nil)
|
||||
testing.expect(t, len(path) > 0)
|
||||
testing.expect(t, filepath.is_abs(path))
|
||||
testing.expectf(t, strings.contains(path, filepath.base(os.args[0])), "expected the executable path to contain the base of os.args[0] which is %q", filepath.base(os.args[0]))
|
||||
testing.expect(t, os.is_absolute_path(path))
|
||||
_, filename := os.split_path(os.args[0])
|
||||
testing.expectf(t, strings.contains(path, filename), "expected the executable path to contain the base of os.args[0] which is %q", filename)
|
||||
}
|
||||
|
||||
posix_to_dos_path :: proc(path: string) -> string {
|
||||
if len(path) == 0 {
|
||||
return path
|
||||
}
|
||||
path := path
|
||||
path, _ = strings.replace_all(path, `/`, `\`, context.temp_allocator)
|
||||
if path[0] == '\\' {
|
||||
path = strings.concatenate({"C:", path}, context.temp_allocator)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_clean_path :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct{
|
||||
path: string,
|
||||
expected: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{`../../foo/../../`, `../../..`},
|
||||
{`../../foo/..`, `../..`},
|
||||
{`../../foo`, `../../foo`},
|
||||
{`../..`, `../..`},
|
||||
{`.././foo`, `../foo`},
|
||||
{`..`, `..`},
|
||||
{`.`, `.`},
|
||||
{`.foo`, `.foo`},
|
||||
{`/../../foo/../../`, `/`},
|
||||
{`/../`, `/`},
|
||||
{`/..`, `/`},
|
||||
{`/`, `/`},
|
||||
{`//home/foo/bar/../../`, `/home`},
|
||||
{`/a/../..`, `/`},
|
||||
{`/a/../`, `/`},
|
||||
{`/a/あ`, `/a/あ`},
|
||||
{`/a/あ/..`, `/a`},
|
||||
{`/あ/a/..`, `/あ`},
|
||||
{`/あ/a/../あ`, `/あ/あ`},
|
||||
{`/home/../`, `/`},
|
||||
{`/home/..`, `/`},
|
||||
{`/home/foo/../../usr`, `/usr`},
|
||||
{`/home/foo/../..`, `/`},
|
||||
{`/home/foo/../`, `/home`},
|
||||
{``, `.`},
|
||||
{`a/..`, `.`},
|
||||
{`a`, `a`},
|
||||
{`abc//.//../foo`, `foo`},
|
||||
{`foo`, `foo`},
|
||||
{`home/foo/bar/../../`, `home`},
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
for &tc in test_cases {
|
||||
tc.path = posix_to_dos_path(tc.path)
|
||||
tc.expected = posix_to_dos_path(tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
joined, err := os.clean_path(tc.path, context.temp_allocator)
|
||||
testing.expectf(t, joined == tc.expected && err == nil, "expected clean_path(%q) -> %q; got: %q, %v", tc.path, tc.expected, joined, err)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_is_absolute_path :: proc(t: ^testing.T) {
|
||||
when ODIN_OS == .Windows {
|
||||
testing.expect(t, os.is_absolute_path(`C:\Windows`))
|
||||
} else {
|
||||
testing.expect(t, os.is_absolute_path("/home"))
|
||||
}
|
||||
testing.expect(t, !os.is_absolute_path("home"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_get_relative_path :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
base, target: string,
|
||||
expected: string,
|
||||
}
|
||||
|
||||
Fail_Case :: struct {
|
||||
base, target: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{"", "foo", "foo"},
|
||||
{".", "foo", "foo"},
|
||||
{"/", "/", "."},
|
||||
{"/", "/home/alice/bert", "home/alice/bert"},
|
||||
{"/a", "/b", "../b"},
|
||||
{"/あ", "/あ/a", "a"},
|
||||
{"/a", "/a/あ", "あ"},
|
||||
{"/あ", "/い", "../い"},
|
||||
{"/a", "/usr", "../usr"},
|
||||
{"/home", "/", ".."},
|
||||
{"/home", "/home/alice/bert", "alice/bert"},
|
||||
{"/home/foo", "/", "../.."},
|
||||
{"/home/foo", "/home", ".."},
|
||||
{"/home/foo", "/home/alice/bert", "../alice/bert"},
|
||||
{"/home/foo", "/home/foo", "."},
|
||||
{"/home/foo", "/home/foo/bar", "bar"},
|
||||
{"/home/foo/bar", "/home", "../.."},
|
||||
{"/home/foo/bar", "/home/alice/bert", "../../alice/bert"},
|
||||
{"/home/foo/bar/bert", "/home/alice/bert", "../../../alice/bert"},
|
||||
{"/www", "/mount", "../mount"},
|
||||
{"foo", ".", ".."},
|
||||
{"foo", "bar", "../bar"},
|
||||
{"foo", "bar", "../bar"},
|
||||
{"foo", "../bar", "../../bar"},
|
||||
{"foo", "foo", "."},
|
||||
{"foo", "foo/bar", "bar"},
|
||||
{"home/foo/bar", "home/alice/bert", "../../alice/bert"},
|
||||
}
|
||||
|
||||
fail_cases := [?]Fail_Case {
|
||||
{"", "/home"},
|
||||
{"/home", ""},
|
||||
{"..", ""},
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
for &tc in test_cases {
|
||||
tc.base = posix_to_dos_path(tc.base)
|
||||
tc.target = posix_to_dos_path(tc.target)
|
||||
// Make one part all capitals to test case-insensitivity.
|
||||
tc.target = strings.to_upper(tc.target, context.temp_allocator)
|
||||
tc.expected = posix_to_dos_path(tc.expected)
|
||||
}
|
||||
for &tc in fail_cases {
|
||||
tc.base = posix_to_dos_path(tc.base)
|
||||
tc.target = posix_to_dos_path(tc.target)
|
||||
}
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
result, err := os.get_relative_path(tc.base, tc.target, context.temp_allocator)
|
||||
joined, err2 := os.join_path({tc.base, result}, context.temp_allocator)
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
passed := strings.equal_fold(result, tc.expected) && err == nil
|
||||
join_guaranteed := strings.equal_fold(joined, tc.target) && err2 == nil
|
||||
} else {
|
||||
passed := result == tc.expected && err == nil
|
||||
join_guaranteed := joined == tc.target && err2 == nil
|
||||
}
|
||||
testing.expectf(t, passed, "expected get_relative_path(%q, %q) -> %q; got %q, %v", tc.base, tc.target, tc.expected, result, err)
|
||||
testing.expectf(t, join_guaranteed, "join_path({{%q, %q}}) guarantee of get_relative_path(%q, %q) failed; got %q, %v instead", tc.base, result, tc.base, tc.target, joined, err2)
|
||||
}
|
||||
|
||||
for tc in fail_cases {
|
||||
result, err := os.get_relative_path(tc.base, tc.target, context.temp_allocator)
|
||||
testing.expectf(t, result == "" && err != nil, "expected get_relative_path(%q, %q) to fail, got %q, %v", tc.base, tc.target, result, err)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_split_path :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
path: string,
|
||||
dir, filename: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{ "", "", "" },
|
||||
{ "/", "/", "" },
|
||||
{ "/a", "/", "a" },
|
||||
{ "readme.txt", "", "readme.txt" },
|
||||
{ "/readme.txt", "/", "readme.txt" },
|
||||
{ "/var/readme.txt", "/var", "readme.txt" },
|
||||
{ "/home/foo/bar.tar.gz", "/home/foo", "bar.tar.gz" },
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
for &tc in test_cases {
|
||||
tc.path = posix_to_dos_path(tc.path)
|
||||
tc.dir = posix_to_dos_path(tc.dir)
|
||||
tc.filename = posix_to_dos_path(tc.filename)
|
||||
}
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
dir, filename := os.split_path(tc.path)
|
||||
testing.expectf(t, dir == tc.dir && filename == tc.filename, "expected split_path(%q) -> %q, %q; got: %q, %q", tc.path, tc.dir, tc.filename, dir, filename)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_join_path :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
elems: []string,
|
||||
expected: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{ {"" }, "" },
|
||||
{ {"/" }, "/" },
|
||||
{ {"home" }, "home" },
|
||||
{ {"home", "" }, "home" },
|
||||
{ {"/home", "" }, "/home" },
|
||||
{ {"", "home" }, "home" },
|
||||
{ {"", "/home" }, "/home" },
|
||||
{ {"", "/home", "", "foo" }, "/home/foo" },
|
||||
{ {"", "home", "", "", "foo", "" }, "home/foo" },
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
for &tc in test_cases {
|
||||
for &elem in tc.elems {
|
||||
elem = posix_to_dos_path(elem)
|
||||
}
|
||||
tc.expected = posix_to_dos_path(tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
result, err := os.join_path(tc.elems, context.temp_allocator)
|
||||
testing.expectf(t, result == tc.expected && err == nil, "expected join_path(%v) -> %q; got: %q, %v", tc.elems, tc.expected, result, err)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_split_filename :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
filename: string,
|
||||
base, ext: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{"", "", ""},
|
||||
{"a", "a", ""},
|
||||
{".", ".", ""},
|
||||
{".a", ".a", ""},
|
||||
{".foo", ".foo", ""},
|
||||
{".foo.txt", ".foo", "txt"},
|
||||
{"a.b", "a", "b"},
|
||||
{"foo", "foo", ""},
|
||||
{"readme.txt", "readme", "txt"},
|
||||
{"pkg.tar.gz", "pkg.tar", "gz"},
|
||||
// Assert API ignores directory hierarchies:
|
||||
{"dir/FILE.TXT", "dir/FILE", "TXT"},
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
base, ext := os.split_filename(tc.filename)
|
||||
testing.expectf(t, base == tc.base && ext == tc.ext, "expected split_filename(%q) -> %q, %q; got: %q, %q", tc.filename, tc.base, tc.ext, base, ext)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_split_filename_all :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
filename: string,
|
||||
base, ext: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{"", "", ""},
|
||||
{"a", "a", ""},
|
||||
{".", ".", ""},
|
||||
{".a", ".a", ""},
|
||||
{".foo", ".foo", ""},
|
||||
{".foo.txt", ".foo", "txt"},
|
||||
{"a.b", "a", "b"},
|
||||
{"foo", "foo", ""},
|
||||
{"readme.txt", "readme", "txt"},
|
||||
{"pkg.tar.gz", "pkg", "tar.gz"},
|
||||
// Assert API ignores directory hierarchies:
|
||||
{"dir/FILE.TXT", "dir/FILE", "TXT"},
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
base, ext := os.split_filename_all(tc.filename)
|
||||
testing.expectf(t, base == tc.base && ext == tc.ext, "expected split_filename_all(%q) -> %q, %q; got: %q, %q", tc.filename, tc.base, tc.ext, base, ext)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_join_filename :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
base, ext: string,
|
||||
expected: string,
|
||||
}
|
||||
|
||||
test_cases := [?]Test_Case {
|
||||
{"", "", ""},
|
||||
{"", "foo", "foo"},
|
||||
{"foo", "", "foo"},
|
||||
{"readme", "txt", "readme.txt"},
|
||||
{"pkg.tar", "gz", "pkg.tar.gz"},
|
||||
{"pkg", "tar.gz", "pkg.tar.gz"},
|
||||
// Assert API ignores directory hierarchies:
|
||||
{"dir/FILE", "TXT", "dir/FILE.TXT"},
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
result, err := os.join_filename(tc.base, tc.ext, context.temp_allocator)
|
||||
testing.expectf(t, result == tc.expected && err == nil, "expected join_filename(%q, %q) -> %q; got: %q, %v", tc.base, tc.ext, tc.expected, result, err)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_split_path_list :: proc(t: ^testing.T) {
|
||||
Test_Case :: struct {
|
||||
path_list: string,
|
||||
expected: []string,
|
||||
}
|
||||
|
||||
when ODIN_OS != .Windows {
|
||||
test_cases := [?]Test_Case {
|
||||
{``, {}},
|
||||
{`/bin:`, {`/bin`, ``}},
|
||||
{`/usr/local/bin`, {`/usr/local/bin`}},
|
||||
{`/usr/local/bin:/usr/bin`, {`/usr/local/bin`, `/usr/bin`}},
|
||||
{`"/extra bin":/bin`, {`/extra bin`, `/bin`}},
|
||||
{`"/extra:bin":/bin`, {`/extra:bin`, `/bin`}},
|
||||
}
|
||||
} else {
|
||||
test_cases := [?]Test_Case {
|
||||
{``, {}},
|
||||
{`C:\bin;`, {`C:\bin`, ``}},
|
||||
{`C:\usr\local\bin`, {`C:\usr\local\bin`}},
|
||||
{`C:\usr\local\bin;C:\usr\bin`, {`C:\usr\local\bin`, `C:\usr\bin`}},
|
||||
{`"C:\extra bin";C:\bin`, {`C:\extra bin`, `C:\bin`}},
|
||||
{`"C:\extra;bin";C:\bin`, {`C:\extra;bin`, `C:\bin`}},
|
||||
}
|
||||
}
|
||||
|
||||
for tc in test_cases {
|
||||
result, err := os.split_path_list(tc.path_list, context.temp_allocator)
|
||||
if testing.expectf(t, len(result) == len(tc.expected), "expected split_path_list(%q) -> %v; got %v, %v", tc.path_list, tc.expected, result, err) {
|
||||
ok := true
|
||||
for entry, i in result {
|
||||
if entry != tc.expected[i] {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
testing.expectf(t, ok, "expected split_path_list(%q) -> %v; got %v, %v", tc.path_list, tc.expected, result, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user