From 7cfab2fb5f0f0dce81feab9dd8ad3d3e1b435044 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 8 Oct 2017 21:16:10 -0400 Subject: [PATCH] implement std.os.relative for windows --- std/os/index.zig | 13 +++ std/os/path.zig | 203 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 158 insertions(+), 58 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index e98b6d4b5a..fa51065299 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -1043,3 +1043,16 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void { else => error.Unexpected, }; } + +test "std.os" { + _ = @import("child_process.zig"); + _ = @import("darwin_errno.zig"); + _ = @import("darwin.zig"); + _ = @import("get_user_id.zig"); + _ = @import("linux_errno.zig"); + //_ = @import("linux_i386.zig"); + _ = @import("linux_x86_64.zig"); + _ = @import("linux.zig"); + _ = @import("path.zig"); + _ = @import("windows/index.zig"); +} diff --git a/std/os/path.zig b/std/os/path.zig index 133b3d70ef..8c00ae7a5e 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -195,12 +195,15 @@ pub fn rootPosix(path: []const u8) -> []const u8 { return path[0..1]; } -pub fn drivesEqual(drive1: []const u8, drive2: []const u8) -> bool { - assert(drive1.len == 2); - assert(drive2.len == 2); - assert(drive1[1] == ':'); - assert(drive2[1] == ':'); - return asciiUpper(drive1[0]) == asciiUpper(drive2[0]); +// TODO ASCII is wrong, we actually need full unicode support to compare paths. +fn networkShareServersEql(ns1: []const u8, ns2: []const u8) -> bool { + const sep1 = ns1[0]; + const sep2 = ns2[0]; + + var it1 = mem.split(ns1, []u8{sep1}); + var it2 = mem.split(ns2, []u8{sep2}); + + return asciiEqlIgnoreCase(??it1.next(), ??it2.next()); } fn asciiUpper(byte: u8) -> u8 { @@ -210,6 +213,17 @@ fn asciiUpper(byte: u8) -> u8 { }; } +fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) -> bool { + if (s1.len != s2.len) + return false; + var i: usize = 0; + while (i < s1.len) : (i += 1) { + if (asciiUpper(s1[i]) != asciiUpper(s2[i])) + return false; + } + return true; +} + /// Converts the command line arguments into a slice and calls `resolveSlice`. pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { var paths: [args.len][]const u8 = undefined; @@ -721,26 +735,95 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) { assert(mem.eql(u8, basenameWindows(input), expected_output)); } -/// Returns the relative path from ::from to ::to. If ::from and ::to each -/// resolve to the same path (after calling ::resolve on each), a zero-length +/// Returns the relative path from `from` to `to`. If `from` and `to` each +/// resolve to the same path (after calling `resolve` on each), a zero-length /// string is returned. +/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`. pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { if (is_windows) { - return windowsRelative(allocator, from, to); + return relativeWindows(allocator, from, to); } else { - return posixRelative(allocator, from, to); + return relativePosix(allocator, from, to); } } -fn windowsRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { - @compileError("TODO implement this"); -} - -fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { - const resolved_from = %return resolve(allocator, from); +pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { + const resolved_from = %return resolveWindows(allocator, [][]const u8{from}); defer allocator.free(resolved_from); - const resolved_to = %return resolve(allocator, to); + var clean_up_resolved_to = true; + const resolved_to = %return resolveWindows(allocator, [][]const u8{to}); + defer if (clean_up_resolved_to) allocator.free(resolved_to); + + const result_is_to = if (drive(resolved_to)) |to_drive| { + if (drive(resolved_from)) |from_drive| { + asciiUpper(from_drive[0]) != asciiUpper(to_drive[0]) + } else { + true + } + } else if (networkShare(resolved_to)) |to_ns| { + if (networkShare(resolved_from)) |from_ns| { + !networkShareServersEql(to_ns, from_ns) + } else { + true + } + } else { + unreachable + }; + if (result_is_to) { + clean_up_resolved_to = false; + return resolved_to; + } + + var from_it = mem.split(resolved_from, "/\\"); + var to_it = mem.split(resolved_to, "/\\"); + while (true) { + const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest()); + const to_rest = to_it.rest(); + if (to_it.next()) |to_component| { + // TODO ASCII is wrong, we actually need full unicode support to compare paths. + if (asciiEqlIgnoreCase(from_component, to_component)) + continue; + } + var up_count: usize = 1; + while (from_it.next()) |_| { + up_count += 1; + } + const up_index_end = up_count * "..\\".len; + const result = %return allocator.alloc(u8, up_index_end + to_rest.len); + %defer allocator.free(result); + + var result_index: usize = 0; + while (result_index < up_index_end) { + result[result_index] = '.'; + result_index += 1; + result[result_index] = '.'; + result_index += 1; + result[result_index] = '\\'; + result_index += 1; + } + // shave off the trailing slash + result_index -= 1; + + var rest_it = mem.split(to_rest, "/\\"); + while (rest_it.next()) |to_component| { + result[result_index] = '\\'; + result_index += 1; + mem.copy(u8, result[result_index..], to_component); + result_index += to_component.len; + } + + return result[0..result_index]; + } + + return []u8{}; +} + +pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]u8 { + const resolved_from = %return resolvePosix(allocator, [][]const u8{from}); + defer allocator.free(resolved_from); + + const resolved_to = %return resolvePosix(allocator, [][]const u8{to}); defer allocator.free(resolved_to); var from_it = mem.split(resolved_from, "/"); @@ -782,48 +865,52 @@ fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[] } test "os.path.relative" { - if (is_windows) { - testRelative("c:/blah\\blah", "d:/games", "d:\\games"); - testRelative("c:/aaaa/bbbb", "c:/aaaa", ".."); - testRelative("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); - testRelative("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); - testRelative("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); - testRelative("c:/aaaa/", "c:/aaaa/cccc", "cccc"); - testRelative("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); - testRelative("c:/aaaa/bbbb", "d:\\", "d:\\"); - testRelative("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); - testRelative("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); - testRelative("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); - testRelative("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); - testRelative("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); - testRelative("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); - testRelative("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); - testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); - testRelative("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); - testRelative("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); - testRelative("C:\\baz-quux", "C:\\baz", "..\\baz"); - testRelative("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); - testRelative("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"); - testRelative("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"); - testRelative("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); - testRelative("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz") - } else { - testRelative("/var/lib", "/var", ".."); - testRelative("/var/lib", "/bin", "../../bin"); - testRelative("/var/lib", "/var/lib", ""); - testRelative("/var/lib", "/var/apache", "../apache"); - testRelative("/var/", "/var/lib", "lib"); - testRelative("/", "/var/lib", "var/lib"); - testRelative("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); - testRelative("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); - testRelative("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); - testRelative("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); - testRelative("/baz-quux", "/baz", "../baz"); - testRelative("/baz", "/baz-quux", "../baz-quux"); - } + testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); + testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); + testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); + testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); + testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); + testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc"); + testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); + testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\"); + testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); + testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); + testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); + testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); + testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); + testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); + testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); + testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); + testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); + testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); + testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz"); + testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); + testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"); + testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"); + testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); + testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"); + + testRelativePosix("/var/lib", "/var", ".."); + testRelativePosix("/var/lib", "/bin", "../../bin"); + testRelativePosix("/var/lib", "/var/lib", ""); + testRelativePosix("/var/lib", "/var/apache", "../apache"); + testRelativePosix("/var/", "/var/lib", "lib"); + testRelativePosix("/", "/var/lib", "var/lib"); + testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); + testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); + testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); + testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); + testRelativePosix("/baz-quux", "/baz", "../baz"); + testRelativePosix("/baz", "/baz-quux", "../baz-quux"); } -fn testRelative(from: []const u8, to: []const u8, expected_output: []const u8) { - const result = %%relative(&debug.global_allocator, from, to); + +fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) { + const result = %%relativePosix(&debug.global_allocator, from, to); + assert(mem.eql(u8, result, expected_output)); +} + +fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) { + const result = %%relativeWindows(&debug.global_allocator, from, to); assert(mem.eql(u8, result, expected_output)); }