implement os.path.dirname for windows

This commit is contained in:
Andrew Kelley 2017-10-06 00:27:15 -04:00
parent 968ff38cad
commit 08ee69dac3
4 changed files with 559 additions and 95 deletions

View file

@ -43,15 +43,12 @@ clarity.
* Cross-compiling is a primary use case. * Cross-compiling is a primary use case.
* In addition to creating executables, creating a C library is a primary use * In addition to creating executables, creating a C library is a primary use
case. You can export an auto-generated .h file. 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 * 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 standard library that does not depend on an OS is available to you in
freestanding mode. freestanding mode.
### Support Table ### Support Table
Freestanding means that you do not directly interact with the OS Freestanding means that you do not directly interact with the OS
or you are writing your own OS. or you are writing your own OS.

View file

@ -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. /// Linear search for the index of a scalar value inside a slice.
pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize { pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
for (slice) |item, i| { return indexOfScalarPos(T, slice, 0, value);
if (item == 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 i;
}
} }
return null; return null;
} }
// TODO boyer-moore algorithm
pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize { 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) if (needle.len > haystack.len)
return null; return null;
var i: usize = 0; var i: usize = start_index;
const end = haystack.len - needle.len; const end = haystack.len - needle.len;
while (i <= end) : (i += 1) { while (i <= end) : (i += 1) {
if (eql(T, haystack[i .. i + needle.len], needle)) 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); return eql(u8, a, b);
} }
/// Returns an iterator that iterates over the slices of ::s that are not /// Returns an iterator that iterates over the slices of `buffer` that are not
/// the byte ::c. /// any of the bytes in `split_bytes`.
/// split(" abc def ghi ") /// split(" abc def ghi ", " ")
/// Will return slices for "abc", "def", "ghi", null, in that order. /// 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 { SplitIterator {
.index = 0, .index = 0,
.s = s, .buffer = buffer,
.c = c, .split_bytes = split_bytes,
} }
} }
@ -328,31 +336,32 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> b
} }
const SplitIterator = struct { const SplitIterator = struct {
s: []const u8, buffer: []const u8,
c: u8, split_bytes: []const u8,
index: usize, index: usize,
pub fn next(self: &SplitIterator) -> ?[]const u8 { pub fn next(self: &SplitIterator) -> ?[]const u8 {
// move to beginning of token // 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; const start = self.index;
if (start == self.s.len) { if (start == self.buffer.len) {
return null; return null;
} }
// move to end of token // 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; 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. fn isSplitByte(self: &SplitIterator, byte: u8) -> bool {
pub fn rest(self: &const SplitIterator) -> []const u8 { for (self.split_bytes) |split_byte| {
// move to beginning of token if (byte == split_byte) {
var index: usize = self.index; return true;
while (index < self.s.len and self.s[index] == self.c) : (index += 1) {} }
return self.s[index..]; }
return false;
} }
}; };

View file

@ -474,18 +474,28 @@ pub const args = struct {
/// Caller must free the returned memory. /// Caller must free the returned memory.
pub fn getCwd(allocator: &Allocator) -> %[]u8 { pub fn getCwd(allocator: &Allocator) -> %[]u8 {
var buf = %return allocator.alloc(u8, 1024); switch (builtin.os) {
%defer allocator.free(buf); Os.windows => {
while (true) { @panic("implement getCwd for windows");
const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len)); //if (windows.GetCurrentDirectoryA(buf_len(out_cwd), buf_ptr(out_cwd)) == 0) {
if (err == posix.ERANGE) { // zig_panic("GetCurrentDirectory failed");
buf = %return allocator.realloc(u8, buf, buf.len * 2); //}
continue; },
} else if (err > 0) { else => {
return error.Unexpected; 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);
}
},
} }
} }

View file

@ -11,39 +11,223 @@ const posix = os.posix;
const c = @import("../c/index.zig"); const c = @import("../c/index.zig");
const cstr = @import("../cstr.zig"); const cstr = @import("../cstr.zig");
pub const sep = switch (builtin.os) { pub const sep_windows = '\\';
Os.windows => '\\', pub const sep_posix = '/';
else => '/', pub const sep = if (is_windows) sep_windows else sep_posix;
};
pub const delimiter = switch (builtin.os) { pub const delimiter_windows = ';';
Os.windows => ';', pub const delimiter_posix = ':';
else => ':', 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. /// Naively combines a series of paths with the native path seperator.
/// Allocates memory for the result, which must be freed by the caller. /// Allocates memory for the result, which must be freed by the caller.
pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 { 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" { test "os.path.join" {
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, %%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, %%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, %%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, %%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")); "/home/andy/dev/zig/build/lib/zig/std/io.zig"));
} }
pub fn isAbsolute(path: []const u8) -> bool { pub fn isAbsolute(path: []const u8) -> bool {
switch (builtin.os) { if (is_windows) {
Os.windows => @compileError("Unsupported OS"), return isAbsoluteWindows(path);
else => return path[0] == sep, } 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. /// This function is like a series of `cd` statements executed one after another.
/// The result does not have a trailing path separator. /// The result does not have a trailing path separator.
pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { 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 { pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
if (builtin.os == builtin.Os.windows) { if (is_windows) {
@compileError("TODO implement os.path.resolve for 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); 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 first_index: usize = 0;
var have_abs = false; var have_abs = false;
var max_size: usize = 0; var max_size: usize = 0;
for (paths) |p, i| { for (paths) |p, i| {
if (isAbsolute(p)) { if (isAbsolutePosix(p)) {
first_index = i; first_index = i;
have_abs = true; have_abs = true;
max_size = 0; max_size = 0;
@ -80,6 +377,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
if (have_abs) { if (have_abs) {
result = %return allocator.alloc(u8, max_size); result = %return allocator.alloc(u8, max_size);
} else { } else {
assert(!is_windows); // resolvePosix called on windows can't use getCwd
const cwd = %return os.getCwd(allocator); const cwd = %return os.getCwd(allocator);
defer allocator.free(cwd); defer allocator.free(cwd);
result = %return allocator.alloc(u8, max_size + cwd.len + 1); 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); %defer allocator.free(result);
for (paths[first_index..]) |p, i| { for (paths[first_index..]) |p, i| {
var it = mem.split(p, '/'); var it = mem.split(p, "/");
while (it.next()) |component| { while (it.next()) |component| {
if (mem.eql(u8, component, ".")) { if (mem.eql(u8, component, ".")) {
continue; continue;
@ -119,22 +417,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
} }
test "os.path.resolve" { test "os.path.resolve" {
assert(mem.eql(u8, testResolve("/a/b", "c"), "/a/b/c")); const cwd = %%os.getCwd(&debug.global_allocator);
assert(mem.eql(u8, testResolve("/a/b", "c", "//d", "e///"), "/d/e")); if (is_windows) {
assert(mem.eql(u8, testResolve("/a/b/c", "..", "../"), "/a")); assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
assert(mem.eql(u8, testResolve("/", "..", ".."), "/")); } else {
assert(mem.eql(u8, testResolve("/a/b/c/"), "/a/b/c")); 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 { pub fn dirname(path: []const u8) -> []const u8 {
if (builtin.os == builtin.Os.windows) { if (is_windows) {
@compileError("TODO implement os.path.dirname for windows"); return dirnameWindows(path);
} else {
return dirnamePosix(path);
} }
}
pub fn dirnameWindows(path: []const u8) -> []const u8 {
if (path.len == 0) if (path.len == 0)
return path[0..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; var end_index: usize = path.len - 1;
while (path[end_index] == '/') { while (path[end_index] == '/') {
if (end_index == 0) if (end_index == 0)
@ -154,19 +525,60 @@ pub fn dirname(path: []const u8) -> []const u8 {
return path[0..end_index]; return path[0..end_index];
} }
test "os.path.dirname" { test "os.path.dirnamePosix" {
testDirname("/a/b/c", "/a/b"); testDirnamePosix("/a/b/c", "/a/b");
testDirname("/a/b/c///", "/a/b"); testDirnamePosix("/a/b/c///", "/a/b");
testDirname("/a", "/"); testDirnamePosix("/a", "/");
testDirname("/", "/"); testDirnamePosix("/", "/");
testDirname("////", "/"); testDirnamePosix("////", "/");
testDirname("", ""); testDirnamePosix("", "");
testDirname("a", ""); testDirnamePosix("a", "");
testDirname("a/", ""); testDirnamePosix("a/", "");
testDirname("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 { 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 /// resolve to the same path (after calling ::resolve on each), a zero-length
/// string is returned. /// string is returned.
pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
if (builtin.os == builtin.Os.windows) { if (is_windows) {
@compileError("TODO implement os.path.relative for 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); const resolved_from = %return resolve(allocator, from);
defer allocator.free(resolved_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" { test "os.path.relative" {
testRelative("/var/lib", "/var", ".."); if (is_windows) {
testRelative("/var/lib", "/bin", "../../bin"); testRelative("c:/blah\\blah", "d:/games", "d:\\games");
testRelative("/var/lib", "/var/lib", ""); testRelative("c:/aaaa/bbbb", "c:/aaaa", "..");
testRelative("/var/lib", "/var/apache", "../apache"); testRelative("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
testRelative("/var/", "/var/lib", "lib"); testRelative("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
testRelative("/", "/var/lib", "var/lib"); testRelative("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); testRelative("c:/aaaa/", "c:/aaaa/cccc", "cccc");
testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); testRelative("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); testRelative("c:/aaaa/bbbb", "d:\\", "d:\\");
testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); testRelative("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
testRelative("/baz-quux", "/baz", "../baz"); testRelative("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
testRelative("/baz", "/baz-quux", "../baz-quux"); 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) { fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) {
const result = %%relative(&debug.global_allocator, from, to); const result = %%relative(&debug.global_allocator, from, to);