| ... | ... | @@ -11,41 +11,208 @@ const posix = os.posix; |
| 11 | 11 | const c = @import("../c/index.zig"); |
| 12 | 12 | const cstr = @import("../cstr.zig"); |
| 13 | 13 | |
| 14 | | pub const sep = switch (builtin.os) { |
| 15 | | Os.windows => '\\', |
| 16 | | else => '/', |
| 17 | | }; |
| 18 | | pub const delimiter = switch (builtin.os) { |
| 19 | | Os.windows => ';', |
| 20 | | else => ':', |
| 21 | | }; |
| 14 | pub const sep_windows = '\\'; |
| 15 | pub const sep_posix = '/'; |
| 16 | pub const sep = if (is_windows) sep_windows else sep_posix; |
| 17 | |
| 18 | pub const delimiter_windows = ';'; |
| 19 | pub const delimiter_posix = ':'; |
| 20 | pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; |
| 21 | |
| 22 | const is_windows = builtin.os == builtin.Os.windows; |
| 22 | 23 | |
| 23 | 24 | /// Naively combines a series of paths with the native path seperator. |
| 24 | 25 | /// Allocates memory for the result, which must be freed by the caller. |
| 25 | 26 | pub 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 | |
| 34 | pub fn joinWindows(allocator: &Allocator, paths: ...) -> %[]u8 { |
| 35 | return mem.join(allocator, sep_windows, paths); |
| 36 | } |
| 37 | |
| 38 | pub fn joinPosix(allocator: &Allocator, paths: ...) -> %[]u8 { |
| 39 | return mem.join(allocator, sep_posix, paths); |
| 27 | 40 | } |
| 28 | 41 | |
| 29 | 42 | test "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")); |
| 32 | 45 | |
| 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")); |
| 35 | 48 | |
| 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 | 60 | "/home/andy/dev/zig/build/lib/zig/std/io.zig")); |
| 38 | 61 | } |
| 39 | 62 | |
| 40 | 63 | pub 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); |
| 44 | 68 | } |
| 45 | 69 | } |
| 46 | 70 | |
| 47 | | /// This function is like a series of `cd` statements executed one after another. |
| 48 | | /// The result does not have a trailing path separator. |
| 71 | pub 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 | |
| 90 | pub fn isAbsolutePosix(path: []const u8) -> bool { |
| 91 | return path[0] == sep_posix; |
| 92 | } |
| 93 | |
| 94 | test "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 | |
| 115 | test "os.path.isAbsolutePosix" { |
| 116 | testIsAbsolutePosix("/home/foo", true); |
| 117 | testIsAbsolutePosix("/home/foo/..", true); |
| 118 | testIsAbsolutePosix("bar/", false); |
| 119 | testIsAbsolutePosix("./baz", false); |
| 120 | } |
| 121 | |
| 122 | fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) { |
| 123 | assert(isAbsoluteWindows(path) == expected_result); |
| 124 | } |
| 125 | |
| 126 | fn testIsAbsolutePosix(path: []const u8, expected_result: bool) { |
| 127 | assert(isAbsolutePosix(path) == expected_result); |
| 128 | } |
| 129 | |
| 130 | pub 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 | |
| 138 | pub 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 | |
| 172 | test "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 | |
| 179 | pub 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. |
| 187 | fn 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 | |
| 197 | fn asciiUpper(byte: u8) -> u8 { |
| 198 | return switch (byte) { |
| 199 | 'a' ... 'z' => 'A' + (byte - 'a'), |
| 200 | else => byte, |
| 201 | }; |
| 202 | } |
| 203 | |
| 204 | fn 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`. |
| 49 | 216 | pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { |
| 50 | 217 | var paths: [args.len][]const u8 = undefined; |
| 51 | 218 | comptime var arg_i = 0; |
| ... | ... | @@ -55,18 +222,178 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { |
| 55 | 222 | return resolveSlice(allocator, paths); |
| 56 | 223 | } |
| 57 | 224 | |
| 225 | /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. |
| 58 | 226 | pub 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); |
| 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. |
| 240 | pub 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 | 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. |
| 386 | pub 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 | } |
| 64 | 391 | |
| 65 | 392 | var first_index: usize = 0; |
| 66 | 393 | var have_abs = false; |
| 67 | 394 | var max_size: usize = 0; |
| 68 | 395 | for (paths) |p, i| { |
| 69 | | if (isAbsolute(p)) { |
| 396 | if (isAbsolutePosix(p)) { |
| 70 | 397 | first_index = i; |
| 71 | 398 | have_abs = true; |
| 72 | 399 | max_size = 0; |
| ... | ... | @@ -80,6 +407,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { |
| 80 | 407 | if (have_abs) { |
| 81 | 408 | result = %return allocator.alloc(u8, max_size); |
| 82 | 409 | } else { |
| 410 | assert(!is_windows); // resolvePosix called on windows can't use getCwd |
| 83 | 411 | const cwd = %return os.getCwd(allocator); |
| 84 | 412 | defer allocator.free(cwd); |
| 85 | 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 | 417 | %defer allocator.free(result); |
| 90 | 418 | |
| 91 | 419 | for (paths[first_index..]) |p, i| { |
| 92 | | var it = mem.split(p, '/'); |
| 420 | var it = mem.split(p, "/"); |
| 93 | 421 | while (it.next()) |component| { |
| 94 | 422 | if (mem.eql(u8, component, ".")) { |
| 95 | 423 | continue; |
| ... | ... | @@ -119,22 +447,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { |
| 119 | 447 | } |
| 120 | 448 | |
| 121 | 449 | test "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 | |
| 459 | test "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 | |
| 475 | test "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 | |
| 488 | fn testResolveWindows(paths: []const []const u8) -> []u8 { |
| 489 | return %%resolveWindows(&debug.global_allocator, paths); |
| 127 | 490 | } |
| 128 | | fn testResolve(args: ...) -> []u8 { |
| 129 | | return %%resolve(&debug.global_allocator, args); |
| 491 | |
| 492 | fn testResolvePosix(paths: []const []const u8) -> []u8 { |
| 493 | return %%resolvePosix(&debug.global_allocator, paths); |
| 130 | 494 | } |
| 131 | 495 | |
| 132 | 496 | pub 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); |
| 135 | 501 | } |
| 502 | } |
| 503 | |
| 504 | pub fn dirnameWindows(path: []const u8) -> []const u8 { |
| 136 | 505 | if (path.len == 0) |
| 137 | 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 | |
| 535 | pub fn dirnamePosix(path: []const u8) -> []const u8 { |
| 536 | if (path.len == 0) |
| 537 | return path[0..0]; |
| 538 | |
| 138 | 539 | var end_index: usize = path.len - 1; |
| 139 | 540 | while (path[end_index] == '/') { |
| 140 | 541 | if (end_index == 0) |
| ... | ... | @@ -154,25 +555,71 @@ pub fn dirname(path: []const u8) -> []const u8 { |
| 154 | 555 | return path[0..end_index]; |
| 155 | 556 | } |
| 156 | 557 | |
| 157 | | test "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//", ""); |
| 558 | test "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//", ""); |
| 167 | 568 | } |
| 168 | | fn testDirname(input: []const u8, expected_output: []const u8) { |
| 169 | | assert(mem.eql(u8, dirname(input), expected_output)); |
| 569 | |
| 570 | test "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 | |
| 606 | fn testDirnamePosix(input: []const u8, expected_output: []const u8) { |
| 607 | assert(mem.eql(u8, dirnamePosix(input), expected_output)); |
| 608 | } |
| 609 | |
| 610 | fn testDirnameWindows(input: []const u8, expected_output: []const u8) { |
| 611 | assert(mem.eql(u8, dirnameWindows(input), expected_output)); |
| 170 | 612 | } |
| 171 | 613 | |
| 172 | 614 | pub 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); |
| 175 | 619 | } |
| 620 | } |
| 621 | |
| 622 | pub fn basenamePosix(path: []const u8) -> []const u8 { |
| 176 | 623 | if (path.len == 0) |
| 177 | 624 | return []u8{}; |
| 178 | 625 | |
| ... | ... | @@ -193,6 +640,38 @@ pub fn basename(path: []const u8) -> []const u8 { |
| 193 | 640 | return path[start_index + 1..end_index]; |
| 194 | 641 | } |
| 195 | 642 | |
| 643 | pub 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 | |
| 196 | 675 | test "os.path.basename" { |
| 197 | 676 | testBasename("", ""); |
| 198 | 677 | testBasename("/", ""); |
| ... | ... | @@ -206,26 +685,137 @@ test "os.path.basename" { |
| 206 | 685 | testBasename("/aaa/b", "b"); |
| 207 | 686 | testBasename("/a/b", "b"); |
| 208 | 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 | |
| 210 | 714 | fn testBasename(input: []const u8, expected_output: []const u8) { |
| 211 | 715 | assert(mem.eql(u8, basename(input), expected_output)); |
| 212 | 716 | } |
| 213 | 717 | |
| 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 |
| 718 | fn testBasenamePosix(input: []const u8, expected_output: []const u8) { |
| 719 | assert(mem.eql(u8, basenamePosix(input), expected_output)); |
| 720 | } |
| 721 | |
| 722 | fn 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 | 728 | /// string is returned. |
| 729 | /// On Windows this canonicalizes the drive to a capital letter and paths to `\\`. |
| 217 | 730 | pub 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); |
| 220 | 735 | } |
| 221 | | const resolved_from = %return resolve(allocator, from); |
| 736 | } |
| 737 | |
| 738 | pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { |
| 739 | const resolved_from = %return resolveWindows(allocator, [][]const u8{from}); |
| 222 | 740 | defer allocator.free(resolved_from); |
| 223 | 741 | |
| 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 | |
| 810 | pub 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 | 815 | defer allocator.free(resolved_to); |
| 226 | 816 | |
| 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, "/"); |
| 229 | 819 | while (true) { |
| 230 | 820 | const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest()); |
| 231 | 821 | const to_rest = to_it.rest(); |
| ... | ... | @@ -263,21 +853,52 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u |
| 263 | 853 | } |
| 264 | 854 | |
| 265 | 855 | test "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 | | } |
| 279 | | fn 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 | |
| 895 | fn 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 | |
| 900 | fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) { |
| 901 | const result = %%relativeWindows(&debug.global_allocator, from, to); |
| 281 | 902 | assert(mem.eql(u8, result, expected_output)); |
| 282 | 903 | } |
| 283 | 904 | |