Commit dcf5c9074e

Andrew Kelley <superjoe30@gmail.com>
2017-10-06 23:21:21
more std.os.path work for windows
1 parent 08ee69d
Changed files (4)
std/os/index.zig
@@ -384,7 +384,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
     // +1 for the null terminating byte
     const path_buf = %return allocator.alloc(u8, PATH.len + exe_path.len + 2);
     defer allocator.free(path_buf);
-    var it = mem.split(PATH, ':');
+    var it = mem.split(PATH, ":");
     var seen_eacces = false;
     var err: usize = undefined;
     while (it.next()) |search_path| {
std/os/path.zig
@@ -139,18 +139,18 @@ pub fn networkShare(path: []const u8) -> ?[]const u8 {
     if (path.len < "//a/b".len)
         return null;
 
+    // TODO when I combined these together with `inline for` the compiler crashed
     {
         const this_sep = '/';
         const two_sep = []u8{this_sep, this_sep};
         if (mem.startsWith(u8, path, two_sep)) {
             if (path[2] == this_sep)
                 return null;
-            const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null;
-            const next_start = index_host + 1;
-            if (next_start >= path.len)
-                return null;
-            const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len;
-            return path[0..index_root];
+
+            var it = mem.split(path, []u8{this_sep});
+            _ = (it.next() ?? return null);
+            _ = (it.next() ?? return null);
+            return path[0..it.index];
         }
     }
     {
@@ -159,12 +159,11 @@ pub fn networkShare(path: []const u8) -> ?[]const u8 {
         if (mem.startsWith(u8, path, two_sep)) {
             if (path[2] == this_sep)
                 return null;
-            const index_host = mem.indexOfScalarPos(u8, path, 3, this_sep) ?? return null;
-            const next_start = index_host + 1;
-            if (next_start >= path.len)
-                return null;
-            const index_root = mem.indexOfScalarPos(u8, path, next_start, this_sep) ?? path.len;
-            return path[0..index_root];
+
+            var it = mem.split(path, []u8{this_sep});
+            _ = (it.next() ?? return null);
+            _ = (it.next() ?? return null);
+            return path[0..it.index];
         }
     }
     return null;
@@ -177,23 +176,6 @@ test "os.path.networkShare" {
     assert(networkShare("\\\\a\\") == null);
 }
 
-pub fn preferredSepWindows(path: []const u8) -> u8 {
-    for (path) |byte| {
-        if (byte == '/' or byte == '\\') {
-            return byte;
-        }
-    }
-    return sep_windows;
-}
-
-pub fn preferredSep(path: []const u8) -> u8 {
-    if (is_windows) {
-        return preferredSepWindows(path);
-    } else {
-        return sep_posix;
-    }
-}
-
 pub fn root(path: []const u8) -> []const u8 {
     if (is_windows) {
         return rootWindows(path);
@@ -206,7 +188,7 @@ pub fn rootWindows(path: []const u8) -> []const u8 {
     return drive(path) ?? (networkShare(path) ?? []u8{});
 }
 
-pub fn rootPosix(path: []const u8) -> ?[]const u8 {
+pub fn rootPosix(path: []const u8) -> []const u8 {
     if (path.len == 0 or path[0] != '/')
         return []u8{};
 
@@ -218,18 +200,17 @@ pub fn drivesEqual(drive1: []const u8, drive2: []const u8) -> bool {
     assert(drive2.len == 2);
     assert(drive1[1] == ':');
     assert(drive2[1] == ':');
-    return asciiLower(drive1[0]) == asciiLower(drive2[0]);
+    return asciiUpper(drive1[0]) == asciiUpper(drive2[0]);
 }
 
-fn asciiLower(byte: u8) -> u8 {
+fn asciiUpper(byte: u8) -> u8 {
     return switch (byte) {
-        'A' ... 'Z' => 'a' + (byte - 'A'),
+        'a' ... 'z' => 'A' + (byte - 'a'),
         else => byte,
     };
 }
 
-/// This function is like a series of `cd` statements executed one after another.
-/// The result does not have a trailing path separator.
+/// 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;
     comptime var arg_i = 0;
@@ -239,6 +220,7 @@ pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 {
     return resolveSlice(allocator, paths);
 }
 
+/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
 pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
     if (is_windows) {
         return resolveWindows(allocator, paths);
@@ -247,6 +229,12 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
     }
 }
 
+/// This function is like a series of `cd` statements executed one after another.
+/// It resolves "." and "..".
+/// The result does not have a trailing path separator.
+/// If all paths are relative it uses the current working directory as a starting point.
+/// Each drive has its own current working directory.
+/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
 pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
     if (paths.len == 0) {
         assert(is_windows); // resolveWindows called on non windows can't use getCwd
@@ -254,7 +242,7 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8
     }
 
     // determine which drive we want to result with
-    var result_drive: ?[]const u8 = null;
+    var result_drive_upcase: ?u8 = null;
     var have_abs = false;
     var first_index: usize = 0;
     var max_size: usize = 0;
@@ -266,25 +254,28 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8
             max_size = 0;
         }
         if (drive(p)) |d| {
-            result_drive = d;
-        } else if (is_abs) {
-            result_drive = null;
+            result_drive_upcase = asciiUpper(d[0]);
+        } else if (networkShare(p)) |_| {
+            result_drive_upcase = null;
         }
         max_size += p.len + 1;
     }
 
+
     // if we will result with a drive, loop again to determine
     // which is the first time the drive is absolutely specified, if any
     // and count up the max bytes for paths related to this drive
-    if (result_drive) |res_dr| {
+    if (result_drive_upcase) |res_dr| {
         have_abs = false;
         first_index = 0;
-        max_size = 0;
+        max_size = "_:".len;
         var correct_drive = false;
 
         for (paths) |p, i| {
             if (drive(p)) |dr| {
-                correct_drive = drivesEqual(dr, res_dr);
+                correct_drive = asciiUpper(dr[0]) == res_dr;
+            } else if (networkShare(p)) |_| {
+                continue;
             }
             if (!correct_drive) {
                 continue;
@@ -292,20 +283,46 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8
             const is_abs = isAbsoluteWindows(p);
             if (is_abs) {
                 first_index = i;
-                max_size = 0;
+                max_size = "_:".len;
+                have_abs = true;
             }
             max_size += p.len + 1;
         }
     }
 
+    var drive_buf = "_:";
     var result: []u8 = undefined;
     var result_index: usize = 0;
+    var root_slice: []const u8 = undefined;
 
     if (have_abs) {
         result = %return allocator.alloc(u8, max_size);
-        mem.copy(u8, result, paths[first_index]);
-        result_index += paths[first_index].len;
-        first_index += 1;
+
+        if (result_drive_upcase) |res_dr| {
+            drive_buf[0] = res_dr;
+            root_slice = drive_buf[0..];
+
+            mem.copy(u8, result, root_slice);
+            result_index += root_slice.len;
+        } else {
+            // We know it looks like //a/b or \\a\b because of earlier code
+            var it = mem.split(paths[first_index], "/\\");
+            const server_name = ??it.next();
+            const other_name = ??it.next();
+
+            result[result_index] = '\\';
+            result_index += 1;
+            result[result_index] = '\\';
+            result_index += 1;
+            mem.copy(u8, result[result_index..], server_name);
+            result_index += server_name.len;
+            result[result_index] = '\\';
+            result_index += 1;
+            mem.copy(u8, result[result_index..], other_name);
+            result_index += other_name.len;
+            
+            root_slice = result[0..result_index];
+        }
     } else {
         assert(is_windows); // resolveWindows called on non windows can't use getCwd
         // TODO get cwd for result_drive if applicable
@@ -314,35 +331,37 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8
         result = %return allocator.alloc(u8, max_size + cwd.len + 1);
         mem.copy(u8, result, cwd);
         result_index += cwd.len;
+
+        root_slice = rootWindows(result[0..result_index]);
     }
     %defer allocator.free(result);
 
-    var correct_drive = false;
-    const rootSlice = rootWindows(result[0..result_index]);
-    const preferred_path_sep = preferredSepWindows(result[0..result_index]);
+    var correct_drive = true;
     for (paths[first_index..]) |p, i| {
-        if (result_drive) |res_dr| {
+        if (result_drive_upcase) |res_dr| {
             if (drive(p)) |dr| {
-                correct_drive = drivesEqual(dr, res_dr);
+                correct_drive = asciiUpper(dr[0]) == res_dr;
+            } else if (networkShare(p)) |_| {
+                continue;
             }
             if (!correct_drive) {
                 continue;
             }
         }
-        var it = mem.split(p, "/\\");
+        var it = mem.split(p[rootWindows(p).len..], "/\\");
         while (it.next()) |component| {
             if (mem.eql(u8, component, ".")) {
                 continue;
             } else if (mem.eql(u8, component, "..")) {
                 while (true) {
-                    if (result_index == 0 or result_index == rootSlice.len)
+                    if (result_index == 0 or result_index == root_slice.len)
                         break;
                     result_index -= 1;
                     if (result[result_index] == '\\' or result[result_index] == '/')
                         break;
                 }
             } else {
-                result[result_index] = preferred_path_sep;
+                result[result_index] = sep_windows;
                 result_index += 1;
                 mem.copy(u8, result[result_index..], component);
                 result_index += component.len;
@@ -350,9 +369,18 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) -> %[]u8
         }
     }
 
+    if (result_index == root_slice.len) {
+        result[result_index] = '\\';
+        result_index += 1;
+    }
+
     return result[0..result_index];
 }
 
+/// This function is like a series of `cd` statements executed one after another.
+/// It resolves "." and "..".
+/// The result does not have a trailing path separator.
+/// If all paths are relative it uses the current working directory as a starting point.
 pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) -> %[]u8 {
     if (paths.len == 0) {
         assert(!is_windows); // resolvePosix called on windows can't use getCwd
@@ -427,17 +455,17 @@ test "os.path.resolve" {
 }
 
 test "os.path.resolveWindows" {
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "c:\\blah\\a"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "c:\\blah\\a"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "d:\\e.exe"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "c:\\some\\file"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "d:\\ignore\\some\\dir"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "c:../a"}), "C:\\blah\\a"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/blah\\blah", "d:/games", "C:../a"}), "C:\\blah\\a"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "d:\\a/b\\c/d", "\\e.exe"}), "D:\\e.exe"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/ignore", "c:/some/file"}), "C:\\some\\file"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"d:/ignore", "d:some/dir//"}), "D:\\ignore\\some\\dir"));
     assert(mem.eql(u8, testResolveWindows([][]const u8{"//server/share", "..", "relative\\"}), "\\\\server\\share\\relative"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "c:\\"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "c:\\dir"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//"}), "C:\\"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//dir"}), "C:\\dir"));
     assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server/share"}), "\\\\server\\share\\"));
     assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "//server//share"}), "\\\\server\\share\\"));
-    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "c:\\some\\dir"));
+    assert(mem.eql(u8, testResolveWindows([][]const u8{"c:/", "///some//dir"}), "C:\\some\\dir"));
     assert(mem.eql(u8, testResolveWindows([][]const u8{"C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"}),
         "C:\\foo\\tmp.3\\cycles\\root.js"));
 }
@@ -475,27 +503,27 @@ pub fn dirnameWindows(path: []const u8) -> []const u8 {
     if (path.len == 0)
         return path[0..0];
 
-    const rootSlice = rootWindows(path);
-    if (path.len == rootSlice.len)
+    const root_slice = rootWindows(path);
+    if (path.len == root_slice.len)
         return path;
 
-    const have_root_slash = path.len > rootSlice.len and (path[rootSlice.len] == '/' or path[rootSlice.len] == '\\');
+    const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
 
     var end_index: usize = path.len - 1;
 
-    while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > rootSlice.len) {
+    while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) {
         if (end_index == 0)
             return path[0..0];
         end_index -= 1;
     }
 
-    while (path[end_index] != '/' and path[end_index] != '\\' and end_index > rootSlice.len) {
+    while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) {
         if (end_index == 0)
             return path[0..0];
         end_index -= 1;
     }
 
-    if (have_root_slash and end_index == rootSlice.len) {
+    if (have_root_slash and end_index == root_slice.len) {
         end_index += 1;
     }
 
@@ -645,8 +673,8 @@ fn posixRelative(allocator: &Allocator, from: []const u8, to: []const u8) -> %[]
     const resolved_to = %return resolve(allocator, to);
     defer allocator.free(resolved_to);
 
-    var from_it = mem.split(resolved_from, '/');
-    var to_it = mem.split(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();
std/build.zig
@@ -309,7 +309,7 @@ pub const Builder = struct {
 
     fn processNixOSEnvVars(self: &Builder) {
         if (os.getEnv("NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {
-            var it = mem.split(nix_cflags_compile, ' ');
+            var it = mem.split(nix_cflags_compile, " ");
             while (true) {
                 const word = it.next() ?? break;
                 if (mem.eql(u8, word, "-isystem")) {
@@ -325,7 +325,7 @@ pub const Builder = struct {
             }
         }
         if (os.getEnv("NIX_LDFLAGS")) |nix_ldflags| {
-            var it = mem.split(nix_ldflags, ' ');
+            var it = mem.split(nix_ldflags, " ");
             while (true) {
                 const word = it.next() ?? break;
                 if (mem.eql(u8, word, "-rpath")) {
std/mem.zig
@@ -324,7 +324,7 @@ pub fn split(buffer: []const u8, split_bytes: []const u8) -> SplitIterator {
 }
 
 test "mem.split" {
-    var it = split("   abc def   ghi  ", ' ');
+    var it = split("   abc def   ghi  ", " ");
     assert(eql(u8, ??it.next(), "abc"));
     assert(eql(u8, ??it.next(), "def"));
     assert(eql(u8, ??it.next(), "ghi"));
@@ -355,7 +355,15 @@ const SplitIterator = struct {
         return self.buffer[start..end];
     }
 
-    fn isSplitByte(self: &SplitIterator, byte: u8) -> bool {
+    /// Returns a slice of the remaining bytes. Does not affect iterator state.
+    pub fn rest(self: &const SplitIterator) -> []const u8 {
+        // move to beginning of token
+        var index: usize = self.index;
+        while (index < self.buffer.len and self.isSplitByte(self.buffer[index])) : (index += 1) {}
+        return self.buffer[index..];
+    }
+
+    fn isSplitByte(self: &const SplitIterator, byte: u8) -> bool {
         for (self.split_bytes) |split_byte| {
             if (byte == split_byte) {
                 return true;