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.
4343 * Cross-compiling is a primary use case.
4444 * In addition to creating executables, creating a C library is a primary use
4545 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.
5046 * For OS development, Zig supports all architectures that LLVM does. All the
5147 standard library that does not depend on an OS is available to you in
5248 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
5490## Community
5591
5692 * IRC: `#zig` on Freenode.
std/build.zig+2-2
......@@ -309,7 +309,7 @@ pub const Builder = struct {
309309
310310 fn processNixOSEnvVars(self: &Builder) {
311311 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, " ");
313313 while (true) {
314314 const word = it.next() ?? break;
315315 if (mem.eql(u8, word, "-isystem")) {
......@@ -325,7 +325,7 @@ pub const Builder = struct {
325325 }
326326 }
327327 if (os.getEnv("NIX_LDFLAGS")) |nix_ldflags| {
328 var it = mem.split(nix_ldflags, ' ');
328 var it = mem.split(nix_ldflags, " ");
329329 while (true) {
330330 const word = it.next() ?? break;
331331 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 {
216216
217217/// Linear search for the index of a scalar value inside a slice.
218218pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
219 for (slice) |item, i| {
220 if (item == value) {
219 return indexOfScalarPos(T, slice, 0, 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)
221226 return i;
222 }
223227 }
224228 return null;
225229}
226230
227// TODO boyer-moore algorithm
228231pub 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 {
229237 if (needle.len > haystack.len)
230238 return null;
231239
232 var i: usize = 0;
240 var i: usize = start_index;
233241 const end = haystack.len - needle.len;
234242 while (i <= end) : (i += 1) {
235243 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 {
303311 return eql(u8, a, b);
304312}
305313
306/// Returns an iterator that iterates over the slices of ::s that are not
307/// the byte ::c.
308/// split(" abc def ghi ")
314/// Returns an iterator that iterates over the slices of `buffer` that are not
315/// any of the bytes in `split_bytes`.
316/// split(" abc def ghi ", " ")
309317/// 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 {
311319 SplitIterator {
312320 .index = 0,
313 .s = s,
314 .c = c,
321 .buffer = buffer,
322 .split_bytes = split_bytes,
315323 }
316324}
317325
318326test "mem.split" {
319 var it = split(" abc def ghi ", ' ');
327 var it = split(" abc def ghi ", " ");
320328 assert(eql(u8, ??it.next(), "abc"));
321329 assert(eql(u8, ??it.next(), "def"));
322330 assert(eql(u8, ??it.next(), "ghi"));
......@@ -328,31 +336,40 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> b
328336}
329337
330338const SplitIterator = struct {
331 s: []const u8,
332 c: u8,
339 buffer: []const u8,
340 split_bytes: []const u8,
333341 index: usize,
334342
335343 pub fn next(self: &SplitIterator) -> ?[]const u8 {
336344 // 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) {}
338346 const start = self.index;
339 if (start == self.s.len) {
347 if (start == self.buffer.len) {
340348 return null;
341349 }
342350
343351 // 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) {}
345353 const end = self.index;
346354
347 return self.s[start..end];
355 return self.buffer[start..end];
348356 }
349357
350358 /// Returns a slice of the remaining bytes. Does not affect iterator state.
351359 pub fn rest(self: &const SplitIterator) -> []const u8 {
352360 // move to beginning of token
353361 var index: usize = self.index;
354 while (index < self.s.len and self.s[index] == self.c) : (index += 1) {}
355 return self.s[index..];
362 while (index < self.buffer.len and self.isSplitByte(self.buffer[index])) : (index += 1) {}
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;
356373 }
357374};
358375
std/os/index.zig+48-12
......@@ -384,7 +384,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
384384 // +1 for the null terminating byte
385385 const path_buf = %return allocator.alloc(u8, PATH.len + exe_path.len + 2);
386386 defer allocator.free(path_buf);
387 var it = mem.split(PATH, ':');
387 var it = mem.split(PATH, ":");
388388 var seen_eacces = false;
389389 var err: usize = undefined;
390390 while (it.next()) |search_path| {
......@@ -474,18 +474,41 @@ pub const args = struct {
474474
475475/// Caller must free the returned memory.
476476pub fn getCwd(allocator: &Allocator) -> %[]u8 {
477 var buf = %return allocator.alloc(u8, 1024);
478 %defer allocator.free(buf);
479 while (true) {
480 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
481 if (err == posix.ERANGE) {
482 buf = %return allocator.realloc(u8, buf, buf.len * 2);
483 continue;
484 } else if (err > 0) {
485 return error.Unexpected;
486 }
477 switch (builtin.os) {
478 Os.windows => {
479 var buf = %return allocator.alloc(u8, 256);
480 %defer allocator.free(buf);
481
482 while (true) {
483 const result = windows.GetCurrentDirectoryA(windows.WORD(buf.len), buf.ptr);
484
485 if (result == 0) {
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 },
489512 }
490513}
491514
......@@ -1033,3 +1056,16 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
10331056 else => error.Unexpected,
10341057 };
10351058}
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;
1111const c = @import("../c/index.zig");
1212const cstr = @import("../cstr.zig");
1313
14pub const sep = switch (builtin.os) {
15 Os.windows => '\\',
16 else => '/',
17};
18pub const delimiter = switch (builtin.os) {
19 Os.windows => ';',
20 else => ':',
21};
14pub const sep_windows = '\\';
15pub const sep_posix = '/';
16pub const sep = if (is_windows) sep_windows else sep_posix;
17
18pub const delimiter_windows = ';';
19pub const delimiter_posix = ':';
20pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
21
22const is_windows = builtin.os == builtin.Os.windows;
2223
2324/// Naively combines a series of paths with the native path seperator.
2425/// Allocates memory for the result, which must be freed by the caller.
2526pub 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);
2740}
2841
2942test "os.path.join" {
30 assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c"));
31 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"));
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"));
34 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"));
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"),
3760 "/home/andy/dev/zig/build/lib/zig/std/io.zig"));
3861}
3962
4063pub fn isAbsolute(path: []const u8) -> bool {
41 switch (builtin.os) {
42 Os.windows => @compileError("Unsupported OS"),
43 else => return path[0] == sep,
64 if (is_windows) {
65 return isAbsoluteWindows(path);
66 } else {
67 return isAbsolutePosix(path);
4468 }
4569}
4670
47/// This function is like a series of `cd` statements executed one after another.
48/// The result does not have a trailing path separator.
71pub fn isAbsoluteWindows(path: []const u8) -> bool {
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`.
49216pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {
50217 var paths: [args.len][]const u8 = undefined;
51218 comptime var arg_i = 0;
......@@ -55,18 +222,178 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {
55222 return resolveSlice(allocator, paths);
56223}
57224
225/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
58226pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
59 if (builtin.os == builtin.Os.windows) {
60 @compileError("TODO implement os.path.resolve for windows");
227 if (is_windows) {
228 return resolveWindows(allocator, paths);
229 } else {
230 return resolvePosix(allocator, paths);
61231 }
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
63243 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
65392 var first_index: usize = 0;
66393 var have_abs = false;
67394 var max_size: usize = 0;
68395 for (paths) |p, i| {
69 if (isAbsolute(p)) {
396 if (isAbsolutePosix(p)) {
70397 first_index = i;
71398 have_abs = true;
72399 max_size = 0;
......@@ -80,6 +407,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
80407 if (have_abs) {
81408 result = %return allocator.alloc(u8, max_size);
82409 } else {
410 assert(!is_windows); // resolvePosix called on windows can't use getCwd
83411 const cwd = %return os.getCwd(allocator);
84412 defer allocator.free(cwd);
85413 result = %return allocator.alloc(u8, max_size + cwd.len + 1);
......@@ -89,7 +417,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
89417 %defer allocator.free(result);
90418
91419 for (paths[first_index..]) |p, i| {
92 var it = mem.split(p, '/');
420 var it = mem.split(p, "/");
93421 while (it.next()) |component| {
94422 if (mem.eql(u8, component, ".")) {
95423 continue;
......@@ -119,22 +447,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
119447}
120448
121449test "os.path.resolve" {
122 assert(mem.eql(u8, testResolve("/a/b", "c"), "/a/b/c"));
123 assert(mem.eql(u8, testResolve("/a/b", "c", "//d", "e///"), "/d/e"));
124 assert(mem.eql(u8, testResolve("/a/b/c", "..", "../"), "/a"));
125 assert(mem.eql(u8, testResolve("/", "..", ".."), "/"));
126 assert(mem.eql(u8, testResolve("/a/b/c/"), "/a/b/c"));
450 const cwd = %%os.getCwd(&debug.global_allocator);
451 if (is_windows) {
452 assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
453 } else {
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);
127490}
128fn testResolve(args: ...) -> []u8 {
129 return %%resolve(&debug.global_allocator, args);
491
492fn testResolvePosix(paths: []const []const u8) -> []u8 {
493 return %%resolvePosix(&debug.global_allocator, paths);
130494}
131495
132496pub fn dirname(path: []const u8) -> []const u8 {
133 if (builtin.os == builtin.Os.windows) {
134 @compileError("TODO implement os.path.dirname for windows");
497 if (is_windows) {
498 return dirnameWindows(path);
499 } else {
500 return dirnamePosix(path);
135501 }
502}
503
504pub fn dirnameWindows(path: []const u8) -> []const u8 {
136505 if (path.len == 0)
137506 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
138539 var end_index: usize = path.len - 1;
139540 while (path[end_index] == '/') {
140541 if (end_index == 0)
......@@ -154,25 +555,71 @@ pub fn dirname(path: []const u8) -> []const u8 {
154555 return path[0..end_index];
155556}
156557
157test "os.path.dirname" {
158 testDirname("/a/b/c", "/a/b");
159 testDirname("/a/b/c///", "/a/b");
160 testDirname("/a", "/");
161 testDirname("/", "/");
162 testDirname("////", "/");
163 testDirname("", "");
164 testDirname("a", "");
165 testDirname("a/", "");
166 testDirname("a//", "");
558test "os.path.dirnamePosix" {
559 testDirnamePosix("/a/b/c", "/a/b");
560 testDirnamePosix("/a/b/c///", "/a/b");
561 testDirnamePosix("/a", "/");
562 testDirnamePosix("/", "/");
563 testDirnamePosix("////", "/");
564 testDirnamePosix("", "");
565 testDirnamePosix("a", "");
566 testDirnamePosix("a/", "");
567 testDirnamePosix("a//", "");
167568}
168fn testDirname(input: []const u8, expected_output: []const u8) {
169 assert(mem.eql(u8, dirname(input), expected_output));
569
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));
170612}
171613
172614pub fn basename(path: []const u8) -> []const u8 {
173 if (builtin.os == builtin.Os.windows) {
174 @compileError("TODO implement os.path.basename for windows");
615 if (is_windows) {
616 return basenameWindows(path);
617 } else {
618 return basenamePosix(path);
175619 }
620}
621
622pub fn basenamePosix(path: []const u8) -> []const u8 {
176623 if (path.len == 0)
177624 return []u8{};
178625
......@@ -193,6 +640,38 @@ pub fn basename(path: []const u8) -> []const u8 {
193640 return path[start_index + 1..end_index];
194641}
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
196675test "os.path.basename" {
197676 testBasename("", "");
198677 testBasename("/", "");
......@@ -206,26 +685,137 @@ test "os.path.basename" {
206685 testBasename("/aaa/b", "b");
207686 testBasename("/a/b", "b");
208687 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");
209712}
713
210714fn testBasename(input: []const u8, expected_output: []const u8) {
211715 assert(mem.eql(u8, basename(input), expected_output));
212716}
213717
214/// Returns the relative path from ::from to ::to. If ::from and ::to each
215/// resolve to the same path (after calling ::resolve on each), a zero-length
718fn testBasenamePosix(input: []const u8, expected_output: []const u8) {
719 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
216728/// string is returned.
729/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
217730pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
218 if (builtin.os == builtin.Os.windows) {
219 @compileError("TODO implement os.path.relative for windows");
731 if (is_windows) {
732 return relativeWindows(allocator, from, to);
733 } else {
734 return relativePosix(allocator, from, to);
220735 }
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});
222740 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});
225815 defer allocator.free(resolved_to);
226816
227 var from_it = mem.split(resolved_from, '/');
228 var to_it = mem.split(resolved_to, '/');
817 var from_it = mem.split(resolved_from, "/");
818 var to_it = mem.split(resolved_to, "/");
229819 while (true) {
230820 const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest());
231821 const to_rest = to_it.rest();
......@@ -263,21 +853,52 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u
263853}
264854
265855test "os.path.relative" {
266 testRelative("/var/lib", "/var", "..");
267 testRelative("/var/lib", "/bin", "../../bin");
268 testRelative("/var/lib", "/var/lib", "");
269 testRelative("/var/lib", "/var/apache", "../apache");
270 testRelative("/var/", "/var/lib", "lib");
271 testRelative("/", "/var/lib", "var/lib");
272 testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
273 testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
274 testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
275 testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
276 testRelative("/baz-quux", "/baz", "../baz");
277 testRelative("/baz", "/baz-quux", "../baz-quux");
278}
279fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) {
280 const result = %%relative(&debug.global_allocator, from, to);
856 testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
857 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
858 testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
859 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
860 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
861 testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
862 testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
863 testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
864 testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
865 testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
866 testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
867 testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
868 testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
869 testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
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);
281902 assert(mem.eql(u8, result, expected_output));
282903}
283904
std/os/windows/index.zig+4-1
......@@ -13,6 +13,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLine() -> LPTSTR;
1313
1414pub 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
1618/// Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis.
1719/// Multiple threads do not overwrite each other's last-error code.
1820pub extern "kernel32" stdcallcc fn GetLastError() -> DWORD;
......@@ -50,7 +52,7 @@ pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lp
5052pub const PROV_RSA_FULL = 1;
5153
5254pub const UNICODE = false;
53pub const LPTSTR = if (unicode) LPWSTR else LPSTR;
55pub const LPTSTR = if (UNICODE) LPWSTR else LPSTR;
5456pub const LPWSTR = &WCHAR;
5557pub const LPSTR = &CHAR;
5658pub const CHAR = u8;
......@@ -59,6 +61,7 @@ pub const SIZE_T = usize;
5961
6062pub const BOOL = bool;
6163pub const BYTE = u8;
64pub const WORD = u16;
6265pub const DWORD = u32;
6366pub const FLOAT = f32;
6467pub const HANDLE = &c_void;