Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

resolveWindows

This function is like a series of cd statements executed one after another. It resolves "." and ".." to the best of its ability, but will not convert relative paths to an absolute path, use Io.Dir.realpath instead. ".." components may persist in the resolved path if the resolved path is relative or drive-relative. Path separators are canonicalized to '\' and drives are canonicalized to capital letters.

The result will not have a trailing path separator, except for the following scenarios:

Each drive has its own current working directory, which is only resolved via the paths provided. In the scenario that the resolved path contains a drive-relative path that can't be resolved using the paths alone, the result will be a drive-relative path. Similarly, in the scenario that the resolved path contains a rooted path that can't be resolved using the paths alone, the result will be a rooted path.

Note: all usage of this function should be audited due to the existence of symlinks. Without performing actual syscalls, resolving .. could be incorrect. This API may break in the future: https://github.com/ziglang/zig/issues/13613

path.resolveWindows
pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8

File

lib/std/fs/path.zig:894

Code

pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 {
    // Avoid heap allocation when paths.len is <= @bitSizeOf(usize) * 2
    // (we use `* 3` because stackFallback uses 1 usize as a length)
    var buf: [3]usize = undefined;
    var bit_set_allocator_state: std.heap.BufferFirstAllocator = .init(@ptrCast(&buf), allocator);
    const bit_set_allocator = bit_set_allocator_state.allocator();
    var relevant_paths: std.bit_set.Dynamic = try .initEmpty(bit_set_allocator, paths.len);
    defer relevant_paths.deinit(bit_set_allocator);

    // Iterate the paths backwards, marking the relevant paths along the way.
    // This also allows us to break from the loop whenever any earlier paths are known to be irrelevant.
    var first_path_i: usize = paths.len;
    const effective_root_path: WindowsPath2(u8) = root: {
        var last_effective_root_path: WindowsPath2(u8) = .{ .kind = .relative, .root = "" };
        var last_rooted_path_i: ?usize = null;
        var last_drive_relative_path_i: usize = undefined;
        while (first_path_i > 0) {
            first_path_i -= 1;
            const parsed = parsePathWindows(u8, paths[first_path_i]);
            switch (parsed.kind) {
                .unc_absolute, .root_local_device, .local_device => {
                    switch (last_effective_root_path.kind) {
                        .rooted => {},
                        .drive_relative => continue,
                        else => {
                            relevant_paths.set(first_path_i);
                        },
                    }
                    break :root parsed;
                },
                .drive_relative, .drive_absolute => {
                    switch (last_effective_root_path.kind) {
                        .drive_relative => if (!compareDiskDesignators(u8, .drive, parsed.root, last_effective_root_path.root)) {
                            continue;
                        } else if (last_rooted_path_i != null) {
                            break :root .{ .kind = .drive_absolute, .root = parsed.root };
                        },
                        .relative => last_effective_root_path = parsed,
                        .rooted => {
                            // This is the end of the line, since the rooted path will always be relative
                            // to this drive letter, and even if the current path is drive-relative, the
                            // rooted-ness makes that irrelevant.
                            //
                            // Therefore, force the kind of the effective root to be drive-absolute in order to
                            // properly resolve a rooted path against a drive-relative one, as the result should
                            // always be drive-absolute.
                            break :root .{ .kind = .drive_absolute, .root = parsed.root };
                        },
                        .drive_absolute, .unc_absolute, .root_local_device, .local_device => unreachable,
                    }
                    relevant_paths.set(first_path_i);
                    last_drive_relative_path_i = first_path_i;
                    if (parsed.kind == .drive_absolute) {
                        break :root parsed;
                    }
                },
                .relative => {
                    switch (last_effective_root_path.kind) {
                        .rooted => continue,
                        .relative => last_effective_root_path = parsed,
                        else => {},
                    }
                    relevant_paths.set(first_path_i);
                },
                .rooted => {
                    switch (last_effective_root_path.kind) {
                        .drive_relative => {},
                        .relative => last_effective_root_path = parsed,
                        .rooted => continue,
                        .drive_absolute, .unc_absolute, .root_local_device, .local_device => unreachable,
                    }
                    if (last_rooted_path_i == null) {
                        last_rooted_path_i = first_path_i;
                        relevant_paths.set(first_path_i);
                    }
                },
            }
        }
        // After iterating, if the pending effective root is drive-relative then that means
        // nothing has led to forcing a drive-absolute root (a path that allows resolving the
        // drive-specific CWD would cause an early break), so we now need to ignore all paths
        // before the most recent drive-relative one. For example, if we're resolving
        // { "\\rooted", "relative", "C:drive-relative" }
        // then the `\rooted` and `relative` needs to be ignored since we can't
        // know what the rooted path is rooted against as that'd require knowing the CWD.
        if (last_effective_root_path.kind == .drive_relative) {
            for (0..last_drive_relative_path_i) |i| {
                relevant_paths.unset(i);
            }
        }
        break :root last_effective_root_path;
    };

    var result: std.ArrayList(u8) = .empty;
    defer result.deinit(allocator);

    var want_path_sep_between_root_and_component = false;
    switch (effective_root_path.kind) {
        .root_local_device, .local_device => {
            try result.ensureUnusedCapacity(allocator, 3);
            result.appendSliceAssumeCapacity("\\\\");
            result.appendAssumeCapacity(effective_root_path.root[2]); // . or ?
            want_path_sep_between_root_and_component = true;
        },
        .drive_absolute, .drive_relative => {
            try result.ensureUnusedCapacity(allocator, effective_root_path.root.len);
            result.appendAssumeCapacity(std.ascii.toUpper(effective_root_path.root[0]));
            result.appendAssumeCapacity(':');
            if (effective_root_path.kind == .drive_absolute) {
                result.appendAssumeCapacity('\\');
            }
        },
        .unc_absolute => {
            const unc = parseUNC(u8, effective_root_path.root);

            const root_len = len: {
                var len: usize = 2 + unc.server.len + unc.share.len;
                if (unc.sep_after_server) len += 1;
                if (unc.sep_after_share) len += 1;
                break :len len;
            };
            try result.ensureUnusedCapacity(allocator, root_len);
            result.appendSliceAssumeCapacity("\\\\");
            if (unc.server.len > 0 or unc.sep_after_server) {
                result.appendSliceAssumeCapacity(unc.server);
                if (unc.sep_after_server)
                    result.appendAssumeCapacity('\\')
                else
                    want_path_sep_between_root_and_component = true;
            }
            if (unc.share.len > 0) {
                result.appendSliceAssumeCapacity(unc.share);
                if (unc.sep_after_share)
                    result.appendAssumeCapacity('\\')
                else
                    want_path_sep_between_root_and_component = true;
            }
        },
        .rooted => {
            try result.append(allocator, '\\');
        },
        .relative => {},
    }

    const root_len = result.items.len;
    var negative_count: usize = 0;
    for (paths[first_path_i..], first_path_i..) |path, i| {
        if (!relevant_paths.isSet(i)) continue;

        const parsed = parsePathWindows(u8, path);
        const skip_len = parsed.root.len;
        var it = mem.tokenizeAny(u8, path[skip_len..], "/\\");
        while (it.next()) |component| {
            if (mem.eql(u8, component, ".")) {
                continue;
            } else if (mem.eql(u8, component, "..")) {
                if (result.items.len == 0 or (result.items.len == root_len and effective_root_path.kind == .drive_relative)) {
                    negative_count += 1;
                    continue;
                }
                while (true) {
                    if (result.items.len == root_len) {
                        break;
                    }
                    const end_with_sep = PathType.windows.isSep(u8, result.items[result.items.len - 1]);
                    result.items.len -= 1;
                    if (end_with_sep) break;
                }
            } else if (result.items.len == root_len and !want_path_sep_between_root_and_component) {
                try result.appendSlice(allocator, component);
            } else {
                try result.ensureUnusedCapacity(allocator, 1 + component.len);
                result.appendAssumeCapacity('\\');
                result.appendSliceAssumeCapacity(component);
            }
        }
    }

    if (root_len != 0 and result.items.len == root_len and negative_count == 0) {
        return result.toOwnedSlice(allocator);
    }

    if (result.items.len == root_len) {
        if (negative_count == 0) {
            return allocator.dupe(u8, ".");
        }

        try result.ensureTotalCapacityPrecise(allocator, 3 * negative_count - 1);
        for (0..negative_count - 1) |_| {
            result.appendSliceAssumeCapacity("..\\");
        }
        result.appendSliceAssumeCapacity("..");
    } else {
        const dest = try result.addManyAt(allocator, root_len, 3 * negative_count);
        for (0..negative_count) |i| {
            dest[i * 3 ..][0..3].* = "..\\".*;
        }
    }

    return result.toOwnedSlice(allocator);
}