authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-06 00:27:15-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-10-08 21:43:43-04:00
log08ee69dac360dda17e22d950246e42228fc54f47
tree1c1fbac39b6d019055fff079359f306c663435d2
parent968ff38cad1db6c108d402442825f80453accd7c

implement os.path.dirname for windows


4 files changed, 559 insertions(+), 95 deletions(-)

README.md+1-4
...@@ -43,15 +43,12 @@ clarity....@@ -43,15 +43,12 @@ 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
54### Support Table50### Support Table
51
55Freestanding means that you do not directly interact with the OS52Freestanding means that you do not directly interact with the OS
56or you are writing your own OS.53or you are writing your own OS.
5754
std/mem.zig+32-23
...@@ -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,15 +311,15 @@ pub fn eql_slice_u8(a: []const u8, b: []const u8) -> bool {...@@ -303,15 +311,15 @@ 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
...@@ -328,31 +336,32 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> b...@@ -328,31 +336,32 @@ 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 fn isSplitByte(self: &SplitIterator, byte: u8) -> bool {
351 pub fn rest(self: &const SplitIterator) -> []const u8 {359 for (self.split_bytes) |split_byte| {
352 // move to beginning of token360 if (byte == split_byte) {
353 var index: usize = self.index;361 return true;
354 while (index < self.s.len and self.s[index] == self.c) : (index += 1) {}362 }
355 return self.s[index..];363 }
364 return false;
356 }365 }
357};366};
358367
std/os/index.zig+21-11
...@@ -474,18 +474,28 @@ pub const args = struct {...@@ -474,18 +474,28 @@ 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 @panic("implement getCwd for windows");
480 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));480 //if (windows.GetCurrentDirectoryA(buf_len(out_cwd), buf_ptr(out_cwd)) == 0) {
481 if (err == posix.ERANGE) {481 // zig_panic("GetCurrentDirectory failed");
482 buf = %return allocator.realloc(u8, buf, buf.len * 2);482 //}
483 continue;483 },
484 } else if (err > 0) {484 else => {
485 return error.Unexpected;485 var buf = %return allocator.alloc(u8, 1024);
486 }486 %defer allocator.free(buf);
487 while (true) {
488 const err = posix.getErrno(posix.getcwd(buf.ptr, buf.len));
489 if (err == posix.ERANGE) {
490 buf = %return allocator.realloc(u8, buf, buf.len * 2);
491 continue;
492 } else if (err > 0) {
493 return error.Unexpected;
494 }
487495
488 return cstr.toSlice(buf.ptr);496 return cstr.toSlice(buf.ptr);
497 }
498 },
489 }499 }
490}500}
491501
std/os/path.zig+505-57
...@@ -11,37 +11,221 @@ const posix = os.posix;...@@ -11,37 +11,221 @@ 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"));
45
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"));
3248
33 assert(mem.eql(u8, %%join(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c"));49 assert(mem.eql(u8, %%joinWindows(&debug.global_allocator,
34 assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c"));50 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig"),
51 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig"));
3552
36 assert(mem.eql(u8, %%join(&debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"),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);
68 }
69}
70
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;
44 }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 {
143 const this_sep = '/';
144 const two_sep = []u8{this_sep, this_sep};
145 if (mem.startsWith(u8, path, two_sep)) {
146 if (path[2] == this_sep)
147 return null;
148 const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null;
149 const next_start = index_host + 1;
150 if (next_start >= path.len)
151 return null;
152 const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len;
153 return path[0..index_root];
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 const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null;
163 const next_start = index_host + 1;
164 if (next_start >= path.len)
165 return null;
166 const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len;
167 return path[0..index_root];
168 }
169 }
170 return null;
171}
172
173test "os.path.networkShare" {
174 assert(mem.eql(u8, ??networkShare("//a/b"), "//a/b"));
175 assert(mem.eql(u8, ??networkShare("\\\\a\\b"), "\\\\a\\b"));
176
177 assert(networkShare("\\\\a\\") == null);
178}
179
180pub fn preferredSepWindows(path: []const u8) -> u8 {
181 for (path) |byte| {
182 if (byte == '/' or byte == '\\') {
183 return byte;
184 }
185 }
186 return sep_windows;
187}
188
189pub fn preferredSep(path: []const u8) -> u8 {
190 if (is_windows) {
191 return preferredSepWindows(path);
192 } else {
193 return sep_posix;
194 }
195}
196
197pub fn root(path: []const u8) -> []const u8 {
198 if (is_windows) {
199 return rootWindows(path);
200 } else {
201 return rootPosix(path);
202 }
203}
204
205pub fn rootWindows(path: []const u8) -> []const u8 {
206 return drive(path) ?? (networkShare(path) ?? []u8{});
207}
208
209pub fn rootPosix(path: []const u8) -> ?[]const u8 {
210 if (path.len == 0 or path[0] != '/')
211 return []u8{};
212
213 return path[0..1];
214}
215
216pub fn drivesEqual(drive1: []const u8, drive2: []const u8) -> bool {
217 assert(drive1.len == 2);
218 assert(drive2.len == 2);
219 assert(drive1[1] == ':');
220 assert(drive2[1] == ':');
221 return asciiLower(drive1[0]) == asciiLower(drive2[0]);
222}
223
224fn asciiLower(byte: u8) -> u8 {
225 return switch (byte) {
226 'A' ... 'Z' => 'a' + (byte - 'A'),
227 else => byte,
228 };
45}229}
46230
47/// This function is like a series of `cd` statements executed one after another.231/// This function is like a series of `cd` statements executed one after another.
...@@ -56,17 +240,130 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {...@@ -56,17 +240,130 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {
56}240}
57241
58pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {242pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
59 if (builtin.os == builtin.Os.windows) {243 if (is_windows) {
60 @compileError("TODO implement os.path.resolve for windows");244 return resolveWindows(allocator, paths);
245 } else {
246 return resolvePosix(allocator, paths);
61 }247 }
62 if (paths.len == 0)248}
249
250pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
251 if (paths.len == 0) {
252 assert(is_windows); // resolveWindows called on non windows can't use getCwd
63 return os.getCwd(allocator);253 return os.getCwd(allocator);
254 }
255
256 // determine which drive we want to result with
257 var result_drive: ?[]const u8 = null;
258 var have_abs = false;
259 var first_index: usize = 0;
260 var max_size: usize = 0;
261 for (paths) |p, i| {
262 const is_abs = isAbsoluteWindows(p);
263 if (is_abs) {
264 have_abs = true;
265 first_index = i;
266 max_size = 0;
267 }
268 if (drive(p)) |d| {
269 result_drive = d;
270 } else if (is_abs) {
271 result_drive = null;
272 }
273 max_size += p.len + 1;
274 }
275
276 // if we will result with a drive, loop again to determine
277 // which is the first time the drive is absolutely specified, if any
278 // and count up the max bytes for paths related to this drive
279 if (result_drive) |res_dr| {
280 have_abs = false;
281 first_index = 0;
282 max_size = 0;
283 var correct_drive = false;
284
285 for (paths) |p, i| {
286 if (drive(p)) |dr| {
287 correct_drive = drivesEqual(dr, res_dr);
288 }
289 if (!correct_drive) {
290 continue;
291 }
292 const is_abs = isAbsoluteWindows(p);
293 if (is_abs) {
294 first_index = i;
295 max_size = 0;
296 }
297 max_size += p.len + 1;
298 }
299 }
300
301 var result: []u8 = undefined;
302 var result_index: usize = 0;
303
304 if (have_abs) {
305 result = %return allocator.alloc(u8, max_size);
306 mem.copy(u8, result, paths[first_index]);
307 result_index += paths[first_index].len;
308 first_index += 1;
309 } else {
310 assert(is_windows); // resolveWindows called on non windows can't use getCwd
311 // TODO get cwd for result_drive if applicable
312 const cwd = %return os.getCwd(allocator);
313 defer allocator.free(cwd);
314 result = %return allocator.alloc(u8, max_size + cwd.len + 1);
315 mem.copy(u8, result, cwd);
316 result_index += cwd.len;
317 }
318 %defer allocator.free(result);
319
320 var correct_drive = false;
321 const rootSlice = rootWindows(result[0..result_index]);
322 const preferred_path_sep = preferredSepWindows(result[0..result_index]);
323 for (paths[first_index..]) |p, i| {
324 if (result_drive) |res_dr| {
325 if (drive(p)) |dr| {
326 correct_drive = drivesEqual(dr, res_dr);
327 }
328 if (!correct_drive) {
329 continue;
330 }
331 }
332 var it = mem.split(p, "/\\");
333 while (it.next()) |component| {
334 if (mem.eql(u8, component, ".")) {
335 continue;
336 } else if (mem.eql(u8, component, "..")) {
337 while (true) {
338 if (result_index == 0 or result_index == rootSlice.len)
339 break;
340 result_index -= 1;
341 if (result[result_index] == '\\' or result[result_index] == '/')
342 break;
343 }
344 } else {
345 result[result_index] = preferred_path_sep;
346 result_index += 1;
347 mem.copy(u8, result[result_index..], component);
348 result_index += component.len;
349 }
350 }
351 }
352
353 return result[0..result_index];
354}
355
356pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
357 if (paths.len == 0) {
358 assert(!is_windows); // resolvePosix called on windows can't use getCwd
359 return os.getCwd(allocator);
360 }
64361
65 var first_index: usize = 0;362 var first_index: usize = 0;
66 var have_abs = false;363 var have_abs = false;
67 var max_size: usize = 0;364 var max_size: usize = 0;
68 for (paths) |p, i| {365 for (paths) |p, i| {
69 if (isAbsolute(p)) {366 if (isAbsolutePosix(p)) {
70 first_index = i;367 first_index = i;
71 have_abs = true;368 have_abs = true;
72 max_size = 0;369 max_size = 0;
...@@ -80,6 +377,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {...@@ -80,6 +377,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
80 if (have_abs) {377 if (have_abs) {
81 result = %return allocator.alloc(u8, max_size);378 result = %return allocator.alloc(u8, max_size);
82 } else {379 } else {
380 assert(!is_windows); // resolvePosix called on windows can't use getCwd
83 const cwd = %return os.getCwd(allocator);381 const cwd = %return os.getCwd(allocator);
84 defer allocator.free(cwd);382 defer allocator.free(cwd);
85 result = %return allocator.alloc(u8, max_size + cwd.len + 1);383 result = %return allocator.alloc(u8, max_size + cwd.len + 1);
...@@ -89,7 +387,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {...@@ -89,7 +387,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
89 %defer allocator.free(result);387 %defer allocator.free(result);
90388
91 for (paths[first_index..]) |p, i| {389 for (paths[first_index..]) |p, i| {
92 var it = mem.split(p, '/');390 var it = mem.split(p, "/");
93 while (it.next()) |component| {391 while (it.next()) |component| {
94 if (mem.eql(u8, component, ".")) {392 if (mem.eql(u8, component, ".")) {
95 continue;393 continue;
...@@ -119,22 +417,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {...@@ -119,22 +417,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
119}417}
120418
121test "os.path.resolve" {419test "os.path.resolve" {
122 assert(mem.eql(u8, testResolve("/a/b", "c"), "/a/b/c"));420 const cwd = %%os.getCwd(&debug.global_allocator);
123 assert(mem.eql(u8, testResolve("/a/b", "c", "//d", "e///"), "/d/e"));421 if (is_windows) {
124 assert(mem.eql(u8, testResolve("/a/b/c", "..", "../"), "/a"));422 assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
125 assert(mem.eql(u8, testResolve("/", "..", ".."), "/"));423 } else {
126 assert(mem.eql(u8, testResolve("/a/b/c/"), "/a/b/c"));424 assert(mem.eql(u8, testResolvePosix([][]const u8{"a/b/c/", "../../.."}), cwd));
425 assert(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd));
426 }
427}
428
429test "os.path.resolveWindows" {
430 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "c:\\blah\\a"));
431 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "c:\\blah\\a"));
432 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "d:\\e.exe"));
433 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "c:\\some\\file"));
434 assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "d:\\ignore\\some\\dir"));
435 assert(mem.eql(u8, testResolveWindows([][]const u8{"//server/share", "..", "relative\\"}), "\\\\server\\share\\relative"));
436 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "c:\\"));
437 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "c:\\dir"));
438 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server/share"}), "\\\\server\\share\\"));
439 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server//share"}), "\\\\server\\share\\"));
440 assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "c:\\some\\dir"));
441 assert(mem.eql(u8, testResolveWindows([][]const u8{"C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"}),
442 "C:\\foo\\tmp.3\\cycles\\root.js"));
443}
444
445test "os.path.resolvePosix" {
446 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b", "c"}), "/a/b/c"));
447 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b", "c", "//d", "e///"}), "/d/e"));
448 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c", "..", "../"}), "/a"));
449 assert(mem.eql(u8, testResolvePosix([][]const u8{"/", "..", ".."}), "/"));
450 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c"));
451
452 assert(mem.eql(u8, testResolvePosix([][]const u8{"/var/lib", "../", "file/"}), "/var/file"));
453 assert(mem.eql(u8, testResolvePosix([][]const u8{"/var/lib", "/../", "file/"}), "/file"));
454 assert(mem.eql(u8, testResolvePosix([][]const u8{"/some/dir", ".", "/absolute/"}), "/absolute"));
455 assert(mem.eql(u8, testResolvePosix([][]const u8{"/foo/tmp.3/", "../tmp.3/cycles/root.js"}), "/foo/tmp.3/cycles/root.js"));
456}
457
458fn testResolveWindows(paths: []const []const u8) -> []u8 {
459 return %%resolveWindows(&debug.global_allocator, paths);
127}460}
128fn testResolve(args: ...) -> []u8 {461
129 return %%resolve(&debug.global_allocator, args);462fn testResolvePosix(paths: []const []const u8) -> []u8 {
463 return %%resolvePosix(&debug.global_allocator, paths);
130}464}
131465
132pub fn dirname(path: []const u8) -> []const u8 {466pub fn dirname(path: []const u8) -> []const u8 {
133 if (builtin.os == builtin.Os.windows) {467 if (is_windows) {
134 @compileError("TODO implement os.path.dirname for windows");468 return dirnameWindows(path);
469 } else {
470 return dirnamePosix(path);
471 }
472}
473
474pub fn dirnameWindows(path: []const u8) -> []const u8 {
475 if (path.len == 0)
476 return path[0..0];
477
478 const rootSlice = rootWindows(path);
479 if (path.len == rootSlice.len)
480 return path;
481
482 const have_root_slash = path.len > rootSlice.len and (path[rootSlice.len] == '/' or path[rootSlice.len] == '\\');
483
484 var end_index: usize = path.len - 1;
485
486 while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > rootSlice.len) {
487 if (end_index == 0)
488 return path[0..0];
489 end_index -= 1;
490 }
491
492 while (path[end_index] != '/' and path[end_index] != '\\' and end_index > rootSlice.len) {
493 if (end_index == 0)
494 return path[0..0];
495 end_index -= 1;
496 }
497
498 if (have_root_slash and end_index == rootSlice.len) {
499 end_index += 1;
135 }500 }
501
502 return path[0..end_index];
503}
504
505pub fn dirnamePosix(path: []const u8) -> []const u8 {
136 if (path.len == 0)506 if (path.len == 0)
137 return path[0..0];507 return path[0..0];
508
138 var end_index: usize = path.len - 1;509 var end_index: usize = path.len - 1;
139 while (path[end_index] == '/') {510 while (path[end_index] == '/') {
140 if (end_index == 0)511 if (end_index == 0)
...@@ -154,19 +525,60 @@ pub fn dirname(path: []const u8) -> []const u8 {...@@ -154,19 +525,60 @@ pub fn dirname(path: []const u8) -> []const u8 {
154 return path[0..end_index];525 return path[0..end_index];
155}526}
156527
157test "os.path.dirname" {528test "os.path.dirnamePosix" {
158 testDirname("/a/b/c", "/a/b");529 testDirnamePosix("/a/b/c", "/a/b");
159 testDirname("/a/b/c///", "/a/b");530 testDirnamePosix("/a/b/c///", "/a/b");
160 testDirname("/a", "/");531 testDirnamePosix("/a", "/");
161 testDirname("/", "/");532 testDirnamePosix("/", "/");
162 testDirname("////", "/");533 testDirnamePosix("////", "/");
163 testDirname("", "");534 testDirnamePosix("", "");
164 testDirname("a", "");535 testDirnamePosix("a", "");
165 testDirname("a/", "");536 testDirnamePosix("a/", "");
166 testDirname("a//", "");537 testDirnamePosix("a//", "");
538}
539
540test "os.path.dirnameWindows" {
541 testDirnameWindows("c:\\", "c:\\");
542 testDirnameWindows("c:\\foo", "c:\\");
543 testDirnameWindows("c:\\foo\\", "c:\\");
544 testDirnameWindows("c:\\foo\\bar", "c:\\foo");
545 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
546 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
547 testDirnameWindows("\\", "\\");
548 testDirnameWindows("\\foo", "\\");
549 testDirnameWindows("\\foo\\", "\\");
550 testDirnameWindows("\\foo\\bar", "\\foo");
551 testDirnameWindows("\\foo\\bar\\", "\\foo");
552 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
553 testDirnameWindows("c:", "c:");
554 testDirnameWindows("c:foo", "c:");
555 testDirnameWindows("c:foo\\", "c:");
556 testDirnameWindows("c:foo\\bar", "c:foo");
557 testDirnameWindows("c:foo\\bar\\", "c:foo");
558 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
559 testDirnameWindows("file:stream", "");
560 testDirnameWindows("dir\\file:stream", "dir");
561 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");
562 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
563 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
564 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
565 testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo");
566 testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar");
567 testDirnameWindows("/a/b/", "/a");
568 testDirnameWindows("/a/b", "/a");
569 testDirnameWindows("/a", "/");
570 testDirnameWindows("", "");
571 testDirnameWindows("/", "/");
572 testDirnameWindows("////", "/");
573 testDirnameWindows("foo", "");
167}574}
168fn testDirname(input: []const u8, expected_output: []const u8) {575
169 assert(mem.eql(u8, dirname(input), expected_output));576fn testDirnamePosix(input: []const u8, expected_output: []const u8) {
577 assert(mem.eql(u8, dirnamePosix(input), expected_output));
578}
579
580fn testDirnameWindows(input: []const u8, expected_output: []const u8) {
581 assert(mem.eql(u8, dirnameWindows(input), expected_output));
170}582}
171583
172pub fn basename(path: []const u8) -> []const u8 {584pub fn basename(path: []const u8) -> []const u8 {
...@@ -215,9 +627,18 @@ fn testBasename(input: []const u8, expected_output: []const u8) {...@@ -215,9 +627,18 @@ fn testBasename(input: []const u8, expected_output: []const u8) {
215/// resolve to the same path (after calling ::resolve on each), a zero-length627/// resolve to the same path (after calling ::resolve on each), a zero-length
216/// string is returned.628/// string is returned.
217pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {629pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
218 if (builtin.os == builtin.Os.windows) {630 if (is_windows) {
219 @compileError("TODO implement os.path.relative for windows");631 return windowsRelative(allocator, from, to);
632 } else {
633 return posixRelative(allocator, from, to);
220 }634 }
635}
636
637fn windowsRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
638 @compileError("TODO implement this");
639}
640
641fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 {
221 const resolved_from = %return resolve(allocator, from);642 const resolved_from = %return resolve(allocator, from);
222 defer allocator.free(resolved_from);643 defer allocator.free(resolved_from);
223644
...@@ -263,18 +684,45 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u...@@ -263,18 +684,45 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u
263}684}
264685
265test "os.path.relative" {686test "os.path.relative" {
266 testRelative("/var/lib", "/var", "..");687 if (is_windows) {
267 testRelative("/var/lib", "/bin", "../../bin");688 testRelative("c:/blah\\blah", "d:/games", "d:\\games");
268 testRelative("/var/lib", "/var/lib", "");689 testRelative("c:/aaaa/bbbb", "c:/aaaa", "..");
269 testRelative("/var/lib", "/var/apache", "../apache");690 testRelative("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
270 testRelative("/var/", "/var/lib", "lib");691 testRelative("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
271 testRelative("/", "/var/lib", "var/lib");692 testRelative("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
272 testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json");693 testRelative("c:/aaaa/", "c:/aaaa/cccc", "cccc");
273 testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");694 testRelative("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
274 testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");695 testRelative("c:/aaaa/bbbb", "d:\\", "d:\\");
275 testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");696 testRelative("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
276 testRelative("/baz-quux", "/baz", "../baz");697 testRelative("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
277 testRelative("/baz", "/baz-quux", "../baz-quux");698 testRelative("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
699 testRelative("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
700 testRelative("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
701 testRelative("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
702 testRelative("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
703 testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
704 testRelative("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
705 testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
706 testRelative("C:\\baz-quux", "C:\\baz", "..\\baz");
707 testRelative("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
708 testRelative("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz");
709 testRelative("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux");
710 testRelative("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
711 testRelative("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz")
712 } else {
713 testRelative("/var/lib", "/var", "..");
714 testRelative("/var/lib", "/bin", "../../bin");
715 testRelative("/var/lib", "/var/lib", "");
716 testRelative("/var/lib", "/var/apache", "../apache");
717 testRelative("/var/", "/var/lib", "lib");
718 testRelative("/", "/var/lib", "var/lib");
719 testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
720 testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
721 testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
722 testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
723 testRelative("/baz-quux", "/baz", "../baz");
724 testRelative("/baz", "/baz-quux", "../baz-quux");
725 }
278}726}
279fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) {727fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) {
280 const result = %%relative(&debug.global_allocator, from, to);728 const result = %%relative(&debug.global_allocator, from, to);