Merge branch 'windows-paths'

master
Andrew Kelley 2017-10-08 21:45:04 -04:00
commit 987e0f5acb
6 changed files with 821 additions and 108 deletions

View File

@ -43,14 +43,50 @@ 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.
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.

View File

@ -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")) {

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.
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,20 +311,20 @@ 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,
}
}
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"));
@ -328,31 +336,40 @@ 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..];
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;
}
}
return false;
}
};

View File

@ -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| {
@ -474,18 +474,41 @@ 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 => {
var buf = %return allocator.alloc(u8, 256);
%defer allocator.free(buf);
return cstr.toSlice(buf.ptr);
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);
%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);
}
},
}
}
@ -1033,3 +1056,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");
}

View File

@ -11,41 +11,208 @@ 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);
}
}
/// This function is like a series of `cd` statements executed one after another.
/// The result does not have a trailing path separator.
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;
// 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;
var it = mem.split(path, []u8{this_sep});
_ = (it.next() ?? return null);
_ = (it.next() ?? return null);
return path[0..it.index];
}
}
{
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;
var it = mem.split(path, []u8{this_sep});
_ = (it.next() ?? return null);
_ = (it.next() ?? return null);
return path[0..it.index];
}
}
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 diskDesignator(path: []const u8) -> []const u8 {
if (!is_windows)
return "";
return drive(path) ?? (networkShare(path) ?? []u8{});
}
// 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 {
return switch (byte) {
'a' ... 'z' => 'A' + (byte - 'a'),
else => byte,
};
}
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;
comptime var arg_i = 0;
@ -55,18 +222,178 @@ 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 (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)
}
/// 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
return os.getCwd(allocator);
}
// determine which drive we want to result with
var result_drive_upcase: ?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_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_upcase) |res_dr| {
have_abs = false;
first_index = 0;
max_size = "_:".len;
var correct_drive = false;
for (paths) |p, i| {
if (drive(p)) |dr| {
correct_drive = asciiUpper(dr[0]) == res_dr;
} else if (networkShare(p)) |_| {
continue;
}
if (!correct_drive) {
continue;
}
const is_abs = isAbsoluteWindows(p);
if (is_abs) {
first_index = i;
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);
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
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;
root_slice = diskDesignator(result[0..result_index]);
}
%defer allocator.free(result);
var correct_drive = true;
for (paths[first_index..]) |p, i| {
if (result_drive_upcase) |res_dr| {
if (drive(p)) |dr| {
correct_drive = asciiUpper(dr[0]) == res_dr;
} else if (networkShare(p)) |_| {
continue;
}
if (!correct_drive) {
continue;
}
}
var it = mem.split(p[diskDesignator(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 == root_slice.len)
break;
result_index -= 1;
if (result[result_index] == '\\' or result[result_index] == '/')
break;
}
} else {
result[result_index] = sep_windows;
result_index += 1;
mem.copy(u8, result[result_index..], component);
result_index += component.len;
}
}
}
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
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 +407,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 +417,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 +447,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 root_slice = diskDesignator(path);
if (path.len == root_slice.len)
return path;
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 > 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 > root_slice.len) {
if (end_index == 0)
return path[0..0];
end_index -= 1;
}
if (have_root_slash and end_index == root_slice.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,25 +555,71 @@ 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 {
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{};
@ -193,6 +640,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("/", "");
@ -206,26 +685,137 @@ 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));
}
/// 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
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.
/// 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 (builtin.os == builtin.Os.windows) {
@compileError("TODO implement os.path.relative for windows");
if (is_windows) {
return relativeWindows(allocator, from, to);
} else {
return relativePosix(allocator, from, to);
}
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, '/');
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();
@ -263,21 +853,52 @@ 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");
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));
}

View File

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