| ... | ... | @@ -11,37 +11,221 @@ 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")); |
| 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")); |
| 32 | 48 | |
| 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")); |
| 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")); |
| 35 | 52 | |
| 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 | 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); |
| 68 | } |
| 69 | } |
| 70 | |
| 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; |
| 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 | |
| 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 | { |
| 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 | |
| 173 | test "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 | |
| 180 | pub 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 | |
| 189 | pub fn preferredSep(path: []const u8) -> u8 { |
| 190 | if (is_windows) { |
| 191 | return preferredSepWindows(path); |
| 192 | } else { |
| 193 | return sep_posix; |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | pub fn root(path: []const u8) -> []const u8 { |
| 198 | if (is_windows) { |
| 199 | return rootWindows(path); |
| 200 | } else { |
| 201 | return rootPosix(path); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | pub fn rootWindows(path: []const u8) -> []const u8 { |
| 206 | return drive(path) ?? (networkShare(path) ?? []u8{}); |
| 207 | } |
| 208 | |
| 209 | pub 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 | |
| 216 | pub 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 | |
| 224 | fn asciiLower(byte: u8) -> u8 { |
| 225 | return switch (byte) { |
| 226 | 'A' ... 'Z' => 'a' + (byte - 'A'), |
| 227 | else => byte, |
| 228 | }; |
| 45 | 229 | } |
| 46 | 230 | |
| 47 | 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 | 240 | } |
| 57 | 241 | |
| 58 | 242 | 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"); |
| 243 | if (is_windows) { |
| 244 | return resolveWindows(allocator, paths); |
| 245 | } else { |
| 246 | return resolvePosix(allocator, paths); |
| 61 | 247 | } |
| 62 | | if (paths.len == 0) |
| 248 | } |
| 249 | |
| 250 | pub 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 | 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 | |
| 356 | pub 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 | } |
| 64 | 361 | |
| 65 | 362 | var first_index: usize = 0; |
| 66 | 363 | var have_abs = false; |
| 67 | 364 | var max_size: usize = 0; |
| 68 | 365 | for (paths) |p, i| { |
| 69 | | if (isAbsolute(p)) { |
| 366 | if (isAbsolutePosix(p)) { |
| 70 | 367 | first_index = i; |
| 71 | 368 | have_abs = true; |
| 72 | 369 | max_size = 0; |
| ... | ... | @@ -80,6 +377,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { |
| 80 | 377 | if (have_abs) { |
| 81 | 378 | result = %return allocator.alloc(u8, max_size); |
| 82 | 379 | } else { |
| 380 | assert(!is_windows); // resolvePosix called on windows can't use getCwd |
| 83 | 381 | const cwd = %return os.getCwd(allocator); |
| 84 | 382 | defer allocator.free(cwd); |
| 85 | 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 | 387 | %defer allocator.free(result); |
| 90 | 388 | |
| 91 | 389 | for (paths[first_index..]) |p, i| { |
| 92 | | var it = mem.split(p, '/'); |
| 390 | var it = mem.split(p, "/"); |
| 93 | 391 | while (it.next()) |component| { |
| 94 | 392 | if (mem.eql(u8, component, ".")) { |
| 95 | 393 | continue; |
| ... | ... | @@ -119,22 +417,95 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { |
| 119 | 417 | } |
| 120 | 418 | |
| 121 | 419 | 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")); |
| 420 | const cwd = %%os.getCwd(&debug.global_allocator); |
| 421 | if (is_windows) { |
| 422 | assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd)); |
| 423 | } else { |
| 424 | assert(mem.eql(u8, testResolvePosix([][]const u8{"a/b/c/", "../../.."}), cwd)); |
| 425 | assert(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd)); |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | test "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 | |
| 445 | test "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 | |
| 458 | fn testResolveWindows(paths: []const []const u8) -> []u8 { |
| 459 | return %%resolveWindows(&debug.global_allocator, paths); |
| 127 | 460 | } |
| 128 | | fn testResolve(args: ...) -> []u8 { |
| 129 | | return %%resolve(&debug.global_allocator, args); |
| 461 | |
| 462 | fn testResolvePosix(paths: []const []const u8) -> []u8 { |
| 463 | return %%resolvePosix(&debug.global_allocator, paths); |
| 130 | 464 | } |
| 131 | 465 | |
| 132 | 466 | pub fn dirname(path: []const u8) -> []const u8 { |
| 133 | | if (builtin.os == builtin.Os.windows) { |
| 134 | | @compileError("TODO implement os.path.dirname for windows"); |
| 467 | if (is_windows) { |
| 468 | return dirnameWindows(path); |
| 469 | } else { |
| 470 | return dirnamePosix(path); |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | pub 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 | |
| 505 | pub fn dirnamePosix(path: []const u8) -> []const u8 { |
| 136 | 506 | if (path.len == 0) |
| 137 | 507 | return path[0..0]; |
| 508 | |
| 138 | 509 | var end_index: usize = path.len - 1; |
| 139 | 510 | while (path[end_index] == '/') { |
| 140 | 511 | if (end_index == 0) |
| ... | ... | @@ -154,19 +525,60 @@ pub fn dirname(path: []const u8) -> []const u8 { |
| 154 | 525 | return path[0..end_index]; |
| 155 | 526 | } |
| 156 | 527 | |
| 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//", ""); |
| 528 | test "os.path.dirnamePosix" { |
| 529 | testDirnamePosix("/a/b/c", "/a/b"); |
| 530 | testDirnamePosix("/a/b/c///", "/a/b"); |
| 531 | testDirnamePosix("/a", "/"); |
| 532 | testDirnamePosix("/", "/"); |
| 533 | testDirnamePosix("////", "/"); |
| 534 | testDirnamePosix("", ""); |
| 535 | testDirnamePosix("a", ""); |
| 536 | testDirnamePosix("a/", ""); |
| 537 | testDirnamePosix("a//", ""); |
| 538 | } |
| 539 | |
| 540 | test "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 | } |
| 168 | | fn testDirname(input: []const u8, expected_output: []const u8) { |
| 169 | | assert(mem.eql(u8, dirname(input), expected_output)); |
| 575 | |
| 576 | fn testDirnamePosix(input: []const u8, expected_output: []const u8) { |
| 577 | assert(mem.eql(u8, dirnamePosix(input), expected_output)); |
| 578 | } |
| 579 | |
| 580 | fn testDirnameWindows(input: []const u8, expected_output: []const u8) { |
| 581 | assert(mem.eql(u8, dirnameWindows(input), expected_output)); |
| 170 | 582 | } |
| 171 | 583 | |
| 172 | 584 | pub fn basename(path: []const u8) -> []const u8 { |
| ... | ... | @@ -215,9 +627,18 @@ fn testBasename(input: []const u8, expected_output: []const u8) { |
| 215 | 627 | /// resolve to the same path (after calling ::resolve on each), a zero-length |
| 216 | 628 | /// string is returned. |
| 217 | 629 | 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"); |
| 630 | if (is_windows) { |
| 631 | return windowsRelative(allocator, from, to); |
| 632 | } else { |
| 633 | return posixRelative(allocator, from, to); |
| 220 | 634 | } |
| 635 | } |
| 636 | |
| 637 | fn windowsRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { |
| 638 | @compileError("TODO implement this"); |
| 639 | } |
| 640 | |
| 641 | fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { |
| 221 | 642 | const resolved_from = %return resolve(allocator, from); |
| 222 | 643 | defer allocator.free(resolved_from); |
| 223 | 644 | |
| ... | ... | @@ -263,18 +684,45 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u |
| 263 | 684 | } |
| 264 | 685 | |
| 265 | 686 | 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"); |
| 687 | if (is_windows) { |
| 688 | testRelative("c:/blah\\blah", "d:/games", "d:\\games"); |
| 689 | testRelative("c:/aaaa/bbbb", "c:/aaaa", ".."); |
| 690 | testRelative("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); |
| 691 | testRelative("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); |
| 692 | testRelative("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); |
| 693 | testRelative("c:/aaaa/", "c:/aaaa/cccc", "cccc"); |
| 694 | testRelative("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); |
| 695 | testRelative("c:/aaaa/bbbb", "d:\\", "d:\\"); |
| 696 | testRelative("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); |
| 697 | testRelative("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); |
| 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 | } |
| 279 | 727 | fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) { |
| 280 | 728 | const result = %%relative(&debug.global_allocator, from, to); |