Returns the non-absolute path from from to to according to Windows rules.
Other than memory allocation, this is a pure function; the result solely depends on the input parameters.
If from and to each resolve to the same path (after calling resolve
on each), a zero-length string is returned.
The result is not guaranteed to be relative, as the paths may be on
different volumes. In that case, the result will be the canonicalized
absolute path of to.
Per-drive CWDs are stored in special semi-hidden environment variables of
the format =<drive-letter>:, e.g. =C:. This type of CWD is purely a
shell concept, so there's no guarantee that it'll be set or that it'll even
be accurate. This is the only reason for the environ_map parameter. null is
treated equivalent to the environment variable missing.
pub fn relativeWindows(
gpa: Allocator,
cwd: []const u8,
environ_map: ?*const std.process.Environ.Map,
from: []const u8,
to: []const u8,
) Allocator.Error![]u8
pub fn relativeWindows(
gpa: Allocator,
cwd: []const u8,
environ_map: ?*const std.process.Environ.Map,
from: []const u8,
to: []const u8,
) Allocator.Error![]u8 {
const parsed_from = parsePathWindows(u8, from);
const parsed_to = parsePathWindows(u8, to);
const result_is_always_to = x: {
if (parsed_from.kind != parsed_to.kind) {
break :x false;
}
switch (parsed_from.kind) {
.drive_relative, .drive_absolute => {
break :x !compareDiskDesignators(u8, .drive, parsed_from.root, parsed_to.root);
},
.unc_absolute => {
break :x !compareDiskDesignators(u8, .unc, parsed_from.root, parsed_to.root);
},
.relative, .rooted, .local_device => break :x false,
.root_local_device => break :x true,
}
};
if (result_is_always_to) {
return windowsResolveAgainstCwd(gpa, cwd, environ_map, to, parsed_to);
}
const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, environ_map, from, parsed_from);
defer gpa.free(resolved_from);
var clean_up_resolved_to = true;
const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, environ_map, to, parsed_to);
defer if (clean_up_resolved_to) gpa.free(resolved_to);
const parsed_resolved_from = parsePathWindows(u8, resolved_from);
const parsed_resolved_to = parsePathWindows(u8, resolved_to);
const result_is_to = x: {
if (parsed_resolved_from.kind != parsed_resolved_to.kind) {
break :x true;
}
switch (parsed_resolved_from.kind) {
.drive_absolute, .drive_relative => {
break :x !compareDiskDesignators(u8, .drive, parsed_resolved_from.root, parsed_resolved_to.root);
},
.unc_absolute => {
break :x !compareDiskDesignators(u8, .unc, parsed_resolved_from.root, parsed_resolved_to.root);
},
.relative, .rooted, .local_device => break :x false,
.root_local_device => break :x true,
}
};
if (result_is_to) {
clean_up_resolved_to = false;
return resolved_to;
}
var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\");
var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\");
while (true) {
const from_component = from_it.next() orelse return gpa.dupe(u8, to_it.rest());
const to_rest = to_it.rest();
if (to_it.next()) |to_component| {
if (eqlIgnoreCaseWtf8(from_component, to_component))
continue;
}
var up_index_end = "..".len;
while (from_it.next()) |_| {
up_index_end += "\\..".len;
}
const result = try gpa.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);
errdefer gpa.free(result);
result[0..2].* = "..".*;
var result_index: usize = 2;
while (result_index < up_index_end) {
result[result_index..][0..3].* = "\\..".*;
result_index += 3;
}
var rest_it = mem.tokenizeAny(u8, to_rest, "/\\");
while (rest_it.next()) |to_component| {
result[result_index] = '\\';
result_index += 1;
@memcpy(result[result_index..][0..to_component.len], to_component);
result_index += to_component.len;
}
return gpa.realloc(result, result_index);
}
return [_]u8{};
}