authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-08 21:45:04-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-08 21:45:04-04:00
log987e0f5acb4b16f8b7c10dd4ff43256674bc5065
tree06cc475a5440708b2fd00fd1b0994dc39ca75a75
parentb4e42042cf27177479f448248601b9be92ca6345
parent9d5f15fe3da40813527d19c75b3633dcf2d00b0b

Merge branch 'windows-paths'


6 files changed, 822 insertions(+), 109 deletions(-)

README.md+40-4
...@@ -43,14 +43,50 @@ clarity....@@ -43,14 +43,50 @@ clarity.
43 * Cross-compiling is a primary use case.43 * Cross-compiling is a primary use case.
44 * In addition to creating executables, creating a C library is a primary use44 * In addition to creating executables, creating a C library is a primary use
45 case. You can export an auto-generated .h file.45 case. You can export an auto-generated .h file.
46 * Standard library supports Operating System abstractions for:
47 * `x86_64` `linux`
48 * `x86_64` `macos`
49 * Support for all popular operating systems and architectures is planned.
50 * For OS development, Zig supports all architectures that LLVM does. All the46 * For OS development, Zig supports all architectures that LLVM does. All the
51 standard library that does not depend on an OS is available to you in47 standard library that does not depend on an OS is available to you in
52 freestanding mode.48 freestanding mode.
5349
50### Support Table
51
52Freestanding means that you do not directly interact with the OS
53or you are writing your own OS.
54
55Note that if you use libc or other libraries to interact with the OS,
56that counts as "freestanding" for the purposes of this table.
57
58| | freestanding | linux | macosx | windows | other |
59|-------------|--------------|---------|---------|---------|---------|
60|i386 | OK | planned | OK | OK | planned |
61|x86_64 | OK | OK | OK | OK | planned |
62|arm | OK | planned | planned | N/A | planned |
63|aarch64 | OK | planned | planned | planned | planned |
64|avr | OK | planned | planned | N/A | planned |
65|bpf | OK | planned | planned | N/A | planned |
66|hexagon | OK | planned | planned | N/A | planned |
67|mips | OK | planned | planned | N/A | planned |
68|msp430 | OK | planned | planned | N/A | planned |
69|nios2 | OK | planned | planned | N/A | planned |
70|powerpc | OK | planned | planned | N/A | planned |
71|r600 | OK | planned | planned | N/A | planned |
72|amdgcn | OK | planned | planned | N/A | planned |
73|riscv | OK | planned | planned | N/A | planned |
74|sparc | OK | planned | planned | N/A | planned |
75|s390x | OK | planned | planned | N/A | planned |
76|tce | OK | planned | planned | N/A | planned |
77|thumb | OK | planned | planned | N/A | planned |
78|xcore | OK | planned | planned | N/A | planned |
79|nvptx | OK | planned | planned | N/A | planned |
80|le | OK | planned | planned | N/A | planned |
81|amdil | OK | planned | planned | N/A | planned |
82|hsail | OK | planned | planned | N/A | planned |
83|spir | OK | planned | planned | N/A | planned |
84|kalimba | OK | planned | planned | N/A | planned |
85|shave | OK | planned | planned | N/A | planned |
86|lanai | OK | planned | planned | N/A | planned |
87|wasm | OK | N/A | N/A | N/A | N/A |
88|renderscript | OK | N/A | N/A | N/A | N/A |
89
54## Community90## Community
5591
56 * IRC: `#zig` on Freenode.92 * IRC: `#zig` on Freenode.
std/build.zig+2-2
...@@ -309,7 +309,7 @@ pub const Builder = struct {...@@ -309,7 +309,7 @@ pub const Builder = struct {
309309
310 fn processNixOSEnvVars(self: &Builder) {310 fn processNixOSEnvVars(self: &Builder) {
311 if (os.getEnv("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {311 if (os.getEnv("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
312 var it = mem.split(nix_cflags_compile, ' ');312 var it = mem.split(nix_cflags_compile, " ");
313 while (true) {313 while (true) {
314 const word = it.next() ?? break;314 const word = it.next() ?? break;
315 if (mem.eql(u8, word, "-isystem")) {315 if (mem.eql(u8, word, "-isystem")) {
...@@ -325,7 +325,7 @@ pub const Builder = struct {...@@ -325,7 +325,7 @@ pub const Builder = struct {
325 }325 }
326 }326 }
327 if (os.getEnv("NIX_LDFLAGS")) |nix_ldflags| {327 if (os.getEnv("NIX_LDFLAGS")) |nix_ldflags| {
328 var it = mem.split(nix_ldflags, ' ');328 var it = mem.split(nix_ldflags, " ");
329 while (true) {329 while (true) {
330 const word = it.next() ?? break;330 const word = it.next() ?? break;
331 if (mem.eql(u8, word, "-rpath")) {331 if (mem.eql(u8, word, "-rpath")) {
std/mem.zig+37-20
...@@ -216,20 +216,28 @@ pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) -> %[]T {...@@ -216,20 +216,28 @@ pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) -> %[]T {
216216
217/// Linear search for the index of a scalar value inside a slice.217/// Linear search for the index of a scalar value inside a slice.
218pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {218pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
219 for (slice) |item, i| {219 return indexOfScalarPos(T, slice, 0, value);
220 if (item == value) {220}
221
222pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize, value: T) -> ?usize {
223 var i: usize = start_index;
224 while (i < slice.len) : (i += 1) {
225 if (slice[i] == value)
221 return i;226 return i;
222 }
223 }227 }
224 return null;228 return null;
225}229}
226230
227// TODO boyer-moore algorithm
228pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {231pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {
232 return indexOfPos(T, haystack, 0, needle);
233}
234
235// TODO boyer-moore algorithm
236pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) -> ?usize {
229 if (needle.len > haystack.len)237 if (needle.len > haystack.len)
230 return null;238 return null;
231239
232 var i: usize = 0;240 var i: usize = start_index;
233 const end = haystack.len - needle.len;241 const end = haystack.len - needle.len;
234 while (i <= end) : (i += 1) {242 while (i <= end) : (i += 1) {
235 if (eql(T, haystack[i .. i + needle.len], needle))243 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 {...@@ -303,20 +311,20 @@ pub fn eql_slice_u8(a: []const u8, b: []const u8) -> bool {
303 return eql(u8, a, b);311 return eql(u8, a, b);
304}312}
305313
306/// Returns an iterator that iterates over the slices of ::s that are not314/// Returns an iterator that iterates over the slices of `buffer` that are not
307/// the byte ::c.315/// any of the bytes in `split_bytes`.
308/// split(" abc def ghi ")316/// split(" abc def ghi ", " ")
309/// Will return slices for "abc", "def", "ghi", null, in that order.317/// Will return slices for "abc", "def", "ghi", null, in that order.
310pub fn split(s: []const u8, c: u8) -> SplitIterator {318pub fn split(buffer: []const u8, split_bytes: []const u8) -> SplitIterator {
311 SplitIterator {319 SplitIterator {
312 .index = 0,320 .index = 0,
313 .s = s,321 .buffer = buffer,
314 .c = c,322 .split_bytes = split_bytes,
315 }323 }
316}324}
317325
318test "mem.split" {326test "mem.split" {
319 var it = split(" abc def ghi ", ' ');327 var it = split(" abc def ghi ", " ");
320 assert(eql(u8, ??it.next(), "abc"));328 assert(eql(u8, ??it.next(), "abc"));
321 assert(eql(u8, ??it.next(), "def"));329 assert(eql(u8, ??it.next(), "def"));
322 assert(eql(u8, ??it.next(), "ghi"));330 assert(eql(u8, ??it.next(), "ghi"));
...@@ -328,31 +336,40 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> b...@@ -328,31 +336,40 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> b
328}336}
329337
330const SplitIterator = struct {338const SplitIterator = struct {
331 s: []const u8,339 buffer: []const u8,
332 c: u8,340 split_bytes: []const u8,
333 index: usize,341 index: usize,
334342
335 pub fn next(self: &SplitIterator) -> ?[]const u8 {343 pub fn next(self: &SplitIterator) -> ?[]const u8 {
336 // move to beginning of token344 // move to beginning of token
337 while (self.index < self.s.len and self.s[self.index] == self.c) : (self.index += 1) {}345 while (self.index < self.buffer.len and self.isSplitByte(self.buffer[self.index])) : (self.index += 1) {}
338 const start = self.index;346 const start = self.index;
339 if (start == self.s.len) {347 if (start == self.buffer.len) {
340 return null;348 return null;
341 }349 }
342350
343 // move to end of token351 // move to end of token
344 while (self.index < self.s.len and self.s[self.index] != self.c) : (self.index += 1) {}352 while (self.index < self.buffer.len and !self.isSplitByte(self.buffer[self.index])) : (self.index += 1) {}
345 const end = self.index;353 const end = self.index;
346354
347 return self.s[start..end];355 return self.buffer[start..end];
348 }356 }
349357
350 /// Returns a slice of the remaining bytes. Does not affect iterator state.358 /// Returns a slice of the remaining bytes. Does not affect iterator state.
351 pub fn rest(self: &const SplitIterator) -> []const u8 {359 pub fn rest(self: &const SplitIterator) -> []const u8 {
352 // move to beginning of token360 // move to beginning of token
353 var index: usize = self.index;361 var index: usize = self.index;
354 while (index < self.s.len and self.s[index] == self.c) : (index += 1) {}362 while (index < self.buffer.len and self.isSplitByte(self.buffer[index])) : (index += 1) {}
355 return self.s[index..];363 return self.buffer[index..];
364 }
365
366 fn isSplitByte(self: &const SplitIterator, byte: u8) -> bool {
367 for (self.split_bytes) |split_byte| {
368 if (byte == split_byte) {
369 return true;
370 }
371 }
372 return false;
356 }373 }
357};374};
358375
std/os/index.zig+48-12
...@@ -384,7 +384,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,...@@ -384,7 +384,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
384 // +1 for the null terminating byte384 // +1 for the null terminating byte
385 const path_buf = %return allocator.alloc(u8, PATH.len + exe_path.len + 2);385 const path_buf = %return allocator.alloc(u8, PATH.len + exe_path.len + 2);
386 defer allocator.free(path_buf);386 defer allocator.free(path_buf);
387 var it = mem.split(PATH, ':');387 var it = mem.split(PATH, ":");
388 var seen_eacces = false;388 var seen_eacces = false;
389 var err: usize = undefined;389 var err: usize = undefined;
390 while (it.next()) |search_path| {390 while (it.next()) |search_path| {
...@@ -474,18 +474,41 @@ pub const args = struct {...@@ -474,18 +474,41 @@ pub const args = struct {
474474
475/// Caller must free the returned memory.475/// Caller must free the returned memory.
476pub fn getCwd(allocator: &Allocator) -> %[]u8 {476pub fn getCwd(allocator: &Allocator) -> %[]u8 {
477 var buf = %return allocator.alloc(u8, 1024);477 switch (builtin.os) {
478 %defer allocator.free(buf);478 Os.windows => {
479 while (true) {479 var buf = %return allocator.alloc(u8, 256);
480 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));480 %defer allocator.free(buf);
481 if (err == posix.ERANGE) {481
482 buf = %return allocator.realloc(u8, buf, buf.len * 2);482 while (true) {
483 continue;483 const result = windows.GetCurrentDirectoryA(windows.WORD(buf.len), buf.ptr);
484 } else if (err > 0) {484
485 return error.Unexpected;485 if (result == 0) {
486 }486 return error.Unexpected;
487 }
488
489 if (result > buf.len) {
490 buf = %return allocator.realloc(u8, buf, result);
491 continue;
492 }
493
494 return buf[0..result];
495 }
496 },
497 else => {
498 var buf = %return allocator.alloc(u8, 1024);
499 %defer allocator.free(buf);
500 while (true) {
501 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
502 if (err == posix.ERANGE) {
503 buf = %return allocator.realloc(u8, buf, buf.len * 2);
504 continue;
505 } else if (err > 0) {
506 return error.Unexpected;
507 }
487508
488 return cstr.toSlice(buf.ptr);509 return cstr.toSlice(buf.ptr);
510 }
511 },
489 }512 }
490}513}
491514
...@@ -1033,3 +1056,16 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {...@@ -1033,3 +1056,16 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
1033 else => error.Unexpected,1056 else => error.Unexpected,
1034 };1057 };
1035}1058}
1059
1060test "std.os" {
1061 _ = @import("child_process.zig");
1062 _ = @import("darwin_errno.zig");
1063 _ = @import("darwin.zig");
1064 _ = @import("get_user_id.zig");
1065 _ = @import("linux_errno.zig");
1066 //_ = @import("linux_i386.zig");
1067 _ = @import("linux_x86_64.zig");
1068 _ = @import("linux.zig");
1069 _ = @import("path.zig");
1070 _ = @import("windows/index.zig");
1071}
std/os/path.zig+691-70
...@@ -11,41 +11,208 @@ const posix = os.posix;...@@ -11,41 +11,208 @@ const posix = os.posix;
11const c = @import("../c/index.zig");11const c = @import("../c/index.zig");
12const cstr = @import("../cstr.zig");12const cstr = @import("../cstr.zig");
1313
14pub const sep = switch (builtin.os) {14pub const sep_windows = '\\';
15 Os.windows => '\\',15pub const sep_posix = '/';
16 else => '/',16pub const sep = if (is_windows) sep_windows else sep_posix;
17};17
18pub const delimiter = switch (builtin.os) {18pub const delimiter_windows = ';';
19 Os.windows => ';',19pub const delimiter_posix = ':';
20 else => ':',20pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
21};21
22const is_windows = builtin.os == builtin.Os.windows;
2223
23/// Naively combines a series of paths with the native path seperator.24/// Naively combines a series of paths with the native path seperator.
24/// Allocates memory for the result, which must be freed by the caller.25/// Allocates memory for the result, which must be freed by the caller.
25pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 {26pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 {
26 mem.join(allocator, sep, paths)27 if (is_windows) {
28 return joinWindows(allocator, paths);
29 } else {
30 return joinPosix(allocator, paths);
31 }
32}
33
34pub fn joinWindows(allocator: &Allocator, paths: ...) -> %[]u8 {
35 return mem.join(allocator, sep_windows, paths);
36}
37
38pub fn joinPosix(allocator: &Allocator, paths: ...) -> %[]u8 {
39 return mem.join(allocator, sep_posix, paths);
27}40}
2841
29test "os.path.join" {42test "os.path.join" {
30 assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c"));43 assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\a\\b", "c"), "c:\\a\\b\\c"));
31 assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b/", "c"), "/a/b/c"));44 assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\a\\b\\", "c"), "c:\\a\\b\\c"));
3245
33 assert(mem.eql(u8, %%join(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c"));46 assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\", "a", "b\\", "c"), "c:\\a\\b\\c"));
34 assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c"));47 assert(mem.eql(u8, %%joinWindows(&debug.global_allocator, "c:\\a\\", "b\\", "c"), "c:\\a\\b\\c"));
3548
36 assert(mem.eql(u8, %%join(&debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"),49 assert(mem.eql(u8, %%joinWindows(&debug.global_allocator,
50 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig"),
51 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig"));
52
53 assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/a/b", "c"), "/a/b/c"));
54 assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/a/b/", "c"), "/a/b/c"));
55
56 assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c"));
57 assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c"));
58
59 assert(mem.eql(u8, %%joinPosix(&debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"),
37 "/home/andy/dev/zig/build/lib/zig/std/io.zig"));60 "/home/andy/dev/zig/build/lib/zig/std/io.zig"));
38}61}
3962
40pub fn isAbsolute(path: []const u8) -> bool {63pub fn isAbsolute(path: []const u8) -> bool {
41 switch (builtin.os) {64 if (is_windows) {
42 Os.windows => @compileError("Unsupported OS"),65 return isAbsoluteWindows(path);
43 else => return path[0] == sep,66 } else {
67 return isAbsolutePosix(path);
44 }68 }
45}69}
4670
47/// This function is like a series of `cd` statements executed one after another.71pub fn isAbsoluteWindows(path: []const u8) -> bool {
48/// The result does not have a trailing path separator.72 if (path[0] == '/')
73 return true;
74
75 if (path[0] == '\\') {
76 return true;
77 }
78 if (path.len < 3) {
79 return false;
80 }
81 if (path[1] == ':') {
82 if (path[2] == '/')
83 return true;
84 if (path[2] == '\\')
85 return true;
86 }
87 return false;
88}
89
90pub fn isAbsolutePosix(path: []const u8) -> bool {
91 return path[0] == sep_posix;
92}
93
94test "os.path.isAbsoluteWindows" {
95 testIsAbsoluteWindows("/", true);
96 testIsAbsoluteWindows("//", true);
97 testIsAbsoluteWindows("//server", true);
98 testIsAbsoluteWindows("//server/file", true);
99 testIsAbsoluteWindows("\\\\server\\file", true);
100 testIsAbsoluteWindows("\\\\server", true);
101 testIsAbsoluteWindows("\\\\", true);
102 testIsAbsoluteWindows("c", false);
103 testIsAbsoluteWindows("c:", false);
104 testIsAbsoluteWindows("c:\\", true);
105 testIsAbsoluteWindows("c:/", true);
106 testIsAbsoluteWindows("c://", true);
107 testIsAbsoluteWindows("C:/Users/", true);
108 testIsAbsoluteWindows("C:\\Users\\", true);
109 testIsAbsoluteWindows("C:cwd/another", false);
110 testIsAbsoluteWindows("C:cwd\\another", false);
111 testIsAbsoluteWindows("directory/directory", false);
112 testIsAbsoluteWindows("directory\\directory", false);
113}
114
115test "os.path.isAbsolutePosix" {
116 testIsAbsolutePosix("/home/foo", true);
117 testIsAbsolutePosix("/home/foo/..", true);
118 testIsAbsolutePosix("bar/", false);
119 testIsAbsolutePosix("./baz", false);
120}
121
122fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) {
123 assert(isAbsoluteWindows(path) == expected_result);
124}
125
126fn testIsAbsolutePosix(path: []const u8, expected_result: bool) {
127 assert(isAbsolutePosix(path) == expected_result);
128}
129
130pub fn drive(path: []const u8) -> ?[]const u8 {
131 if (path.len < 2)
132 return null;
133 if (path[1] != ':')
134 return null;
135 return path[0..2];
136}
137
138pub fn networkShare(path: []const u8) -> ?[]const u8 {
139 if (path.len < "//a/b".len)
140 return null;
141
142 // TODO when I combined these together with `inline for` the compiler crashed
143 {
144 const this_sep = '/';
145 const two_sep = []u8{this_sep, this_sep};
146 if (mem.startsWith(u8, path, two_sep)) {
147 if (path[2] == this_sep)
148 return null;
149
150 var it = mem.split(path, []u8{this_sep});
151 _ = (it.next() ?? return null);
152 _ = (it.next() ?? return null);
153 return path[0..it.index];
154 }
155 }
156 {
157 const this_sep = '\\';
158 const two_sep = []u8{this_sep, this_sep};
159 if (mem.startsWith(u8, path, two_sep)) {
160 if (path[2] == this_sep)
161 return null;
162
163 var it = mem.split(path, []u8{this_sep});
164 _ = (it.next() ?? return null);
165 _ = (it.next() ?? return null);
166 return path[0..it.index];
167 }
168 }
169 return null;
170}
171
172test "os.path.networkShare" {
173 assert(mem.eql(u8, ??networkShare("//a/b"), "//a/b"));
174 assert(mem.eql(u8, ??networkShare("\\\\a\\b"), "\\\\a\\b"));
175
176 assert(networkShare("\\\\a\\") == null);
177}
178
179pub fn diskDesignator(path: []const u8) -> []const u8 {
180 if (!is_windows)
181 return "";
182
183 return drive(path) ?? (networkShare(path) ?? []u8{});
184}
185
186// TODO ASCII is wrong, we actually need full unicode support to compare paths.
187fn networkShareServersEql(ns1: []const u8, ns2: []const u8) -> bool {
188 const sep1 = ns1[0];
189 const sep2 = ns2[0];
190
191 var it1 = mem.split(ns1, []u8{sep1});
192 var it2 = mem.split(ns2, []u8{sep2});
193
194 return asciiEqlIgnoreCase(??it1.next(), ??it2.next());
195}
196
197fn asciiUpper(byte: u8) -> u8 {
198 return switch (byte) {
199 'a' ... 'z' => 'A' + (byte - 'a'),
200 else => byte,
201 };
202}
203
204fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) -> bool {
205 if (s1.len != s2.len)
206 return false;
207 var i: usize = 0;
208 while (i < s1.len) : (i += 1) {
209 if (asciiUpper(s1[i]) != asciiUpper(s2[i]))
210 return false;
211 }
212 return true;
213}
214
215/// Converts the command line arguments into a slice and calls `resolveSlice`.
49pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {216pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {
50 var paths: [args.len][]const u8 = undefined;217 var paths: [args.len][]const u8 = undefined;
51 comptime var arg_i = 0;218 comptime var arg_i = 0;
...@@ -55,18 +222,178 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {...@@ -55,18 +222,178 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {
55 return resolveSlice(allocator, paths);222 return resolveSlice(allocator, paths);
56}223}
57224
225/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
58pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {226pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
59 if (builtin.os == builtin.Os.windows) {227 if (is_windows) {
60 @compileError("TODO implement os.path.resolve for windows");228 return resolveWindows(allocator, paths);
229 } else {
230 return resolvePosix(allocator, paths);
61 }231 }
62 if (paths.len == 0)232}
233
234/// This function is like a series of `cd` statements executed one after another.
235/// It resolves "." and "..".
236/// The result does not have a trailing path separator.
237/// If all paths are relative it uses the current working directory as a starting point.
238/// Each drive has its own current working directory.
239/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
240pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
241 if (paths.len == 0) {
242 assert(is_windows); // resolveWindows called on non windows can't use getCwd
63 return os.getCwd(allocator);243 return os.getCwd(allocator);
244 }
245
246 // determine which drive we want to result with
247 var result_drive_upcase: ?u8 = null;
248 var have_abs = false;
249 var first_index: usize = 0;
250 var max_size: usize = 0;
251 for (paths) |p, i| {
252 const is_abs = isAbsoluteWindows(p);
253 if (is_abs) {
254 have_abs = true;
255 first_index = i;
256 max_size = 0;
257 }
258 if (drive(p)) |d| {
259 result_drive_upcase = asciiUpper(d[0]);
260 } else if (networkShare(p)) |_| {
261 result_drive_upcase = null;
262 }
263 max_size += p.len + 1;
264 }
265
266
267 // if we will result with a drive, loop again to determine
268 // which is the first time the drive is absolutely specified, if any
269 // and count up the max bytes for paths related to this drive
270 if (result_drive_upcase) |res_dr| {
271 have_abs = false;
272 first_index = 0;
273 max_size = "_:".len;
274 var correct_drive = false;
275
276 for (paths) |p, i| {
277 if (drive(p)) |dr| {
278 correct_drive = asciiUpper(dr[0]) == res_dr;
279 } else if (networkShare(p)) |_| {
280 continue;
281 }
282 if (!correct_drive) {
283 continue;
284 }
285 const is_abs = isAbsoluteWindows(p);
286 if (is_abs) {
287 first_index = i;
288 max_size = "_:".len;
289 have_abs = true;
290 }
291 max_size += p.len + 1;
292 }
293 }
294
295 var drive_buf = "_:";
296 var result: []u8 = undefined;
297 var result_index: usize = 0;
298 var root_slice: []const u8 = undefined;
299
300 if (have_abs) {
301 result = %return allocator.alloc(u8, max_size);
302
303 if (result_drive_upcase) |res_dr| {
304 drive_buf[0] = res_dr;
305 root_slice = drive_buf[0..];
306
307 mem.copy(u8, result, root_slice);
308 result_index += root_slice.len;
309 } else {
310 // We know it looks like //a/b or \\a\b because of earlier code
311 var it = mem.split(paths[first_index], "/\\");
312 const server_name = ??it.next();
313 const other_name = ??it.next();
314
315 result[result_index] = '\\';
316 result_index += 1;
317 result[result_index] = '\\';
318 result_index += 1;
319 mem.copy(u8, result[result_index..], server_name);
320 result_index += server_name.len;
321 result[result_index] = '\\';
322 result_index += 1;
323 mem.copy(u8, result[result_index..], other_name);
324 result_index += other_name.len;
325
326 root_slice = result[0..result_index];
327 }
328 } else {
329 assert(is_windows); // resolveWindows called on non windows can't use getCwd
330 // TODO get cwd for result_drive if applicable
331 const cwd = %return os.getCwd(allocator);
332 defer allocator.free(cwd);
333 result = %return allocator.alloc(u8, max_size + cwd.len + 1);
334 mem.copy(u8, result, cwd);
335 result_index += cwd.len;
336
337 root_slice = diskDesignator(result[0..result_index]);
338 }
339 %defer allocator.free(result);
340
341 var correct_drive = true;
342 for (paths[first_index..]) |p, i| {
343 if (result_drive_upcase) |res_dr| {
344 if (drive(p)) |dr| {
345 correct_drive = asciiUpper(dr[0]) == res_dr;
346 } else if (networkShare(p)) |_| {
347 continue;
348 }
349 if (!correct_drive) {
350 continue;
351 }
352 }
353 var it = mem.split(p[diskDesignator(p).len..], "/\\");
354 while (it.next()) |component| {
355 if (mem.eql(u8, component, ".")) {
356 continue;
357 } else if (mem.eql(u8, component, "..")) {
358 while (true) {
359 if (result_index == 0 or result_index == root_slice.len)
360 break;
361 result_index -= 1;
362 if (result[result_index] == '\\' or result[result_index] == '/')
363 break;
364 }
365 } else {
366 result[result_index] = sep_windows;
367 result_index += 1;
368 mem.copy(u8, result[result_index..], component);
369 result_index += component.len;
370 }
371 }
372 }
373
374 if (result_index == root_slice.len) {
375 result[result_index] = '\\';
376 result_index += 1;
377 }
378
379 return result[0..result_index];
380}
381
382/// This function is like a series of `cd` statements executed one after another.
383/// It resolves "." and "..".
384/// The result does not have a trailing path separator.
385/// If all paths are relative it uses the current working directory as a starting point.
386pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
387 if (paths.len == 0) {
388 assert(!is_windows); // resolvePosix called on windows can't use getCwd
389 return os.getCwd(allocator);
390 }
64391
65 var first_index: usize = 0;392 var first_index: usize = 0;
66 var have_abs = false;393 var have_abs = false;
67 var max_size: usize = 0;394 var max_size: usize = 0;
68 for (paths) |p, i| {395 for (paths) |p, i| {
69 if (isAbsolute(p)) {396 if (isAbsolutePosix(p)) {
70 first_index = i;397 first_index = i;
71 have_abs = true;398 have_abs = true;
72 max_size = 0;399 max_size = 0;
...@@ -80,6 +407,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {...@@ -80,6 +407,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
80 if (have_abs) {407 if (have_abs) {
81 result = %return allocator.alloc(u8, max_size);408 result = %return allocator.alloc(u8, max_size);
82 } else {409 } else {
410 assert(!is_windows); // resolvePosix called on windows can't use getCwd
83 const cwd = %return os.getCwd(allocator);411 const cwd = %return os.getCwd(allocator);
84 defer allocator.free(cwd);412 defer allocator.free(cwd);
85 result = %return allocator.alloc(u8, max_size + cwd.len + 1);413 result = %return allocator.alloc(u8, max_size + cwd.len + 1);
...@@ -89,7 +417,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {...@@ -89,7 +417,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
89 %defer allocator.free(result);417 %defer allocator.free(result);
90418
91 for (paths[first_index..]) |p, i| {419 for (paths[first_index..]) |p, i| {
92 var it = mem.split(p, '/');420 var it = mem.split(p, "/");
93 while (it.next()) |component| {421 while (it.next()) |component| {
94 if (mem.eql(u8, component, ".")) {422 if (mem.eql(u8, component, ".")) {
95 continue;423 continue;
...@@ -119,22 +447,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {...@@ -119,22 +447,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
119}447}
120448
121test "os.path.resolve" {449test "os.path.resolve" {
122 assert(mem.eql(u8, testResolve("/a/b", "c"), "/a/b/c"));450 const cwd = %%os.getCwd(&debug.global_allocator);
123 assert(mem.eql(u8, testResolve("/a/b", "c", "//d", "e///"), "/d/e"));451 if (is_windows) {
124 assert(mem.eql(u8, testResolve("/a/b/c", "..", "../"), "/a"));452 assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
125 assert(mem.eql(u8, testResolve("/", "..", ".."), "/"));453 } else {
126 assert(mem.eql(u8, testResolve("/a/b/c/"), "/a/b/c"));454 assert(mem.eql(u8, testResolvePosix([][]const u8{"a/b/c/", "../../.."}), cwd));
455 assert(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd));
456 }
457}
458
459test "os.path.resolveWindows" {
460 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "C:\\blah\\a"));
461 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "C:\\blah\\a"));
462 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "D:\\e.exe"));
463 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "C:\\some\\file"));
464 assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "D:\\ignore\\some\\dir"));
465 assert(mem.eql(u8, testResolveWindows([][]const u8{"//server/share", "..", "relative\\"}), "\\\\server\\share\\relative"));
466 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "C:\\"));
467 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "C:\\dir"));
468 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server/share"}), "\\\\server\\share\\"));
469 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server//share"}), "\\\\server\\share\\"));
470 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "C:\\some\\dir"));
471 assert(mem.eql(u8, testResolveWindows([][]const u8{"C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"}),
472 "C:\\foo\\tmp.3\\cycles\\root.js"));
473}
474
475test "os.path.resolvePosix" {
476 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b", "c"}), "/a/b/c"));
477 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b", "c", "//d", "e///"}), "/d/e"));
478 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c", "..", "../"}), "/a"));
479 assert(mem.eql(u8, testResolvePosix([][]const u8{"/", "..", ".."}), "/"));
480 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c"));
481
482 assert(mem.eql(u8, testResolvePosix([][]const u8{"/var/lib", "../", "file/"}), "/var/file"));
483 assert(mem.eql(u8, testResolvePosix([][]const u8{"/var/lib", "/../", "file/"}), "/file"));
484 assert(mem.eql(u8, testResolvePosix([][]const u8{"/some/dir", ".", "/absolute/"}), "/absolute"));
485 assert(mem.eql(u8, testResolvePosix([][]const u8{"/foo/tmp.3/", "../tmp.3/cycles/root.js"}), "/foo/tmp.3/cycles/root.js"));
486}
487
488fn testResolveWindows(paths: []const []const u8) -> []u8 {
489 return %%resolveWindows(&debug.global_allocator, paths);
127}490}
128fn testResolve(args: ...) -> []u8 {491
129 return %%resolve(&debug.global_allocator, args);492fn testResolvePosix(paths: []const []const u8) -> []u8 {
493 return %%resolvePosix(&debug.global_allocator, paths);
130}494}
131495
132pub fn dirname(path: []const u8) -> []const u8 {496pub fn dirname(path: []const u8) -> []const u8 {
133 if (builtin.os == builtin.Os.windows) {497 if (is_windows) {
134 @compileError("TODO implement os.path.dirname for windows");498 return dirnameWindows(path);
499 } else {
500 return dirnamePosix(path);
135 }501 }
502}
503
504pub fn dirnameWindows(path: []const u8) -> []const u8 {
136 if (path.len == 0)505 if (path.len == 0)
137 return path[0..0];506 return path[0..0];
507
508 const root_slice = diskDesignator(path);
509 if (path.len == root_slice.len)
510 return path;
511
512 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
513
514 var end_index: usize = path.len - 1;
515
516 while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) {
517 if (end_index == 0)
518 return path[0..0];
519 end_index -= 1;
520 }
521
522 while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) {
523 if (end_index == 0)
524 return path[0..0];
525 end_index -= 1;
526 }
527
528 if (have_root_slash and end_index == root_slice.len) {
529 end_index += 1;
530 }
531
532 return path[0..end_index];
533}
534
535pub fn dirnamePosix(path: []const u8) -> []const u8 {
536 if (path.len == 0)
537 return path[0..0];
538
138 var end_index: usize = path.len - 1;539 var end_index: usize = path.len - 1;
139 while (path[end_index] == '/') {540 while (path[end_index] == '/') {
140 if (end_index == 0)541 if (end_index == 0)
...@@ -154,25 +555,71 @@ pub fn dirname(path: []const u8) -> []const u8 {...@@ -154,25 +555,71 @@ pub fn dirname(path: []const u8) -> []const u8 {
154 return path[0..end_index];555 return path[0..end_index];
155}556}
156557
157test "os.path.dirname" {558test "os.path.dirnamePosix" {
158 testDirname("/a/b/c", "/a/b");559 testDirnamePosix("/a/b/c", "/a/b");
159 testDirname("/a/b/c///", "/a/b");560 testDirnamePosix("/a/b/c///", "/a/b");
160 testDirname("/a", "/");561 testDirnamePosix("/a", "/");
161 testDirname("/", "/");562 testDirnamePosix("/", "/");
162 testDirname("////", "/");563 testDirnamePosix("////", "/");
163 testDirname("", "");564 testDirnamePosix("", "");
164 testDirname("a", "");565 testDirnamePosix("a", "");
165 testDirname("a/", "");566 testDirnamePosix("a/", "");
166 testDirname("a//", "");567 testDirnamePosix("a//", "");
167}568}
168fn testDirname(input: []const u8, expected_output: []const u8) {569
169 assert(mem.eql(u8, dirname(input), expected_output));570test "os.path.dirnameWindows" {
571 testDirnameWindows("c:\\", "c:\\");
572 testDirnameWindows("c:\\foo", "c:\\");
573 testDirnameWindows("c:\\foo\\", "c:\\");
574 testDirnameWindows("c:\\foo\\bar", "c:\\foo");
575 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
576 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
577 testDirnameWindows("\\", "\\");
578 testDirnameWindows("\\foo", "\\");
579 testDirnameWindows("\\foo\\", "\\");
580 testDirnameWindows("\\foo\\bar", "\\foo");
581 testDirnameWindows("\\foo\\bar\\", "\\foo");
582 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
583 testDirnameWindows("c:", "c:");
584 testDirnameWindows("c:foo", "c:");
585 testDirnameWindows("c:foo\\", "c:");
586 testDirnameWindows("c:foo\\bar", "c:foo");
587 testDirnameWindows("c:foo\\bar\\", "c:foo");
588 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
589 testDirnameWindows("file:stream", "");
590 testDirnameWindows("dir\\file:stream", "dir");
591 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");
592 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
593 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
594 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
595 testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo");
596 testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar");
597 testDirnameWindows("/a/b/", "/a");
598 testDirnameWindows("/a/b", "/a");
599 testDirnameWindows("/a", "/");
600 testDirnameWindows("", "");
601 testDirnameWindows("/", "/");
602 testDirnameWindows("////", "/");
603 testDirnameWindows("foo", "");
604}
605
606fn testDirnamePosix(input: []const u8, expected_output: []const u8) {
607 assert(mem.eql(u8, dirnamePosix(input), expected_output));
608}
609
610fn testDirnameWindows(input: []const u8, expected_output: []const u8) {
611 assert(mem.eql(u8, dirnameWindows(input), expected_output));
170}612}
171613
172pub fn basename(path: []const u8) -> []const u8 {614pub fn basename(path: []const u8) -> []const u8 {
173 if (builtin.os == builtin.Os.windows) {615 if (is_windows) {
174 @compileError("TODO implement os.path.basename for windows");616 return basenameWindows(path);
617 } else {
618 return basenamePosix(path);
175 }619 }
620}
621
622pub fn basenamePosix(path: []const u8) -> []const u8 {
176 if (path.len == 0)623 if (path.len == 0)
177 return []u8{};624 return []u8{};
178625
...@@ -193,6 +640,38 @@ pub fn basename(path: []const u8) -> []const u8 {...@@ -193,6 +640,38 @@ pub fn basename(path: []const u8) -> []const u8 {
193 return path[start_index + 1..end_index];640 return path[start_index + 1..end_index];
194}641}
195642
643pub fn basenameWindows(path: []const u8) -> []const u8 {
644 if (path.len == 0)
645 return []u8{};
646
647 var end_index: usize = path.len - 1;
648 while (true) {
649 const byte = path[end_index];
650 if (byte == '/' or byte == '\\') {
651 if (end_index == 0)
652 return []u8{};
653 end_index -= 1;
654 continue;
655 }
656 if (byte == ':' and end_index == 1) {
657 return []u8{};
658 }
659 break;
660 }
661
662 var start_index: usize = end_index;
663 end_index += 1;
664 while (path[start_index] != '/' and path[start_index] != '\\' and
665 !(path[start_index] == ':' and start_index == 1))
666 {
667 if (start_index == 0)
668 return path[0..end_index];
669 start_index -= 1;
670 }
671
672 return path[start_index + 1..end_index];
673}
674
196test "os.path.basename" {675test "os.path.basename" {
197 testBasename("", "");676 testBasename("", "");
198 testBasename("/", "");677 testBasename("/", "");
...@@ -206,26 +685,137 @@ test "os.path.basename" {...@@ -206,26 +685,137 @@ test "os.path.basename" {
206 testBasename("/aaa/b", "b");685 testBasename("/aaa/b", "b");
207 testBasename("/a/b", "b");686 testBasename("/a/b", "b");
208 testBasename("//a", "a");687 testBasename("//a", "a");
688
689 testBasenamePosix("\\dir\\basename.ext", "\\dir\\basename.ext");
690 testBasenamePosix("\\basename.ext", "\\basename.ext");
691 testBasenamePosix("basename.ext", "basename.ext");
692 testBasenamePosix("basename.ext\\", "basename.ext\\");
693 testBasenamePosix("basename.ext\\\\", "basename.ext\\\\");
694 testBasenamePosix("foo", "foo");
695
696 testBasenameWindows("\\dir\\basename.ext", "basename.ext");
697 testBasenameWindows("\\basename.ext", "basename.ext");
698 testBasenameWindows("basename.ext", "basename.ext");
699 testBasenameWindows("basename.ext\\", "basename.ext");
700 testBasenameWindows("basename.ext\\\\", "basename.ext");
701 testBasenameWindows("foo", "foo");
702 testBasenameWindows("C:", "");
703 testBasenameWindows("C:.", ".");
704 testBasenameWindows("C:\\", "");
705 testBasenameWindows("C:\\dir\\base.ext", "base.ext");
706 testBasenameWindows("C:\\basename.ext", "basename.ext");
707 testBasenameWindows("C:basename.ext", "basename.ext");
708 testBasenameWindows("C:basename.ext\\", "basename.ext");
709 testBasenameWindows("C:basename.ext\\\\", "basename.ext");
710 testBasenameWindows("C:foo", "foo");
711 testBasenameWindows("file:stream", "file:stream");
209}712}
713
210fn testBasename(input: []const u8, expected_output: []const u8) {714fn testBasename(input: []const u8, expected_output: []const u8) {
211 assert(mem.eql(u8, basename(input), expected_output));715 assert(mem.eql(u8, basename(input), expected_output));
212}716}
213717
214/// Returns the relative path from ::from to ::to. If ::from and ::to each718fn testBasenamePosix(input: []const u8, expected_output: []const u8) {
215/// resolve to the same path (after calling ::resolve on each), a zero-length719 assert(mem.eql(u8, basenamePosix(input), expected_output));
720}
721
722fn testBasenameWindows(input: []const u8, expected_output: []const u8) {
723 assert(mem.eql(u8, basenameWindows(input), expected_output));
724}
725
726/// Returns the relative path from `from` to `to`. If `from` and `to` each
727/// resolve to the same path (after calling `resolve` on each), a zero-length
216/// string is returned.728/// string is returned.
729/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
217pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {730pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
218 if (builtin.os == builtin.Os.windows) {731 if (is_windows) {
219 @compileError("TODO implement os.path.relative for windows");732 return relativeWindows(allocator, from, to);
733 } else {
734 return relativePosix(allocator, from, to);
220 }735 }
221 const resolved_from = %return resolve(allocator, from);736}
737
738pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
739 const resolved_from = %return resolveWindows(allocator, [][]const u8{from});
222 defer allocator.free(resolved_from);740 defer allocator.free(resolved_from);
223741
224 const resolved_to = %return resolve(allocator, to);742 var clean_up_resolved_to = true;
743 const resolved_to = %return resolveWindows(allocator, [][]const u8{to});
744 defer if (clean_up_resolved_to) allocator.free(resolved_to);
745
746 const result_is_to = if (drive(resolved_to)) |to_drive| {
747 if (drive(resolved_from)) |from_drive| {
748 asciiUpper(from_drive[0]) != asciiUpper(to_drive[0])
749 } else {
750 true
751 }
752 } else if (networkShare(resolved_to)) |to_ns| {
753 if (networkShare(resolved_from)) |from_ns| {
754 !networkShareServersEql(to_ns, from_ns)
755 } else {
756 true
757 }
758 } else {
759 unreachable
760 };
761 if (result_is_to) {
762 clean_up_resolved_to = false;
763 return resolved_to;
764 }
765
766 var from_it = mem.split(resolved_from, "/\\");
767 var to_it = mem.split(resolved_to, "/\\");
768 while (true) {
769 const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest());
770 const to_rest = to_it.rest();
771 if (to_it.next()) |to_component| {
772 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
773 if (asciiEqlIgnoreCase(from_component, to_component))
774 continue;
775 }
776 var up_count: usize = 1;
777 while (from_it.next()) |_| {
778 up_count += 1;
779 }
780 const up_index_end = up_count * "..\\".len;
781 const result = %return allocator.alloc(u8, up_index_end + to_rest.len);
782 %defer allocator.free(result);
783
784 var result_index: usize = 0;
785 while (result_index < up_index_end) {
786 result[result_index] = '.';
787 result_index += 1;
788 result[result_index] = '.';
789 result_index += 1;
790 result[result_index] = '\\';
791 result_index += 1;
792 }
793 // shave off the trailing slash
794 result_index -= 1;
795
796 var rest_it = mem.split(to_rest, "/\\");
797 while (rest_it.next()) |to_component| {
798 result[result_index] = '\\';
799 result_index += 1;
800 mem.copy(u8, result[result_index..], to_component);
801 result_index += to_component.len;
802 }
803
804 return result[0..result_index];
805 }
806
807 return []u8{};
808}
809
810pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
811 const resolved_from = %return resolvePosix(allocator, [][]const u8{from});
812 defer allocator.free(resolved_from);
813
814 const resolved_to = %return resolvePosix(allocator, [][]const u8{to});
225 defer allocator.free(resolved_to);815 defer allocator.free(resolved_to);
226816
227 var from_it = mem.split(resolved_from, '/');817 var from_it = mem.split(resolved_from, "/");
228 var to_it = mem.split(resolved_to, '/');818 var to_it = mem.split(resolved_to, "/");
229 while (true) {819 while (true) {
230 const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest());820 const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest());
231 const to_rest = to_it.rest();821 const to_rest = to_it.rest();
...@@ -263,21 +853,52 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u...@@ -263,21 +853,52 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u
263}853}
264854
265test "os.path.relative" {855test "os.path.relative" {
266 testRelative("/var/lib", "/var", "..");856 testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
267 testRelative("/var/lib", "/bin", "../../bin");857 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
268 testRelative("/var/lib", "/var/lib", "");858 testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
269 testRelative("/var/lib", "/var/apache", "../apache");859 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
270 testRelative("/var/", "/var/lib", "lib");860 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
271 testRelative("/", "/var/lib", "var/lib");861 testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
272 testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json");862 testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
273 testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");863 testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
274 testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");864 testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
275 testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");865 testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
276 testRelative("/baz-quux", "/baz", "../baz");866 testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
277 testRelative("/baz", "/baz-quux", "../baz-quux");867 testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
278}868 testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
279fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) {869 testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
280 const result = %%relative(&debug.global_allocator, from, to);870 testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
871 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
872 testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
873 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
874 testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
875 testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
876 testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz");
877 testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux");
878 testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
879 testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
880
881 testRelativePosix("/var/lib", "/var", "..");
882 testRelativePosix("/var/lib", "/bin", "../../bin");
883 testRelativePosix("/var/lib", "/var/lib", "");
884 testRelativePosix("/var/lib", "/var/apache", "../apache");
885 testRelativePosix("/var/", "/var/lib", "lib");
886 testRelativePosix("/", "/var/lib", "var/lib");
887 testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
888 testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
889 testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
890 testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
891 testRelativePosix("/baz-quux", "/baz", "../baz");
892 testRelativePosix("/baz", "/baz-quux", "../baz-quux");
893}
894
895fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) {
896 const result = %%relativePosix(&debug.global_allocator, from, to);
897 assert(mem.eql(u8, result, expected_output));
898}
899
900fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) {
901 const result = %%relativeWindows(&debug.global_allocator, from, to);
281 assert(mem.eql(u8, result, expected_output));902 assert(mem.eql(u8, result, expected_output));
282}903}
283904
std/os/windows/index.zig+4-1
...@@ -13,6 +13,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLine() -> LPTSTR;...@@ -13,6 +13,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLine() -> LPTSTR;
1313
14pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: &DWORD) -> bool;14pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: &DWORD) -> bool;
1515
16pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPTSTR) -> DWORD;
17
16/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis.18/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis.
17/// Multiple threads do not overwrite each other's last-error code.19/// Multiple threads do not overwrite each other's last-error code.
18pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;20pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;
...@@ -50,7 +52,7 @@ pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lp...@@ -50,7 +52,7 @@ pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lp
50pub const PROV_RSA_FULL = 1;52pub const PROV_RSA_FULL = 1;
5153
52pub const UNICODE = false;54pub const UNICODE = false;
53pub const LPTSTR = if (unicode) LPWSTR else LPSTR;55pub const LPTSTR = if (UNICODE) LPWSTR else LPSTR;
54pub const LPWSTR = &WCHAR;56pub const LPWSTR = &WCHAR;
55pub const LPSTR = &CHAR;57pub const LPSTR = &CHAR;
56pub const CHAR = u8;58pub const CHAR = u8;
...@@ -59,6 +61,7 @@ pub const SIZE_T = usize;...@@ -59,6 +61,7 @@ pub const SIZE_T = usize;
5961
60pub const BOOL = bool;62pub const BOOL = bool;
61pub const BYTE = u8;63pub const BYTE = u8;
64pub const WORD = u16;
62pub const DWORD = u32;65pub const DWORD = u32;
63pub const FLOAT = f32;66pub const FLOAT = f32;
64pub const HANDLE = &c_void;67pub const HANDLE = &c_void;