From 968ff38cad1db6c108d402442825f80453accd7c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Oct 2017 12:07:22 -0400 Subject: [PATCH 1/6] better support table --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index e04268d4b2..ac9cff6ec5 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,45 @@ clarity. standard library that does not depend on an OS is available to you in freestanding mode. +### Support Table +Freestanding means that you do not directly interact with the OS +or you are writing your own OS. + +Note that if you use libc or other libraries to interact with the OS, +that counts as "freestanding" for the purposes of this table. + +| | freestanding | linux | macosx | windows | other | +|-------------|--------------|---------|---------|---------|---------| +|i386 | OK | planned | OK | OK | planned | +|x86_64 | OK | OK | OK | OK | planned | +|arm | OK | planned | planned | N/A | planned | +|aarch64 | OK | planned | planned | planned | planned | +|avr | OK | planned | planned | N/A | planned | +|bpf | OK | planned | planned | N/A | planned | +|hexagon | OK | planned | planned | N/A | planned | +|mips | OK | planned | planned | N/A | planned | +|msp430 | OK | planned | planned | N/A | planned | +|nios2 | OK | planned | planned | N/A | planned | +|powerpc | OK | planned | planned | N/A | planned | +|r600 | OK | planned | planned | N/A | planned | +|amdgcn | OK | planned | planned | N/A | planned | +|riscv | OK | planned | planned | N/A | planned | +|sparc | OK | planned | planned | N/A | planned | +|s390x | OK | planned | planned | N/A | planned | +|tce | OK | planned | planned | N/A | planned | +|thumb | OK | planned | planned | N/A | planned | +|xcore | OK | planned | planned | N/A | planned | +|nvptx | OK | planned | planned | N/A | planned | +|le | OK | planned | planned | N/A | planned | +|amdil | OK | planned | planned | N/A | planned | +|hsail | OK | planned | planned | N/A | planned | +|spir | OK | planned | planned | N/A | planned | +|kalimba | OK | planned | planned | N/A | planned | +|shave | OK | planned | planned | N/A | planned | +|lanai | OK | planned | planned | N/A | planned | +|wasm | OK | N/A | N/A | N/A | N/A | +|renderscript | OK | N/A | N/A | N/A | N/A | + ## Community * IRC: `#zig` on Freenode. From 08ee69dac360dda17e22d950246e42228fc54f47 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Oct 2017 00:27:15 -0400 Subject: [PATCH 2/6] implement os.path.dirname for windows --- README.md | 5 +- std/mem.zig | 55 +++-- std/os/index.zig | 32 ++- std/os/path.zig | 562 ++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 559 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index ac9cff6ec5..8cbf31b3e1 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,12 @@ clarity. * Cross-compiling is a primary use case. * In addition to creating executables, creating a C library is a primary use case. You can export an auto-generated .h file. - * Standard library supports Operating System abstractions for: - * `x86_64` `linux` - * `x86_64` `macos` - * Support for all popular operating systems and architectures is planned. * For OS development, Zig supports all architectures that LLVM does. All the standard library that does not depend on an OS is available to you in freestanding mode. ### Support Table + Freestanding means that you do not directly interact with the OS or you are writing your own OS. diff --git a/std/mem.zig b/std/mem.zig index e16e947c25..bc25c6e706 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -216,20 +216,28 @@ pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) -> %[]T { /// Linear search for the index of a scalar value inside a slice. pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize { - for (slice) |item, i| { - if (item == value) { + return indexOfScalarPos(T, slice, 0, value); +} + +pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize, value: T) -> ?usize { + var i: usize = start_index; + while (i < slice.len) : (i += 1) { + if (slice[i] == value) return i; - } } return null; } -// TODO boyer-moore algorithm pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize { + return indexOfPos(T, haystack, 0, needle); +} + +// TODO boyer-moore algorithm +pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) -> ?usize { if (needle.len > haystack.len) return null; - var i: usize = 0; + var i: usize = start_index; const end = haystack.len - needle.len; while (i <= end) : (i += 1) { if (eql(T, haystack[i .. i + needle.len], needle)) @@ -303,15 +311,15 @@ pub fn eql_slice_u8(a: []const u8, b: []const u8) -> bool { return eql(u8, a, b); } -/// Returns an iterator that iterates over the slices of ::s that are not -/// the byte ::c. -/// split(" abc def ghi ") +/// Returns an iterator that iterates over the slices of `buffer` that are not +/// any of the bytes in `split_bytes`. +/// split(" abc def ghi ", " ") /// Will return slices for "abc", "def", "ghi", null, in that order. -pub fn split(s: []const u8, c: u8) -> SplitIterator { +pub fn split(buffer: []const u8, split_bytes: []const u8) -> SplitIterator { SplitIterator { .index = 0, - .s = s, - .c = c, + .buffer = buffer, + .split_bytes = split_bytes, } } @@ -328,31 +336,32 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> b } const SplitIterator = struct { - s: []const u8, - c: u8, + buffer: []const u8, + split_bytes: []const u8, index: usize, pub fn next(self: &SplitIterator) -> ?[]const u8 { // move to beginning of token - while (self.index < self.s.len and self.s[self.index] == self.c) : (self.index += 1) {} + while (self.index < self.buffer.len and self.isSplitByte(self.buffer[self.index])) : (self.index += 1) {} const start = self.index; - if (start == self.s.len) { + if (start == self.buffer.len) { return null; } // move to end of token - while (self.index < self.s.len and self.s[self.index] != self.c) : (self.index += 1) {} + while (self.index < self.buffer.len and !self.isSplitByte(self.buffer[self.index])) : (self.index += 1) {} const end = self.index; - return self.s[start..end]; + return self.buffer[start..end]; } - /// Returns a slice of the remaining bytes. Does not affect iterator state. - pub fn rest(self: &const SplitIterator) -> []const u8 { - // move to beginning of token - var index: usize = self.index; - while (index < self.s.len and self.s[index] == self.c) : (index += 1) {} - return self.s[index..]; + fn isSplitByte(self: &SplitIterator, byte: u8) -> bool { + for (self.split_bytes) |split_byte| { + if (byte == split_byte) { + return true; + } + } + return false; } }; diff --git a/std/os/index.zig b/std/os/index.zig index ccd7527975..3065396fa0 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -474,18 +474,28 @@ pub const args = struct { /// Caller must free the returned memory. pub fn getCwd(allocator: &Allocator) -> %[]u8 { - var buf = %return allocator.alloc(u8, 1024); - %defer allocator.free(buf); - while (true) { - const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len)); - if (err == posix.ERANGE) { - buf = %return allocator.realloc(u8, buf, buf.len * 2); - continue; - } else if (err > 0) { - return error.Unexpected; - } + switch (builtin.os) { + Os.windows => { + @panic("implement getCwd for windows"); + //if (windows.GetCurrentDirectoryA(buf_len(out_cwd), buf_ptr(out_cwd)) == 0) { + // zig_panic("GetCurrentDirectory failed"); + //} + }, + else => { + var buf = %return allocator.alloc(u8, 1024); + %defer allocator.free(buf); + while (true) { + const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len)); + if (err == posix.ERANGE) { + buf = %return allocator.realloc(u8, buf, buf.len * 2); + continue; + } else if (err > 0) { + return error.Unexpected; + } - return cstr.toSlice(buf.ptr); + return cstr.toSlice(buf.ptr); + } + }, } } diff --git a/std/os/path.zig b/std/os/path.zig index cfed8d7b2d..bce866a5fd 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -11,39 +11,223 @@ const posix = os.posix; const c = @import("../c/index.zig"); const cstr = @import("../cstr.zig"); -pub const sep = switch (builtin.os) { - Os.windows => '\\', - else => '/', -}; -pub const delimiter = switch (builtin.os) { - Os.windows => ';', - else => ':', -}; +pub const sep_windows = '\\'; +pub const sep_posix = '/'; +pub const sep = if (is_windows) sep_windows else sep_posix; + +pub const delimiter_windows = ';'; +pub const delimiter_posix = ':'; +pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; + +const is_windows = builtin.os == builtin.Os.windows; /// Naively combines a series of paths with the native path seperator. /// Allocates memory for the result, which must be freed by the caller. pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 { - mem.join(allocator, sep, paths) + if (is_windows) { + return joinWindows(allocator, paths); + } else { + return joinPosix(allocator, paths); + } +} + +pub fn joinWindows(allocator: &Allocator, paths: ...) -> %[]u8 { + return mem.join(allocator, sep_windows, paths); +} + +pub fn joinPosix(allocator: &Allocator, paths: ...) -> %[]u8 { + return mem.join(allocator, sep_posix, paths); } test "os.path.join" { - assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c")); - assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b/", "c"), "/a/b/c")); + assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\a\\b", "c"), "c:\\a\\b\\c")); + assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\a\\b\\", "c"), "c:\\a\\b\\c")); - assert(mem.eql(u8, %%join(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c")); - assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c")); + assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\", "a", "b\\", "c"), "c:\\a\\b\\c")); + assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\a\\", "b\\", "c"), "c:\\a\\b\\c")); - assert(mem.eql(u8, %%join(&debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"), + assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, + "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig"), + "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig")); + + assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/a/b", "c"), "/a/b/c")); + assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/a/b/", "c"), "/a/b/c")); + + assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c")); + assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c")); + + assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"), "/home/andy/dev/zig/build/lib/zig/std/io.zig")); } pub fn isAbsolute(path: []const u8) -> bool { - switch (builtin.os) { - Os.windows => @compileError("Unsupported OS"), - else => return path[0] == sep, + if (is_windows) { + return isAbsoluteWindows(path); + } else { + return isAbsolutePosix(path); } } +pub fn isAbsoluteWindows(path: []const u8) -> bool { + if (path[0] == '/') + return true; + + if (path[0] == '\\') { + return true; + } + if (path.len < 3) { + return false; + } + if (path[1] == ':') { + if (path[2] == '/') + return true; + if (path[2] == '\\') + return true; + } + return false; +} + +pub fn isAbsolutePosix(path: []const u8) -> bool { + return path[0] == sep_posix; +} + +test "os.path.isAbsoluteWindows" { + testIsAbsoluteWindows("/", true); + testIsAbsoluteWindows("//", true); + testIsAbsoluteWindows("//server", true); + testIsAbsoluteWindows("//server/file", true); + testIsAbsoluteWindows("\\\\server\\file", true); + testIsAbsoluteWindows("\\\\server", true); + testIsAbsoluteWindows("\\\\", true); + testIsAbsoluteWindows("c", false); + testIsAbsoluteWindows("c:", false); + testIsAbsoluteWindows("c:\\", true); + testIsAbsoluteWindows("c:/", true); + testIsAbsoluteWindows("c://", true); + testIsAbsoluteWindows("C:/Users/", true); + testIsAbsoluteWindows("C:\\Users\\", true); + testIsAbsoluteWindows("C:cwd/another", false); + testIsAbsoluteWindows("C:cwd\\another", false); + testIsAbsoluteWindows("directory/directory", false); + testIsAbsoluteWindows("directory\\directory", false); +} + +test "os.path.isAbsolutePosix" { + testIsAbsolutePosix("/home/foo", true); + testIsAbsolutePosix("/home/foo/..", true); + testIsAbsolutePosix("bar/", false); + testIsAbsolutePosix("./baz", false); +} + +fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) { + assert(isAbsoluteWindows(path) == expected_result); +} + +fn testIsAbsolutePosix(path: []const u8, expected_result: bool) { + assert(isAbsolutePosix(path) == expected_result); +} + +pub fn drive(path: []const u8) -> ?[]const u8 { + if (path.len < 2) + return null; + if (path[1] != ':') + return null; + return path[0..2]; +} + +pub fn networkShare(path: []const u8) -> ?[]const u8 { + if (path.len < "//a/b".len) + return null; + + { + const this_sep = '/'; + const two_sep = []u8{this_sep, this_sep}; + if (mem.startsWith(u8, path, two_sep)) { + if (path[2] == this_sep) + return null; + const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null; + const next_start = index_host + 1; + if (next_start >= path.len) + return null; + const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len; + return path[0..index_root]; + } + } + { + const this_sep = '\\'; + const two_sep = []u8{this_sep, this_sep}; + if (mem.startsWith(u8, path, two_sep)) { + if (path[2] == this_sep) + return null; + const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null; + const next_start = index_host + 1; + if (next_start >= path.len) + return null; + const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len; + return path[0..index_root]; + } + } + return null; +} + +test "os.path.networkShare" { + assert(mem.eql(u8, ??networkShare("//a/b"), "//a/b")); + assert(mem.eql(u8, ??networkShare("\\\\a\\b"), "\\\\a\\b")); + + assert(networkShare("\\\\a\\") == null); +} + +pub fn preferredSepWindows(path: []const u8) -> u8 { + for (path) |byte| { + if (byte == '/' or byte == '\\') { + return byte; + } + } + return sep_windows; +} + +pub fn preferredSep(path: []const u8) -> u8 { + if (is_windows) { + return preferredSepWindows(path); + } else { + return sep_posix; + } +} + +pub fn root(path: []const u8) -> []const u8 { + if (is_windows) { + return rootWindows(path); + } else { + return rootPosix(path); + } +} + +pub fn rootWindows(path: []const u8) -> []const u8 { + return drive(path) ?? (networkShare(path) ?? []u8{}); +} + +pub fn rootPosix(path: []const u8) -> ?[]const u8 { + if (path.len == 0 or path[0] != '/') + return []u8{}; + + return path[0..1]; +} + +pub fn drivesEqual(drive1: []const u8, drive2: []const u8) -> bool { + assert(drive1.len == 2); + assert(drive2.len == 2); + assert(drive1[1] == ':'); + assert(drive2[1] == ':'); + return asciiLower(drive1[0]) == asciiLower(drive2[0]); +} + +fn asciiLower(byte: u8) -> u8 { + return switch (byte) { + 'A' ... 'Z' => 'a' + (byte - 'A'), + else => byte, + }; +} + /// This function is like a series of `cd` statements executed one after another. /// The result does not have a trailing path separator. pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { @@ -56,17 +240,130 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { } pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { - if (builtin.os == builtin.Os.windows) { - @compileError("TODO implement os.path.resolve for windows"); + if (is_windows) { + return resolveWindows(allocator, paths); + } else { + return resolvePosix(allocator, paths); } - if (paths.len == 0) +} + +pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { + if (paths.len == 0) { + assert(is_windows); // resolveWindows called on non windows can't use getCwd return os.getCwd(allocator); + } + + // determine which drive we want to result with + var result_drive: ?[]const u8 = null; + var have_abs = false; + var first_index: usize = 0; + var max_size: usize = 0; + for (paths) |p, i| { + const is_abs = isAbsoluteWindows(p); + if (is_abs) { + have_abs = true; + first_index = i; + max_size = 0; + } + if (drive(p)) |d| { + result_drive = d; + } else if (is_abs) { + result_drive = null; + } + max_size += p.len + 1; + } + + // if we will result with a drive, loop again to determine + // which is the first time the drive is absolutely specified, if any + // and count up the max bytes for paths related to this drive + if (result_drive) |res_dr| { + have_abs = false; + first_index = 0; + max_size = 0; + var correct_drive = false; + + for (paths) |p, i| { + if (drive(p)) |dr| { + correct_drive = drivesEqual(dr, res_dr); + } + if (!correct_drive) { + continue; + } + const is_abs = isAbsoluteWindows(p); + if (is_abs) { + first_index = i; + max_size = 0; + } + max_size += p.len + 1; + } + } + + var result: []u8 = undefined; + var result_index: usize = 0; + + if (have_abs) { + result = %return allocator.alloc(u8, max_size); + mem.copy(u8, result, paths[first_index]); + result_index += paths[first_index].len; + first_index += 1; + } else { + assert(is_windows); // resolveWindows called on non windows can't use getCwd + // TODO get cwd for result_drive if applicable + const cwd = %return os.getCwd(allocator); + defer allocator.free(cwd); + result = %return allocator.alloc(u8, max_size + cwd.len + 1); + mem.copy(u8, result, cwd); + result_index += cwd.len; + } + %defer allocator.free(result); + + var correct_drive = false; + const rootSlice = rootWindows(result[0..result_index]); + const preferred_path_sep = preferredSepWindows(result[0..result_index]); + for (paths[first_index..]) |p, i| { + if (result_drive) |res_dr| { + if (drive(p)) |dr| { + correct_drive = drivesEqual(dr, res_dr); + } + if (!correct_drive) { + continue; + } + } + var it = mem.split(p, "/\\"); + while (it.next()) |component| { + if (mem.eql(u8, component, ".")) { + continue; + } else if (mem.eql(u8, component, "..")) { + while (true) { + if (result_index == 0 or result_index == rootSlice.len) + break; + result_index -= 1; + if (result[result_index] == '\\' or result[result_index] == '/') + break; + } + } else { + result[result_index] = preferred_path_sep; + result_index += 1; + mem.copy(u8, result[result_index..], component); + result_index += component.len; + } + } + } + + return result[0..result_index]; +} + +pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { + if (paths.len == 0) { + assert(!is_windows); // resolvePosix called on windows can't use getCwd + return os.getCwd(allocator); + } var first_index: usize = 0; var have_abs = false; var max_size: usize = 0; for (paths) |p, i| { - if (isAbsolute(p)) { + if (isAbsolutePosix(p)) { first_index = i; have_abs = true; max_size = 0; @@ -80,6 +377,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { if (have_abs) { result = %return allocator.alloc(u8, max_size); } else { + assert(!is_windows); // resolvePosix called on windows can't use getCwd const cwd = %return os.getCwd(allocator); defer allocator.free(cwd); result = %return allocator.alloc(u8, max_size + cwd.len + 1); @@ -89,7 +387,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { %defer allocator.free(result); for (paths[first_index..]) |p, i| { - var it = mem.split(p, '/'); + var it = mem.split(p, "/"); while (it.next()) |component| { if (mem.eql(u8, component, ".")) { continue; @@ -119,22 +417,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { } test "os.path.resolve" { - assert(mem.eql(u8, testResolve("/a/b", "c"), "/a/b/c")); - assert(mem.eql(u8, testResolve("/a/b", "c", "//d", "e///"), "/d/e")); - assert(mem.eql(u8, testResolve("/a/b/c", "..", "../"), "/a")); - assert(mem.eql(u8, testResolve("/", "..", ".."), "/")); - assert(mem.eql(u8, testResolve("/a/b/c/"), "/a/b/c")); + const cwd = %%os.getCwd(&debug.global_allocator); + if (is_windows) { + assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd)); + } else { + assert(mem.eql(u8, testResolvePosix([][]const u8{"a/b/c/", "../../.."}), cwd)); + assert(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd)); + } } -fn testResolve(args: ...) -> []u8 { - return %%resolve(&debug.global_allocator, args); + +test "os.path.resolveWindows" { + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "c:\\blah\\a")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "c:\\blah\\a")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "d:\\e.exe")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "c:\\some\\file")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "d:\\ignore\\some\\dir")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"//server/share", "..", "relative\\"}), "\\\\server\\share\\relative")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "c:\\")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "c:\\dir")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server/share"}), "\\\\server\\share\\")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server//share"}), "\\\\server\\share\\")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "c:\\some\\dir")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"}), + "C:\\foo\\tmp.3\\cycles\\root.js")); +} + +test "os.path.resolvePosix" { + assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b", "c"}), "/a/b/c")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b", "c", "//d", "e///"}), "/d/e")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c", "..", "../"}), "/a")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/", "..", ".."}), "/")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c")); + + assert(mem.eql(u8, testResolvePosix([][]const u8{"/var/lib", "../", "file/"}), "/var/file")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/var/lib", "/../", "file/"}), "/file")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/some/dir", ".", "/absolute/"}), "/absolute")); + assert(mem.eql(u8, testResolvePosix([][]const u8{"/foo/tmp.3/", "../tmp.3/cycles/root.js"}), "/foo/tmp.3/cycles/root.js")); +} + +fn testResolveWindows(paths: []const []const u8) -> []u8 { + return %%resolveWindows(&debug.global_allocator, paths); +} + +fn testResolvePosix(paths: []const []const u8) -> []u8 { + return %%resolvePosix(&debug.global_allocator, paths); } pub fn dirname(path: []const u8) -> []const u8 { - if (builtin.os == builtin.Os.windows) { - @compileError("TODO implement os.path.dirname for windows"); + if (is_windows) { + return dirnameWindows(path); + } else { + return dirnamePosix(path); } +} + +pub fn dirnameWindows(path: []const u8) -> []const u8 { if (path.len == 0) return path[0..0]; + + const rootSlice = rootWindows(path); + if (path.len == rootSlice.len) + return path; + + const have_root_slash = path.len > rootSlice.len and (path[rootSlice.len] == '/' or path[rootSlice.len] == '\\'); + + var end_index: usize = path.len - 1; + + while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > rootSlice.len) { + if (end_index == 0) + return path[0..0]; + end_index -= 1; + } + + while (path[end_index] != '/' and path[end_index] != '\\' and end_index > rootSlice.len) { + if (end_index == 0) + return path[0..0]; + end_index -= 1; + } + + if (have_root_slash and end_index == rootSlice.len) { + end_index += 1; + } + + return path[0..end_index]; +} + +pub fn dirnamePosix(path: []const u8) -> []const u8 { + if (path.len == 0) + return path[0..0]; + var end_index: usize = path.len - 1; while (path[end_index] == '/') { if (end_index == 0) @@ -154,19 +525,60 @@ pub fn dirname(path: []const u8) -> []const u8 { return path[0..end_index]; } -test "os.path.dirname" { - testDirname("/a/b/c", "/a/b"); - testDirname("/a/b/c///", "/a/b"); - testDirname("/a", "/"); - testDirname("/", "/"); - testDirname("////", "/"); - testDirname("", ""); - testDirname("a", ""); - testDirname("a/", ""); - testDirname("a//", ""); +test "os.path.dirnamePosix" { + testDirnamePosix("/a/b/c", "/a/b"); + testDirnamePosix("/a/b/c///", "/a/b"); + testDirnamePosix("/a", "/"); + testDirnamePosix("/", "/"); + testDirnamePosix("////", "/"); + testDirnamePosix("", ""); + testDirnamePosix("a", ""); + testDirnamePosix("a/", ""); + testDirnamePosix("a//", ""); } -fn testDirname(input: []const u8, expected_output: []const u8) { - assert(mem.eql(u8, dirname(input), expected_output)); + +test "os.path.dirnameWindows" { + testDirnameWindows("c:\\", "c:\\"); + testDirnameWindows("c:\\foo", "c:\\"); + testDirnameWindows("c:\\foo\\", "c:\\"); + testDirnameWindows("c:\\foo\\bar", "c:\\foo"); + testDirnameWindows("c:\\foo\\bar\\", "c:\\foo"); + testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar"); + testDirnameWindows("\\", "\\"); + testDirnameWindows("\\foo", "\\"); + testDirnameWindows("\\foo\\", "\\"); + testDirnameWindows("\\foo\\bar", "\\foo"); + testDirnameWindows("\\foo\\bar\\", "\\foo"); + testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar"); + testDirnameWindows("c:", "c:"); + testDirnameWindows("c:foo", "c:"); + testDirnameWindows("c:foo\\", "c:"); + testDirnameWindows("c:foo\\bar", "c:foo"); + testDirnameWindows("c:foo\\bar\\", "c:foo"); + testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar"); + testDirnameWindows("file:stream", ""); + testDirnameWindows("dir\\file:stream", "dir"); + testDirnameWindows("\\\\unc\\share", "\\\\unc\\share"); + testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\"); + testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\"); + testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo"); + testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo"); + testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar"); + testDirnameWindows("/a/b/", "/a"); + testDirnameWindows("/a/b", "/a"); + testDirnameWindows("/a", "/"); + testDirnameWindows("", ""); + testDirnameWindows("/", "/"); + testDirnameWindows("////", "/"); + testDirnameWindows("foo", ""); +} + +fn testDirnamePosix(input: []const u8, expected_output: []const u8) { + assert(mem.eql(u8, dirnamePosix(input), expected_output)); +} + +fn testDirnameWindows(input: []const u8, expected_output: []const u8) { + assert(mem.eql(u8, dirnameWindows(input), expected_output)); } pub fn basename(path: []const u8) -> []const u8 { @@ -215,9 +627,18 @@ fn testBasename(input: []const u8, expected_output: []const u8) { /// resolve to the same path (after calling ::resolve on each), a zero-length /// string is returned. pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { - if (builtin.os == builtin.Os.windows) { - @compileError("TODO implement os.path.relative for windows"); + if (is_windows) { + return windowsRelative(allocator, from, to); + } else { + return posixRelative(allocator, from, to); } +} + +fn windowsRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { + @compileError("TODO implement this"); +} + +fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { const resolved_from = %return resolve(allocator, from); defer allocator.free(resolved_from); @@ -263,18 +684,45 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u } test "os.path.relative" { - testRelative("/var/lib", "/var", ".."); - testRelative("/var/lib", "/bin", "../../bin"); - testRelative("/var/lib", "/var/lib", ""); - testRelative("/var/lib", "/var/apache", "../apache"); - testRelative("/var/", "/var/lib", "lib"); - testRelative("/", "/var/lib", "var/lib"); - testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); - testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); - testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); - testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); - testRelative("/baz-quux", "/baz", "../baz"); - testRelative("/baz", "/baz-quux", "../baz-quux"); + if (is_windows) { + testRelative("c:/blah\\blah", "d:/games", "d:\\games"); + testRelative("c:/aaaa/bbbb", "c:/aaaa", ".."); + testRelative("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); + testRelative("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); + testRelative("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); + testRelative("c:/aaaa/", "c:/aaaa/cccc", "cccc"); + testRelative("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); + testRelative("c:/aaaa/bbbb", "d:\\", "d:\\"); + testRelative("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); + testRelative("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); + testRelative("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); + testRelative("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); + testRelative("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); + testRelative("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); + testRelative("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); + testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); + testRelative("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); + testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); + testRelative("C:\\baz-quux", "C:\\baz", "..\\baz"); + testRelative("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); + testRelative("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"); + testRelative("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"); + testRelative("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); + testRelative("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz") + } else { + testRelative("/var/lib", "/var", ".."); + testRelative("/var/lib", "/bin", "../../bin"); + testRelative("/var/lib", "/var/lib", ""); + testRelative("/var/lib", "/var/apache", "../apache"); + testRelative("/var/", "/var/lib", "lib"); + testRelative("/", "/var/lib", "var/lib"); + testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); + testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); + testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); + testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); + testRelative("/baz-quux", "/baz", "../baz"); + testRelative("/baz", "/baz-quux", "../baz-quux"); + } } fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) { const result = %%relative(&debug.global_allocator, from, to); From dcf5c9074e8a0feab204200d74831fda10e6c625 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Oct 2017 17:21:21 -0400 Subject: [PATCH 3/6] more std.os.path work for windows --- std/build.zig | 4 +- std/mem.zig | 12 +++- std/os/index.zig | 2 +- std/os/path.zig | 168 +++++++++++++++++++++++++++-------------------- 4 files changed, 111 insertions(+), 75 deletions(-) diff --git a/std/build.zig b/std/build.zig index 98cc78e211..526e2c3cf9 100644 --- a/std/build.zig +++ b/std/build.zig @@ -309,7 +309,7 @@ pub const Builder = struct { fn processNixOSEnvVars(self: &Builder) { if (os.getEnv("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { - var it = mem.split(nix_cflags_compile, ' '); + var it = mem.split(nix_cflags_compile, " "); while (true) { const word = it.next() ?? break; if (mem.eql(u8, word, "-isystem")) { @@ -325,7 +325,7 @@ pub const Builder = struct { } } if (os.getEnv("NIX_LDFLAGS")) |nix_ldflags| { - var it = mem.split(nix_ldflags, ' '); + var it = mem.split(nix_ldflags, " "); while (true) { const word = it.next() ?? break; if (mem.eql(u8, word, "-rpath")) { diff --git a/std/mem.zig b/std/mem.zig index bc25c6e706..9aca27ed69 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -324,7 +324,7 @@ pub fn split(buffer: []const u8, split_bytes: []const u8) -> SplitIterator { } test "mem.split" { - var it = split(" abc def ghi ", ' '); + var it = split(" abc def ghi ", " "); assert(eql(u8, ??it.next(), "abc")); assert(eql(u8, ??it.next(), "def")); assert(eql(u8, ??it.next(), "ghi")); @@ -355,7 +355,15 @@ const SplitIterator = struct { return self.buffer[start..end]; } - fn isSplitByte(self: &SplitIterator, byte: u8) -> bool { + /// Returns a slice of the remaining bytes. Does not affect iterator state. + pub fn rest(self: &const SplitIterator) -> []const u8 { + // move to beginning of token + var index: usize = self.index; + while (index < self.buffer.len and self.isSplitByte(self.buffer[index])) : (index += 1) {} + return self.buffer[index..]; + } + + fn isSplitByte(self: &const SplitIterator, byte: u8) -> bool { for (self.split_bytes) |split_byte| { if (byte == split_byte) { return true; diff --git a/std/os/index.zig b/std/os/index.zig index 3065396fa0..e98b6d4b5a 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -384,7 +384,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, // +1 for the null terminating byte const path_buf = %return allocator.alloc(u8, PATH.len + exe_path.len + 2); defer allocator.free(path_buf); - var it = mem.split(PATH, ':'); + var it = mem.split(PATH, ":"); var seen_eacces = false; var err: usize = undefined; while (it.next()) |search_path| { diff --git a/std/os/path.zig b/std/os/path.zig index bce866a5fd..bb4334228a 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -139,18 +139,18 @@ pub fn networkShare(path: []const u8) -> ?[]const u8 { if (path.len < "//a/b".len) return null; + // TODO when I combined these together with `inline for` the compiler crashed { const this_sep = '/'; const two_sep = []u8{this_sep, this_sep}; if (mem.startsWith(u8, path, two_sep)) { if (path[2] == this_sep) return null; - const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null; - const next_start = index_host + 1; - if (next_start >= path.len) - return null; - const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len; - return path[0..index_root]; + + var it = mem.split(path, []u8{this_sep}); + _ = (it.next() ?? return null); + _ = (it.next() ?? return null); + return path[0..it.index]; } } { @@ -159,12 +159,11 @@ pub fn networkShare(path: []const u8) -> ?[]const u8 { if (mem.startsWith(u8, path, two_sep)) { if (path[2] == this_sep) return null; - const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null; - const next_start = index_host + 1; - if (next_start >= path.len) - return null; - const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len; - return path[0..index_root]; + + var it = mem.split(path, []u8{this_sep}); + _ = (it.next() ?? return null); + _ = (it.next() ?? return null); + return path[0..it.index]; } } return null; @@ -177,23 +176,6 @@ test "os.path.networkShare" { assert(networkShare("\\\\a\\") == null); } -pub fn preferredSepWindows(path: []const u8) -> u8 { - for (path) |byte| { - if (byte == '/' or byte == '\\') { - return byte; - } - } - return sep_windows; -} - -pub fn preferredSep(path: []const u8) -> u8 { - if (is_windows) { - return preferredSepWindows(path); - } else { - return sep_posix; - } -} - pub fn root(path: []const u8) -> []const u8 { if (is_windows) { return rootWindows(path); @@ -206,7 +188,7 @@ pub fn rootWindows(path: []const u8) -> []const u8 { return drive(path) ?? (networkShare(path) ?? []u8{}); } -pub fn rootPosix(path: []const u8) -> ?[]const u8 { +pub fn rootPosix(path: []const u8) -> []const u8 { if (path.len == 0 or path[0] != '/') return []u8{}; @@ -218,18 +200,17 @@ pub fn drivesEqual(drive1: []const u8, drive2: []const u8) -> bool { assert(drive2.len == 2); assert(drive1[1] == ':'); assert(drive2[1] == ':'); - return asciiLower(drive1[0]) == asciiLower(drive2[0]); + return asciiUpper(drive1[0]) == asciiUpper(drive2[0]); } -fn asciiLower(byte: u8) -> u8 { +fn asciiUpper(byte: u8) -> u8 { return switch (byte) { - 'A' ... 'Z' => 'a' + (byte - 'A'), + 'a' ... 'z' => 'A' + (byte - 'a'), else => byte, }; } -/// This function is like a series of `cd` statements executed one after another. -/// The result does not have a trailing path separator. +/// Converts the command line arguments into a slice and calls `resolveSlice`. pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { var paths: [args.len][]const u8 = undefined; comptime var arg_i = 0; @@ -239,6 +220,7 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { return resolveSlice(allocator, paths); } +/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { if (is_windows) { return resolveWindows(allocator, paths); @@ -247,6 +229,12 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { } } +/// This function is like a series of `cd` statements executed one after another. +/// It resolves "." and "..". +/// The result does not have a trailing path separator. +/// If all paths are relative it uses the current working directory as a starting point. +/// Each drive has its own current working directory. +/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { if (paths.len == 0) { assert(is_windows); // resolveWindows called on non windows can't use getCwd @@ -254,7 +242,7 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 } // determine which drive we want to result with - var result_drive: ?[]const u8 = null; + var result_drive_upcase: ?u8 = null; var have_abs = false; var first_index: usize = 0; var max_size: usize = 0; @@ -266,25 +254,28 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 max_size = 0; } if (drive(p)) |d| { - result_drive = d; - } else if (is_abs) { - result_drive = null; + result_drive_upcase = asciiUpper(d[0]); + } else if (networkShare(p)) |_| { + result_drive_upcase = null; } max_size += p.len + 1; } + // if we will result with a drive, loop again to determine // which is the first time the drive is absolutely specified, if any // and count up the max bytes for paths related to this drive - if (result_drive) |res_dr| { + if (result_drive_upcase) |res_dr| { have_abs = false; first_index = 0; - max_size = 0; + max_size = "_:".len; var correct_drive = false; for (paths) |p, i| { if (drive(p)) |dr| { - correct_drive = drivesEqual(dr, res_dr); + correct_drive = asciiUpper(dr[0]) == res_dr; + } else if (networkShare(p)) |_| { + continue; } if (!correct_drive) { continue; @@ -292,20 +283,46 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 const is_abs = isAbsoluteWindows(p); if (is_abs) { first_index = i; - max_size = 0; + max_size = "_:".len; + have_abs = true; } max_size += p.len + 1; } } + var drive_buf = "_:"; var result: []u8 = undefined; var result_index: usize = 0; + var root_slice: []const u8 = undefined; if (have_abs) { result = %return allocator.alloc(u8, max_size); - mem.copy(u8, result, paths[first_index]); - result_index += paths[first_index].len; - first_index += 1; + + if (result_drive_upcase) |res_dr| { + drive_buf[0] = res_dr; + root_slice = drive_buf[0..]; + + mem.copy(u8, result, root_slice); + result_index += root_slice.len; + } else { + // We know it looks like //a/b or \\a\b because of earlier code + var it = mem.split(paths[first_index], "/\\"); + const server_name = ??it.next(); + const other_name = ??it.next(); + + result[result_index] = '\\'; + result_index += 1; + result[result_index] = '\\'; + result_index += 1; + mem.copy(u8, result[result_index..], server_name); + result_index += server_name.len; + result[result_index] = '\\'; + result_index += 1; + mem.copy(u8, result[result_index..], other_name); + result_index += other_name.len; + + root_slice = result[0..result_index]; + } } else { assert(is_windows); // resolveWindows called on non windows can't use getCwd // TODO get cwd for result_drive if applicable @@ -314,35 +331,37 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 result = %return allocator.alloc(u8, max_size + cwd.len + 1); mem.copy(u8, result, cwd); result_index += cwd.len; + + root_slice = rootWindows(result[0..result_index]); } %defer allocator.free(result); - var correct_drive = false; - const rootSlice = rootWindows(result[0..result_index]); - const preferred_path_sep = preferredSepWindows(result[0..result_index]); + var correct_drive = true; for (paths[first_index..]) |p, i| { - if (result_drive) |res_dr| { + if (result_drive_upcase) |res_dr| { if (drive(p)) |dr| { - correct_drive = drivesEqual(dr, res_dr); + correct_drive = asciiUpper(dr[0]) == res_dr; + } else if (networkShare(p)) |_| { + continue; } if (!correct_drive) { continue; } } - var it = mem.split(p, "/\\"); + var it = mem.split(p[rootWindows(p).len..], "/\\"); while (it.next()) |component| { if (mem.eql(u8, component, ".")) { continue; } else if (mem.eql(u8, component, "..")) { while (true) { - if (result_index == 0 or result_index == rootSlice.len) + if (result_index == 0 or result_index == root_slice.len) break; result_index -= 1; if (result[result_index] == '\\' or result[result_index] == '/') break; } } else { - result[result_index] = preferred_path_sep; + result[result_index] = sep_windows; result_index += 1; mem.copy(u8, result[result_index..], component); result_index += component.len; @@ -350,9 +369,18 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 } } + if (result_index == root_slice.len) { + result[result_index] = '\\'; + result_index += 1; + } + return result[0..result_index]; } +/// This function is like a series of `cd` statements executed one after another. +/// It resolves "." and "..". +/// The result does not have a trailing path separator. +/// If all paths are relative it uses the current working directory as a starting point. pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { if (paths.len == 0) { assert(!is_windows); // resolvePosix called on windows can't use getCwd @@ -427,17 +455,17 @@ test "os.path.resolve" { } test "os.path.resolveWindows" { - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "c:\\blah\\a")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "c:\\blah\\a")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "d:\\e.exe")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "c:\\some\\file")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "d:\\ignore\\some\\dir")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "C:\\blah\\a")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "C:\\blah\\a")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "D:\\e.exe")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "C:\\some\\file")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "D:\\ignore\\some\\dir")); assert(mem.eql(u8, testResolveWindows([][]const u8{"//server/share", "..", "relative\\"}), "\\\\server\\share\\relative")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "c:\\")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "c:\\dir")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "C:\\")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "C:\\dir")); assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server/share"}), "\\\\server\\share\\")); assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server//share"}), "\\\\server\\share\\")); - assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "c:\\some\\dir")); + assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "C:\\some\\dir")); assert(mem.eql(u8, testResolveWindows([][]const u8{"C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"}), "C:\\foo\\tmp.3\\cycles\\root.js")); } @@ -475,27 +503,27 @@ pub fn dirnameWindows(path: []const u8) -> []const u8 { if (path.len == 0) return path[0..0]; - const rootSlice = rootWindows(path); - if (path.len == rootSlice.len) + const root_slice = rootWindows(path); + if (path.len == root_slice.len) return path; - const have_root_slash = path.len > rootSlice.len and (path[rootSlice.len] == '/' or path[rootSlice.len] == '\\'); + const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\'); var end_index: usize = path.len - 1; - while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > rootSlice.len) { + while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) { if (end_index == 0) return path[0..0]; end_index -= 1; } - while (path[end_index] != '/' and path[end_index] != '\\' and end_index > rootSlice.len) { + while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) { if (end_index == 0) return path[0..0]; end_index -= 1; } - if (have_root_slash and end_index == rootSlice.len) { + if (have_root_slash and end_index == root_slice.len) { end_index += 1; } @@ -645,8 +673,8 @@ fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[] const resolved_to = %return resolve(allocator, to); defer allocator.free(resolved_to); - var from_it = mem.split(resolved_from, '/'); - var to_it = mem.split(resolved_to, '/'); + var from_it = mem.split(resolved_from, "/"); + var to_it = mem.split(resolved_to, "/"); while (true) { const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest()); const to_rest = to_it.rest(); From e15e1e09f087b8d1f4d80d90a88e2944ead13332 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Oct 2017 19:18:47 -0400 Subject: [PATCH 4/6] os.path.basename implementation for windows --- std/os/path.zig | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/std/os/path.zig b/std/os/path.zig index bb4334228a..133b3d70ef 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -610,9 +610,14 @@ fn testDirnameWindows(input: []const u8, expected_output: []const u8) { } pub fn basename(path: []const u8) -> []const u8 { - if (builtin.os == builtin.Os.windows) { - @compileError("TODO implement os.path.basename for windows"); + if (is_windows) { + return basenameWindows(path); + } else { + return basenamePosix(path); } +} + +pub fn basenamePosix(path: []const u8) -> []const u8 { if (path.len == 0) return []u8{}; @@ -633,6 +638,38 @@ pub fn basename(path: []const u8) -> []const u8 { return path[start_index + 1..end_index]; } +pub fn basenameWindows(path: []const u8) -> []const u8 { + if (path.len == 0) + return []u8{}; + + var end_index: usize = path.len - 1; + while (true) { + const byte = path[end_index]; + if (byte == '/' or byte == '\\') { + if (end_index == 0) + return []u8{}; + end_index -= 1; + continue; + } + if (byte == ':' and end_index == 1) { + return []u8{}; + } + break; + } + + var start_index: usize = end_index; + end_index += 1; + while (path[start_index] != '/' and path[start_index] != '\\' and + !(path[start_index] == ':' and start_index == 1)) + { + if (start_index == 0) + return path[0..end_index]; + start_index -= 1; + } + + return path[start_index + 1..end_index]; +} + test "os.path.basename" { testBasename("", ""); testBasename("/", ""); @@ -646,11 +683,44 @@ test "os.path.basename" { testBasename("/aaa/b", "b"); testBasename("/a/b", "b"); testBasename("//a", "a"); + + testBasenamePosix("\\dir\\basename.ext", "\\dir\\basename.ext"); + testBasenamePosix("\\basename.ext", "\\basename.ext"); + testBasenamePosix("basename.ext", "basename.ext"); + testBasenamePosix("basename.ext\\", "basename.ext\\"); + testBasenamePosix("basename.ext\\\\", "basename.ext\\\\"); + testBasenamePosix("foo", "foo"); + + testBasenameWindows("\\dir\\basename.ext", "basename.ext"); + testBasenameWindows("\\basename.ext", "basename.ext"); + testBasenameWindows("basename.ext", "basename.ext"); + testBasenameWindows("basename.ext\\", "basename.ext"); + testBasenameWindows("basename.ext\\\\", "basename.ext"); + testBasenameWindows("foo", "foo"); + testBasenameWindows("C:", ""); + testBasenameWindows("C:.", "."); + testBasenameWindows("C:\\", ""); + testBasenameWindows("C:\\dir\\base.ext", "base.ext"); + testBasenameWindows("C:\\basename.ext", "basename.ext"); + testBasenameWindows("C:basename.ext", "basename.ext"); + testBasenameWindows("C:basename.ext\\", "basename.ext"); + testBasenameWindows("C:basename.ext\\\\", "basename.ext"); + testBasenameWindows("C:foo", "foo"); + testBasenameWindows("file:stream", "file:stream"); } + fn testBasename(input: []const u8, expected_output: []const u8) { assert(mem.eql(u8, basename(input), expected_output)); } +fn testBasenamePosix(input: []const u8, expected_output: []const u8) { + assert(mem.eql(u8, basenamePosix(input), expected_output)); +} + +fn testBasenameWindows(input: []const u8, expected_output: []const u8) { + assert(mem.eql(u8, basenameWindows(input), expected_output)); +} + /// Returns the relative path from ::from to ::to. If ::from and ::to each /// resolve to the same path (after calling ::resolve on each), a zero-length /// string is returned. From 7cfab2fb5f0f0dce81feab9dd8ad3d3e1b435044 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Oct 2017 21:16:10 -0400 Subject: [PATCH 5/6] implement std.os.relative for windows --- std/os/index.zig | 13 +++ std/os/path.zig | 203 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 158 insertions(+), 58 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index e98b6d4b5a..fa51065299 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -1043,3 +1043,16 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void { else => error.Unexpected, }; } + +test "std.os" { + _ = @import("child_process.zig"); + _ = @import("darwin_errno.zig"); + _ = @import("darwin.zig"); + _ = @import("get_user_id.zig"); + _ = @import("linux_errno.zig"); + //_ = @import("linux_i386.zig"); + _ = @import("linux_x86_64.zig"); + _ = @import("linux.zig"); + _ = @import("path.zig"); + _ = @import("windows/index.zig"); +} diff --git a/std/os/path.zig b/std/os/path.zig index 133b3d70ef..8c00ae7a5e 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -195,12 +195,15 @@ pub fn rootPosix(path: []const u8) -> []const u8 { return path[0..1]; } -pub fn drivesEqual(drive1: []const u8, drive2: []const u8) -> bool { - assert(drive1.len == 2); - assert(drive2.len == 2); - assert(drive1[1] == ':'); - assert(drive2[1] == ':'); - return asciiUpper(drive1[0]) == asciiUpper(drive2[0]); +// TODO ASCII is wrong, we actually need full unicode support to compare paths. +fn networkShareServersEql(ns1: []const u8, ns2: []const u8) -> bool { + const sep1 = ns1[0]; + const sep2 = ns2[0]; + + var it1 = mem.split(ns1, []u8{sep1}); + var it2 = mem.split(ns2, []u8{sep2}); + + return asciiEqlIgnoreCase(??it1.next(), ??it2.next()); } fn asciiUpper(byte: u8) -> u8 { @@ -210,6 +213,17 @@ fn asciiUpper(byte: u8) -> u8 { }; } +fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) -> bool { + if (s1.len != s2.len) + return false; + var i: usize = 0; + while (i < s1.len) : (i += 1) { + if (asciiUpper(s1[i]) != asciiUpper(s2[i])) + return false; + } + return true; +} + /// Converts the command line arguments into a slice and calls `resolveSlice`. pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { var paths: [args.len][]const u8 = undefined; @@ -721,26 +735,95 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) { assert(mem.eql(u8, basenameWindows(input), expected_output)); } -/// Returns the relative path from ::from to ::to. If ::from and ::to each -/// resolve to the same path (after calling ::resolve on each), a zero-length +/// Returns the relative path from `from` to `to`. If `from` and `to` each +/// resolve to the same path (after calling `resolve` on each), a zero-length /// string is returned. +/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`. pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { if (is_windows) { - return windowsRelative(allocator, from, to); + return relativeWindows(allocator, from, to); } else { - return posixRelative(allocator, from, to); + return relativePosix(allocator, from, to); } } -fn windowsRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { - @compileError("TODO implement this"); -} - -fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { - const resolved_from = %return resolve(allocator, from); +pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { + const resolved_from = %return resolveWindows(allocator, [][]const u8{from}); defer allocator.free(resolved_from); - const resolved_to = %return resolve(allocator, to); + var clean_up_resolved_to = true; + const resolved_to = %return resolveWindows(allocator, [][]const u8{to}); + defer if (clean_up_resolved_to) allocator.free(resolved_to); + + const result_is_to = if (drive(resolved_to)) |to_drive| { + if (drive(resolved_from)) |from_drive| { + asciiUpper(from_drive[0]) != asciiUpper(to_drive[0]) + } else { + true + } + } else if (networkShare(resolved_to)) |to_ns| { + if (networkShare(resolved_from)) |from_ns| { + !networkShareServersEql(to_ns, from_ns) + } else { + true + } + } else { + unreachable + }; + if (result_is_to) { + clean_up_resolved_to = false; + return resolved_to; + } + + var from_it = mem.split(resolved_from, "/\\"); + var to_it = mem.split(resolved_to, "/\\"); + while (true) { + const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest()); + const to_rest = to_it.rest(); + if (to_it.next()) |to_component| { + // TODO ASCII is wrong, we actually need full unicode support to compare paths. + if (asciiEqlIgnoreCase(from_component, to_component)) + continue; + } + var up_count: usize = 1; + while (from_it.next()) |_| { + up_count += 1; + } + const up_index_end = up_count * "..\\".len; + const result = %return allocator.alloc(u8, up_index_end + to_rest.len); + %defer allocator.free(result); + + var result_index: usize = 0; + while (result_index < up_index_end) { + result[result_index] = '.'; + result_index += 1; + result[result_index] = '.'; + result_index += 1; + result[result_index] = '\\'; + result_index += 1; + } + // shave off the trailing slash + result_index -= 1; + + var rest_it = mem.split(to_rest, "/\\"); + while (rest_it.next()) |to_component| { + result[result_index] = '\\'; + result_index += 1; + mem.copy(u8, result[result_index..], to_component); + result_index += to_component.len; + } + + return result[0..result_index]; + } + + return []u8{}; +} + +pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { + const resolved_from = %return resolvePosix(allocator, [][]const u8{from}); + defer allocator.free(resolved_from); + + const resolved_to = %return resolvePosix(allocator, [][]const u8{to}); defer allocator.free(resolved_to); var from_it = mem.split(resolved_from, "/"); @@ -782,48 +865,52 @@ fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[] } test "os.path.relative" { - if (is_windows) { - testRelative("c:/blah\\blah", "d:/games", "d:\\games"); - testRelative("c:/aaaa/bbbb", "c:/aaaa", ".."); - testRelative("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); - testRelative("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); - testRelative("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); - testRelative("c:/aaaa/", "c:/aaaa/cccc", "cccc"); - testRelative("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); - testRelative("c:/aaaa/bbbb", "d:\\", "d:\\"); - testRelative("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); - testRelative("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); - testRelative("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); - testRelative("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); - testRelative("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); - testRelative("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); - testRelative("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); - testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); - testRelative("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); - testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); - testRelative("C:\\baz-quux", "C:\\baz", "..\\baz"); - testRelative("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); - testRelative("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"); - testRelative("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"); - testRelative("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); - testRelative("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz") - } else { - testRelative("/var/lib", "/var", ".."); - testRelative("/var/lib", "/bin", "../../bin"); - testRelative("/var/lib", "/var/lib", ""); - testRelative("/var/lib", "/var/apache", "../apache"); - testRelative("/var/", "/var/lib", "lib"); - testRelative("/", "/var/lib", "var/lib"); - testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); - testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); - testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); - testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); - testRelative("/baz-quux", "/baz", "../baz"); - testRelative("/baz", "/baz-quux", "../baz-quux"); - } + testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); + testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); + testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); + testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); + testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); + testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc"); + testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); + testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\"); + testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); + testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); + testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); + testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); + testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); + testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); + testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); + testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); + testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); + testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); + testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz"); + testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); + testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"); + testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"); + testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); + testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"); + + testRelativePosix("/var/lib", "/var", ".."); + testRelativePosix("/var/lib", "/bin", "../../bin"); + testRelativePosix("/var/lib", "/var/lib", ""); + testRelativePosix("/var/lib", "/var/apache", "../apache"); + testRelativePosix("/var/", "/var/lib", "lib"); + testRelativePosix("/", "/var/lib", "var/lib"); + testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); + testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); + testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); + testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); + testRelativePosix("/baz-quux", "/baz", "../baz"); + testRelativePosix("/baz", "/baz-quux", "../baz-quux"); } -fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) { - const result = %%relative(&debug.global_allocator, from, to); + +fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) { + const result = %%relativePosix(&debug.global_allocator, from, to); + assert(mem.eql(u8, result, expected_output)); +} + +fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) { + const result = %%relativeWindows(&debug.global_allocator, from, to); assert(mem.eql(u8, result, expected_output)); } From 9d5f15fe3da40813527d19c75b3633dcf2d00b0b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Oct 2017 21:40:33 -0400 Subject: [PATCH 6/6] implement os.getCwd for windows --- std/os/index.zig | 21 +++++++++++++++++---- std/os/path.zig | 24 ++++++------------------ std/os/windows/index.zig | 5 ++++- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index fa51065299..d4681eaa15 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -476,10 +476,23 @@ pub const args = struct { pub fn getCwd(allocator: &Allocator) -> %[]u8 { switch (builtin.os) { Os.windows => { - @panic("implement getCwd for windows"); - //if (windows.GetCurrentDirectoryA(buf_len(out_cwd), buf_ptr(out_cwd)) == 0) { - // zig_panic("GetCurrentDirectory failed"); - //} + var buf = %return allocator.alloc(u8, 256); + %defer allocator.free(buf); + + while (true) { + const result = windows.GetCurrentDirectoryA(windows.WORD(buf.len), buf.ptr); + + if (result == 0) { + return error.Unexpected; + } + + if (result > buf.len) { + buf = %return allocator.realloc(u8, buf, result); + continue; + } + + return buf[0..result]; + } }, else => { var buf = %return allocator.alloc(u8, 1024); diff --git a/std/os/path.zig b/std/os/path.zig index 8c00ae7a5e..eea0df461a 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -176,25 +176,13 @@ test "os.path.networkShare" { assert(networkShare("\\\\a\\") == null); } -pub fn root(path: []const u8) -> []const u8 { - if (is_windows) { - return rootWindows(path); - } else { - return rootPosix(path); - } -} +pub fn diskDesignator(path: []const u8) -> []const u8 { + if (!is_windows) + return ""; -pub fn rootWindows(path: []const u8) -> []const u8 { return drive(path) ?? (networkShare(path) ?? []u8{}); } -pub fn rootPosix(path: []const u8) -> []const u8 { - if (path.len == 0 or path[0] != '/') - return []u8{}; - - return path[0..1]; -} - // TODO ASCII is wrong, we actually need full unicode support to compare paths. fn networkShareServersEql(ns1: []const u8, ns2: []const u8) -> bool { const sep1 = ns1[0]; @@ -346,7 +334,7 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 mem.copy(u8, result, cwd); result_index += cwd.len; - root_slice = rootWindows(result[0..result_index]); + root_slice = diskDesignator(result[0..result_index]); } %defer allocator.free(result); @@ -362,7 +350,7 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 continue; } } - var it = mem.split(p[rootWindows(p).len..], "/\\"); + var it = mem.split(p[diskDesignator(p).len..], "/\\"); while (it.next()) |component| { if (mem.eql(u8, component, ".")) { continue; @@ -517,7 +505,7 @@ pub fn dirnameWindows(path: []const u8) -> []const u8 { if (path.len == 0) return path[0..0]; - const root_slice = rootWindows(path); + const root_slice = diskDesignator(path); if (path.len == root_slice.len) return path; diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index 8922de8599..83e96d6777 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -13,6 +13,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLine() -> LPTSTR; pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: &DWORD) -> bool; +pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPTSTR) -> DWORD; + /// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis. /// Multiple threads do not overwrite each other's last-error code. pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD; @@ -50,7 +52,7 @@ pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lp pub const PROV_RSA_FULL = 1; pub const UNICODE = false; -pub const LPTSTR = if (unicode) LPWSTR else LPSTR; +pub const LPTSTR = if (UNICODE) LPWSTR else LPSTR; pub const LPWSTR = &WCHAR; pub const LPSTR = &CHAR; pub const CHAR = u8; @@ -59,6 +61,7 @@ pub const SIZE_T = usize; pub const BOOL = bool; pub const BYTE = u8; +pub const WORD = u16; pub const DWORD = u32; pub const FLOAT = f32; pub const HANDLE = &c_void;