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);