diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2022-04-27 14:37:15 +0200 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2022-04-27 14:37:15 +0200 |
| commit | c4e0d1efa1ec655bae9134b95a0fcd060cc7bbea (patch) | |
| tree | c29bd0b78138e8d67aebe34ac689d13e32d9d15f /core/path | |
| parent | 6e61abc7d06f22129f93110a9f652c3eec21f0c6 (diff) | |
| parent | 9349dfba8fec53f52f77a0c8928e115ec93ff447 (diff) | |
Merge branch 'master' into xml
Diffstat (limited to 'core/path')
| -rw-r--r-- | core/path/filepath/match.odin | 27 | ||||
| -rw-r--r-- | core/path/filepath/path.odin | 154 | ||||
| -rw-r--r-- | core/path/filepath/path_unix.odin | 11 | ||||
| -rw-r--r-- | core/path/filepath/walk.odin | 2 | ||||
| -rw-r--r-- | core/path/path_error.odin | 5 | ||||
| -rw-r--r-- | core/path/slashpath/match.odin (renamed from core/path/match.odin) | 2 | ||||
| -rw-r--r-- | core/path/slashpath/path.odin (renamed from core/path/path.odin) | 4 |
7 files changed, 171 insertions, 34 deletions
diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index cba44953d..252912710 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -89,7 +89,7 @@ scan_chunk :: proc(pattern: string) -> (star: bool, chunk, rest: string) { scan_loop: for i = 0; i < len(pattern); i += 1 { switch pattern[i] { case '\\': - when ODIN_OS != "windows" { + when ODIN_OS != .Windows { if i+1 < len(pattern) { i += 1 } @@ -161,7 +161,7 @@ match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Match_Err chunk = chunk[1:] case '\\': - when ODIN_OS != "windows" { + when ODIN_OS != .Windows { chunk = chunk[1:] if len(chunk) == 0 { err = .Syntax_Error @@ -188,7 +188,7 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er return } chunk := chunk - if chunk[0] == '\\' && ODIN_OS != "windows" { + if chunk[0] == '\\' && ODIN_OS != .Windows { chunk = chunk[1:] if len(chunk) == 0 { err = .Syntax_Error @@ -220,19 +220,21 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er // glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) { + context.allocator = allocator + if !has_meta(pattern) { // TODO(bill): os.lstat on here to check for error - m := make([]string, 1, allocator) + m := make([]string, 1) m[0] = pattern return m[:], .None } - temp_buf: [8]byte - dir, file := split(pattern) volume_len := 0 - when ODIN_OS == "windows" { + when ODIN_OS == .Windows { + temp_buf: [8]byte volume_len, dir = clean_glob_path_windows(dir, temp_buf[:]) + } else { dir = clean_glob_path(dir) } @@ -247,7 +249,7 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str if err != .None { return } - dmatches := make([dynamic]string, 0, 0, allocator) + dmatches := make([dynamic]string, 0, 0) for d in m { dmatches, err = _glob(d, file, &dmatches) if err != .None { @@ -259,11 +261,13 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str } return } -_glob :: proc(dir, pattern: string, matches: ^[dynamic]string) -> (m: [dynamic]string, e: Match_Error) { +_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) { + context.allocator = allocator + if matches != nil { m = matches^ } else { - m = make([dynamic]string, 0, 0, context.allocator) + m = make([dynamic]string, 0, 0) } @@ -276,6 +280,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string) -> (m: [dynamic]s { file_info, ferr := os.fstat(d) defer os.file_info_delete(file_info) + if ferr != 0 { return } @@ -308,7 +313,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string) -> (m: [dynamic]s @(private) has_meta :: proc(path: string) -> bool { - when ODIN_OS == "windows" { + when ODIN_OS == .Windows { CHARS :: `*?[` } else { CHARS :: `*?[\` diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 39cd80a47..32e4a8a37 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -1,14 +1,16 @@ // The path/filepath package uses either forward slashes or backslashes depending on the operating system -// To process paths usch as URLs that depend on forward slashes regardless of the OS, use the path package +// To process paths such as URLs that depend on forward slashes regardless of the OS, use the path package package filepath import "core:strings" +SEPARATOR_CHARS :: `/\` + // is_separator checks whether the byte is a valid separator character is_separator :: proc(c: byte) -> bool { switch c { case '/': return true - case '\\': return ODIN_OS == "windows" + case '\\': return ODIN_OS == .Windows } return false } @@ -32,7 +34,7 @@ volume_name :: proc(path: string) -> string { } volume_name_len :: proc(path: string) -> int { - if ODIN_OS == "windows" { + if ODIN_OS == .Windows { if len(path) < 2 { return 0 } @@ -69,6 +71,16 @@ volume_name_len :: proc(path: string) -> int { return 0 } +/* + Gets the file name and extension from a path. + + i.e: + 'path/to/name.tar.gz' -> 'name.tar.gz' + 'path/to/name.txt' -> 'name.txt' + 'path/to/name' -> 'name' + + Returns "." if the path is an empty string. +*/ base :: proc(path: string) -> string { if path == "" { return "." @@ -94,6 +106,118 @@ base :: proc(path: string) -> string { return path } +/* + Gets the name of a file from a path. + + The stem of a file is such that stem(path) + ext(path) = base(path). + + Only the last dot is considered when splitting the file extension. + See `short_stem`. + + i.e: + 'name.tar.gz' -> 'name.tar' + 'name.txt' -> 'name' + + Returns an empty string if there is no stem. e.g: '.gitignore'. + Returns an empty string if there's a trailing path separator. +*/ +stem :: proc(path: string) -> string { + if len(path) > 0 && is_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { + path = path[i+1:] + } + + if i := strings.last_index_byte(path, '.'); i != -1 { + return path[:i] + } + + return path +} + +/* + Gets the name of a file from a path. + + The short stem is such that short_stem(path) + long_ext(path) = base(path). + + The first dot is used to split off the file extension, unlike `stem` which uses the last dot. + + i.e: + 'name.tar.gz' -> 'name' + 'name.txt' -> 'name' + + Returns an empty string if there is no stem. e.g: '.gitignore'. + Returns an empty string if there's a trailing path separator. +*/ +short_stem :: proc(path: string) -> string { + s := stem(path) + if i := strings.index_byte(s, '.'); i != -1 { + return s[:i] + } + return s +} + +/* + Gets the file extension from a path, including the dot. + + The file extension is such that stem(path) + ext(path) = base(path). + + Only the last dot is considered when splitting the file extension. + See `long_ext`. + + i.e: + 'name.tar.gz' -> '.gz' + 'name.txt' -> '.txt' + + Returns an empty string if there is no dot. + Returns an empty string if there is a trailing path separator. +*/ +ext :: proc(path: string) -> string { + for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { + if path[i] == '.' { + return path[i:] + } + } + return "" +} + +/* + Gets the file extension from a path, including the dot. + + The long file extension is such that short_stem(path) + long_ext(path) = base(path). + + The first dot is used to split off the file extension, unlike `ext` which uses the last dot. + + i.e: + 'name.tar.gz' -> '.tar.gz' + 'name.txt' -> '.txt' + + Returns an empty string if there is no dot. + Returns an empty string if there is a trailing path separator. +*/ +long_ext :: proc(path: string) -> string { + if len(path) > 0 && is_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { + path = path[i+1:] + } + + if i := strings.index_byte(path, '.'); i != -1 { + return path[i:] + } + + return "" +} clean :: proc(path: string, allocator := context.allocator) -> string { context.allocator = allocator @@ -122,6 +246,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string { vol_and_path = original_path, vol_len = vol_len, } + defer lazy_buffer_destroy(out) r, dot_dot := 0, 0 if rooted { @@ -170,7 +295,6 @@ clean :: proc(path: string, allocator := context.allocator) -> string { cleaned, new_allocation := from_slash(s) if new_allocation { delete(s) - lazy_buffer_destroy(out) } return cleaned } @@ -189,15 +313,6 @@ to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: str return strings.replace_all(path, SEPARATOR_STRING, "/", allocator) } -ext :: proc(path: string) -> string { - for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { - if path[i] == '.' { - return path[i:] - } - } - return "" -} - Relative_Error :: enum { None, @@ -284,13 +399,14 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> ( } dir :: proc(path: string, allocator := context.allocator) -> string { + context.allocator = allocator vol := volume_name(path) i := len(path) - 1 for i >= len(vol) && !is_separator(path[i]) { i -= 1 } - dir := clean(path[len(vol) : i+1], allocator) - defer delete(dir, allocator) + dir := clean(path[len(vol) : i+1]) + defer delete(dir) if dir == "." && len(vol) > 2 { return strings.clone(vol) } @@ -299,6 +415,11 @@ dir :: proc(path: string, allocator := context.allocator) -> string { +// Splits the PATH-like `path` string, returning an array of its separated components (delete after use). +// For Windows the separator is `;`, for Unix it's `:`. +// An empty string returns nil. A non-empty string with no separators returns a 1-element array. +// Any empty components will be included, e.g. `a::b` will return a 3-element array, as will `::`. +// Separators within pairs of double-quotes will be ignored and stripped, e.g. `"a:b"c:d` will return []{`a:bc`, `d`}. split_list :: proc(path: string, allocator := context.allocator) -> []string { if path == "" { return nil @@ -321,7 +442,7 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { } start, quote = 0, false - list := make([]string, count, allocator) + list := make([]string, count + 1, allocator) index := 0 for i := 0; i < len(path); i += 1 { c := path[i] @@ -335,6 +456,7 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { } } assert(index == count) + list[index] = path[start:] for s0, i in list { s, new := strings.replace_all(s0, `"`, ``, allocator) diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 1db528a2f..d0eaa3635 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,7 +1,7 @@ -//+build linux, darwin, freebsd +//+build linux, darwin, freebsd, openbsd package filepath -when ODIN_OS == "darwin" { +when ODIN_OS == .Darwin { foreign import libc "System.framework" } else { foreign import libc "system:c" @@ -54,11 +54,16 @@ foreign libc { @(link_name="free") _unix_free :: proc(ptr: rawptr) --- } -when ODIN_OS == "darwin" { +when ODIN_OS == .Darwin { @(private) foreign libc { @(link_name="__error") __error :: proc() -> ^i32 --- } +} else when ODIN_OS == .OpenBSD { + @(private) + foreign libc { + @(link_name="__errno") __error :: proc() -> ^i32 --- + } } else { @(private) foreign libc { diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 29d4fd5b1..dad63cc09 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -71,7 +71,7 @@ _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc) -> (err: os.Errno, skip_ @(private) read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) { - f, err := os.open(dir_name) + f, err := os.open(dir_name, os.O_RDONLY) if err != 0 { return nil, err } diff --git a/core/path/path_error.odin b/core/path/path_error.odin new file mode 100644 index 000000000..2be0b4cf4 --- /dev/null +++ b/core/path/path_error.odin @@ -0,0 +1,5 @@ +package path + +#panic( +`core:path/slashpath - for paths separated by forward slashes, e.g. paths in URLs, this does not deal with OS-specific paths +core:path/filepath - uses either forward slashes or backslashes depending on the operating system, deals with Windows/NT paths with volume letters or backslashes (on the related platforms)`) diff --git a/core/path/match.odin b/core/path/slashpath/match.odin index 0bea4f6e7..09e774275 100644 --- a/core/path/match.odin +++ b/core/path/slashpath/match.odin @@ -1,4 +1,4 @@ -package path +package slashpath import "core:strings" import "core:unicode/utf8" diff --git a/core/path/path.odin b/core/path/slashpath/path.odin index 186176b42..8ac10e655 100644 --- a/core/path/path.odin +++ b/core/path/slashpath/path.odin @@ -1,9 +1,9 @@ -// The path package is only to be used for paths separated by forward slashes,
+// The slashpath package is only to be used for paths separated by forward slashes,
// e.g. paths in URLs
//
// This package does not deal with Windows/NT paths with volume letters or backslashes
// To manipulate operating system specific paths, use the path/filepath package
-package path
+package slashpath
import "core:strings"
|