| ... | ... | @@ -860,21 +860,84 @@ fn testCompareDiskDesignators(expected_result: bool, kind: DiskDesignatorKind, p |
| 860 | 860 | try std.testing.expectEqual(expected_result, compareDiskDesignators(u16, kind, wtf16_buf1[0..w1_len], wtf16_buf2[0..w2_len])); |
| 861 | 861 | } |
| 862 | 862 | |
| 863 | | /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. |
| 864 | | pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 { |
| 865 | | if (native_os == .windows) { |
| 866 | | return resolveWindows(allocator, paths); |
| 867 | | } else { |
| 868 | | return resolvePosix(allocator, paths); |
| 863 | /// Deprecated in favor of `resolveAlloc`. |
| 864 | pub const resolve = resolveAlloc; |
| 865 | /// Deprecated in favor of `resolveAllocWindows`. |
| 866 | pub const resolveWindows = resolveAllocWindows; |
| 867 | /// Deprecated in favor of `resolveAllocPosix`. |
| 868 | pub const resolvePosix = resolveAllocPosix; |
| 869 | |
| 870 | /// Deprecated in favor of `relativeAlloc`. |
| 871 | pub const relative = relativeAlloc; |
| 872 | /// Deprecated in favor of `relativeAllocPosix`. |
| 873 | pub const relativePosix = relativeAllocPosix; |
| 874 | /// Deprecated in favor of `relativeAllocWindows`. |
| 875 | pub const relativeWindows = relativeAllocWindows; |
| 876 | |
| 877 | /// On Windows, calls `resolveAllocWindows`; otherwise calls `resolveAllocPosix`. |
| 878 | /// |
| 879 | /// See also: |
| 880 | /// `resolveAppend` |
| 881 | pub fn resolveAlloc(gpa: Allocator, paths: []const []const u8) Allocator.Error![]u8 { |
| 882 | switch (native_os) { |
| 883 | .windows => return resolveAllocWindows(gpa, paths), |
| 884 | else => return resolveAllocPosix(gpa, paths), |
| 885 | } |
| 886 | } |
| 887 | |
| 888 | /// On Windows, calls `resolveAppendWindows`; otherwise calls `resolveAppendPosix`. |
| 889 | /// |
| 890 | /// See also: |
| 891 | /// `resolveAlloc` |
| 892 | pub fn resolveAppend(gpa: Allocator, al: *std.ArrayList(u8), paths: []const []const u8) Allocator.Error!void { |
| 893 | switch (native_os) { |
| 894 | .windows => return resolveAppendWindows(gpa, al, paths), |
| 895 | else => return resolveAppendPosix(gpa, al, paths), |
| 869 | 896 | } |
| 870 | 897 | } |
| 871 | 898 | |
| 872 | | /// This function is like a series of `cd` statements executed one after another. |
| 873 | | /// It resolves "." and ".." to the best of its ability, but will not convert relative paths to |
| 874 | | /// an absolute path, use Io.Dir.realpath instead. |
| 875 | | /// ".." components may persist in the resolved path if the resolved path is relative or drive-relative. |
| 899 | /// Simulates a series of relative directory changes on a virtual filesystem that has no symlinks, returning the result |
| 900 | /// as an allocated slice. |
| 901 | /// |
| 902 | /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. |
| 903 | /// |
| 904 | /// "." and ".." are resolved but will not convert relative paths to an absolute path. ".." components may persist in |
| 905 | /// the resolved path if the resolved path is relative or drive-relative. |
| 906 | /// |
| 907 | /// The result will not have a trailing path separator, except for the following scenarios: |
| 908 | /// - The resolved path is drive-absolute with no components (e.g. `C:\`). |
| 909 | /// - The resolved path is a UNC path with only a server name, and the input path contained a trailing separator |
| 910 | /// (e.g. `\\server\`). |
| 911 | /// - The resolved path is a UNC path with no components after the share name, and the input path contained a |
| 912 | /// trailing separator (e.g. `\\server\share\`). |
| 913 | /// |
| 914 | /// Each drive has its own current working directory, which is only resolved via the paths provided. In the scenario |
| 915 | /// that the resolved path contains a drive-relative path that can't be resolved using the paths alone, the result will |
| 916 | /// be a drive-relative path. Similarly, in the scenario that the resolved path contains a rooted path that can't be |
| 917 | /// resolved using the paths alone, the result will be a rooted path. |
| 918 | /// |
| 919 | /// This function does not perform any syscalls. Executing this series of path lookups on an actual filesystem may |
| 920 | /// produce different results due to symlinks. |
| 921 | /// |
| 922 | /// See also: |
| 923 | /// * `resolveAlloc` |
| 924 | /// * `resolveAppendWindows` |
| 925 | pub fn resolveAllocWindows(gpa: Allocator, paths: []const []const u8) Allocator.Error![]u8 { |
| 926 | var buffer: std.ArrayList(u8) = .empty; |
| 927 | defer buffer.deinit(gpa); |
| 928 | try resolveAppendWindows(gpa, &buffer, paths); |
| 929 | try buffer.shrinkToLen(gpa); |
| 930 | return buffer.toOwnedSliceAssert(); |
| 931 | } |
| 932 | |
| 933 | /// Simulates a series of relative directory changes on a virtual filesystem that has no symlinks, appending the result |
| 934 | /// to the provided array list. |
| 935 | /// |
| 876 | 936 | /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. |
| 877 | 937 | /// |
| 938 | /// "." and ".." are resolved but will not convert relative paths to an absolute path. ".." components may persist in |
| 939 | /// the resolved path if the resolved path is relative or drive-relative. |
| 940 | /// |
| 878 | 941 | /// The result will not have a trailing path separator, except for the following scenarios: |
| 879 | 942 | /// - The resolved path is drive-absolute with no components (e.g. `C:\`). |
| 880 | 943 | /// - The resolved path is a UNC path with only a server name, and the input path contained a trailing separator |
| ... | ... | @@ -882,20 +945,22 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error! |
| 882 | 945 | /// - The resolved path is a UNC path with no components after the share name, and the input path contained a |
| 883 | 946 | /// trailing separator (e.g. `\\server\share\`). |
| 884 | 947 | /// |
| 885 | | /// Each drive has its own current working directory, which is only resolved via the paths provided. |
| 886 | | /// In the scenario that the resolved path contains a drive-relative path that can't be resolved using the paths alone, |
| 887 | | /// the result will be a drive-relative path. |
| 888 | | /// Similarly, in the scenario that the resolved path contains a rooted path that can't be resolved using the paths alone, |
| 889 | | /// the result will be a rooted path. |
| 948 | /// Each drive has its own current working directory, which is only resolved via the paths provided. In the scenario |
| 949 | /// that the resolved path contains a drive-relative path that can't be resolved using the paths alone, the result will |
| 950 | /// be a drive-relative path. Similarly, in the scenario that the resolved path contains a rooted path that can't be |
| 951 | /// resolved using the paths alone, the result will be a rooted path. |
| 952 | /// |
| 953 | /// This function does not perform any syscalls. Executing this series of path lookups on an actual filesystem may |
| 954 | /// produce different results due to symlinks. |
| 890 | 955 | /// |
| 891 | | /// Note: all usage of this function should be audited due to the existence of symlinks. |
| 892 | | /// Without performing actual syscalls, resolving `..` could be incorrect. |
| 893 | | /// This API may break in the future: https://github.com/ziglang/zig/issues/13613 |
| 894 | | pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 { |
| 956 | /// See also: |
| 957 | /// * `resolveAppend` |
| 958 | /// * `resolveAllocWindows` |
| 959 | pub fn resolveAppendWindows(gpa: Allocator, al: *std.ArrayList(u8), paths: []const []const u8) Allocator.Error!void { |
| 895 | 960 | // Avoid heap allocation when paths.len is <= @bitSizeOf(usize) * 2 |
| 896 | 961 | // (we use `* 3` because stackFallback uses 1 usize as a length) |
| 897 | 962 | var buf: [3]usize = undefined; |
| 898 | | var bit_set_allocator_state: std.heap.BufferFirstAllocator = .init(@ptrCast(&buf), allocator); |
| 963 | var bit_set_allocator_state: std.heap.BufferFirstAllocator = .init(@ptrCast(&buf), gpa); |
| 899 | 964 | const bit_set_allocator = bit_set_allocator_state.allocator(); |
| 900 | 965 | var relevant_paths: std.bit_set.Dynamic = try .initEmpty(bit_set_allocator, paths.len); |
| 901 | 966 | defer relevant_paths.deinit(bit_set_allocator); |
| ... | ... | @@ -984,23 +1049,23 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator |
| 984 | 1049 | break :root last_effective_root_path; |
| 985 | 1050 | }; |
| 986 | 1051 | |
| 987 | | var result: std.ArrayList(u8) = .empty; |
| 988 | | defer result.deinit(allocator); |
| 1052 | const prev_len = al.items.len; |
| 1053 | errdefer al.shrinkRetainingCapacity(prev_len); |
| 989 | 1054 | |
| 990 | 1055 | var want_path_sep_between_root_and_component = false; |
| 991 | 1056 | switch (effective_root_path.kind) { |
| 992 | 1057 | .root_local_device, .local_device => { |
| 993 | | try result.ensureUnusedCapacity(allocator, 3); |
| 994 | | result.appendSliceAssumeCapacity("\\\\"); |
| 995 | | result.appendAssumeCapacity(effective_root_path.root[2]); // . or ? |
| 1058 | try al.ensureUnusedCapacity(gpa, 3); |
| 1059 | al.appendSliceAssumeCapacity("\\\\"); |
| 1060 | al.appendAssumeCapacity(effective_root_path.root[2]); // . or ? |
| 996 | 1061 | want_path_sep_between_root_and_component = true; |
| 997 | 1062 | }, |
| 998 | 1063 | .drive_absolute, .drive_relative => { |
| 999 | | try result.ensureUnusedCapacity(allocator, effective_root_path.root.len); |
| 1000 | | result.appendAssumeCapacity(std.ascii.toUpper(effective_root_path.root[0])); |
| 1001 | | result.appendAssumeCapacity(':'); |
| 1064 | try al.ensureUnusedCapacity(gpa, effective_root_path.root.len); |
| 1065 | al.appendAssumeCapacity(std.ascii.toUpper(effective_root_path.root[0])); |
| 1066 | al.appendAssumeCapacity(':'); |
| 1002 | 1067 | if (effective_root_path.kind == .drive_absolute) { |
| 1003 | | result.appendAssumeCapacity('\\'); |
| 1068 | al.appendAssumeCapacity('\\'); |
| 1004 | 1069 | } |
| 1005 | 1070 | }, |
| 1006 | 1071 | .unc_absolute => { |
| ... | ... | @@ -1012,30 +1077,30 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator |
| 1012 | 1077 | if (unc.sep_after_share) len += 1; |
| 1013 | 1078 | break :len len; |
| 1014 | 1079 | }; |
| 1015 | | try result.ensureUnusedCapacity(allocator, root_len); |
| 1016 | | result.appendSliceAssumeCapacity("\\\\"); |
| 1080 | try al.ensureUnusedCapacity(gpa, root_len); |
| 1081 | al.appendSliceAssumeCapacity("\\\\"); |
| 1017 | 1082 | if (unc.server.len > 0 or unc.sep_after_server) { |
| 1018 | | result.appendSliceAssumeCapacity(unc.server); |
| 1083 | al.appendSliceAssumeCapacity(unc.server); |
| 1019 | 1084 | if (unc.sep_after_server) |
| 1020 | | result.appendAssumeCapacity('\\') |
| 1085 | al.appendAssumeCapacity('\\') |
| 1021 | 1086 | else |
| 1022 | 1087 | want_path_sep_between_root_and_component = true; |
| 1023 | 1088 | } |
| 1024 | 1089 | if (unc.share.len > 0) { |
| 1025 | | result.appendSliceAssumeCapacity(unc.share); |
| 1090 | al.appendSliceAssumeCapacity(unc.share); |
| 1026 | 1091 | if (unc.sep_after_share) |
| 1027 | | result.appendAssumeCapacity('\\') |
| 1092 | al.appendAssumeCapacity('\\') |
| 1028 | 1093 | else |
| 1029 | 1094 | want_path_sep_between_root_and_component = true; |
| 1030 | 1095 | } |
| 1031 | 1096 | }, |
| 1032 | 1097 | .rooted => { |
| 1033 | | try result.append(allocator, '\\'); |
| 1098 | try al.append(gpa, '\\'); |
| 1034 | 1099 | }, |
| 1035 | 1100 | .relative => {}, |
| 1036 | 1101 | } |
| 1037 | 1102 | |
| 1038 | | const root_len = result.items.len; |
| 1103 | const root_len = al.items.len - prev_len; |
| 1039 | 1104 | var negative_count: usize = 0; |
| 1040 | 1105 | for (paths[first_path_i..], first_path_i..) |path, i| { |
| 1041 | 1106 | if (!relevant_paths.isSet(i)) continue; |
| ... | ... | @@ -1047,69 +1112,90 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator |
| 1047 | 1112 | if (mem.eql(u8, component, ".")) { |
| 1048 | 1113 | continue; |
| 1049 | 1114 | } else if (mem.eql(u8, component, "..")) { |
| 1050 | | if (result.items.len == 0 or (result.items.len == root_len and effective_root_path.kind == .drive_relative)) { |
| 1115 | if ((al.items.len - prev_len) == 0 or |
| 1116 | ((al.items.len - prev_len) == root_len and effective_root_path.kind == .drive_relative)) |
| 1117 | { |
| 1051 | 1118 | negative_count += 1; |
| 1052 | 1119 | continue; |
| 1053 | 1120 | } |
| 1054 | | while (true) { |
| 1055 | | if (result.items.len == root_len) { |
| 1056 | | break; |
| 1057 | | } |
| 1058 | | const end_with_sep = PathType.windows.isSep(u8, result.items[result.items.len - 1]); |
| 1059 | | result.items.len -= 1; |
| 1121 | while ((al.items.len - prev_len) != root_len) { |
| 1122 | const end_with_sep = PathType.windows.isSep(u8, al.items[al.items.len - 1]); |
| 1123 | al.items.len -= 1; |
| 1060 | 1124 | if (end_with_sep) break; |
| 1061 | 1125 | } |
| 1062 | | } else if (result.items.len == root_len and !want_path_sep_between_root_and_component) { |
| 1063 | | try result.appendSlice(allocator, component); |
| 1126 | } else if ((al.items.len - prev_len) == root_len and !want_path_sep_between_root_and_component) { |
| 1127 | try al.appendSlice(gpa, component); |
| 1064 | 1128 | } else { |
| 1065 | | try result.ensureUnusedCapacity(allocator, 1 + component.len); |
| 1066 | | result.appendAssumeCapacity('\\'); |
| 1067 | | result.appendSliceAssumeCapacity(component); |
| 1129 | try al.ensureUnusedCapacity(gpa, 1 + component.len); |
| 1130 | al.appendAssumeCapacity('\\'); |
| 1131 | al.appendSliceAssumeCapacity(component); |
| 1068 | 1132 | } |
| 1069 | 1133 | } |
| 1070 | 1134 | } |
| 1071 | 1135 | |
| 1072 | | if (root_len != 0 and result.items.len == root_len and negative_count == 0) { |
| 1073 | | return result.toOwnedSlice(allocator); |
| 1074 | | } |
| 1136 | if (root_len != 0 and (al.items.len - prev_len) == root_len and negative_count == 0) return; |
| 1075 | 1137 | |
| 1076 | | if (result.items.len == root_len) { |
| 1138 | if ((al.items.len - prev_len) == root_len) { |
| 1077 | 1139 | if (negative_count == 0) { |
| 1078 | | return allocator.dupe(u8, "."); |
| 1140 | try al.resize(gpa, prev_len + 1); |
| 1141 | al.items[al.items.len - 1] = '.'; |
| 1142 | return; |
| 1079 | 1143 | } |
| 1080 | 1144 | |
| 1081 | | try result.ensureTotalCapacityPrecise(allocator, 3 * negative_count - 1); |
| 1145 | try al.ensureTotalCapacityPrecise(gpa, prev_len + 3 * negative_count - 1); |
| 1082 | 1146 | for (0..negative_count - 1) |_| { |
| 1083 | | result.appendSliceAssumeCapacity("..\\"); |
| 1147 | al.appendSliceAssumeCapacity("..\\"); |
| 1084 | 1148 | } |
| 1085 | | result.appendSliceAssumeCapacity(".."); |
| 1149 | al.appendSliceAssumeCapacity(".."); |
| 1086 | 1150 | } else { |
| 1087 | | const dest = try result.addManyAt(allocator, root_len, 3 * negative_count); |
| 1151 | const dest = try al.addManyAt(gpa, prev_len + root_len, 3 * negative_count); |
| 1088 | 1152 | for (0..negative_count) |i| { |
| 1089 | 1153 | dest[i * 3 ..][0..3].* = "..\\".*; |
| 1090 | 1154 | } |
| 1091 | 1155 | } |
| 1092 | | |
| 1093 | | return result.toOwnedSlice(allocator); |
| 1094 | 1156 | } |
| 1095 | 1157 | |
| 1096 | | /// Simulates a series of relative directory changes on a virtual filesystem |
| 1097 | | /// that has no symlinks. |
| 1158 | /// Simulates a series of relative directory changes on a virtual filesystem that has no symlinks, returning the result |
| 1159 | /// as an allocated slice. |
| 1098 | 1160 | /// |
| 1099 | | /// "." and ".." are resolved but will not make relative paths absolute. ".." |
| 1100 | | /// components remain in the resolved path when the resolved path is relative |
| 1101 | | /// and there are not previous components to cancel out. |
| 1161 | /// "." and ".." are resolved but will not make relative paths absolute. ".." components remain in the resolved path |
| 1162 | /// when the resolved path is relative and there are not previous components to cancel out. |
| 1102 | 1163 | /// |
| 1103 | 1164 | /// The result does not have a trailing path separator. |
| 1104 | 1165 | /// |
| 1105 | | /// This function does not perform any syscalls. Executing this series of path |
| 1106 | | /// lookups on an actual filesystem may produce different results due to |
| 1107 | | /// symlinks. |
| 1108 | | pub fn resolvePosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![]u8 { |
| 1166 | /// This function does not perform any syscalls. Executing this series of path lookups on an actual filesystem may |
| 1167 | /// produce different results due to symlinks. |
| 1168 | /// |
| 1169 | /// See also: |
| 1170 | /// * `resolveAlloc` |
| 1171 | /// * `resolveAppendPosix` |
| 1172 | pub fn resolveAllocPosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![]u8 { |
| 1173 | var buffer: std.ArrayList(u8) = .empty; |
| 1174 | defer buffer.deinit(gpa); |
| 1175 | try resolveAppendPosix(gpa, &buffer, paths); |
| 1176 | try buffer.shrinkToLen(gpa); |
| 1177 | return buffer.toOwnedSliceAssert(); |
| 1178 | } |
| 1179 | |
| 1180 | /// Simulates a series of relative directory changes on a virtual filesystem that has no symlinks, appending the result |
| 1181 | /// to the provided array list. |
| 1182 | /// |
| 1183 | /// "." and ".." are resolved but will not make relative paths absolute. ".." components remain in the resolved path |
| 1184 | /// when the resolved path is relative and there are not previous components to cancel out. |
| 1185 | /// |
| 1186 | /// The result does not have a trailing path separator. |
| 1187 | /// |
| 1188 | /// This function does not perform any syscalls. Executing this series of path lookups on an actual filesystem may |
| 1189 | /// produce different results due to symlinks. |
| 1190 | /// |
| 1191 | /// See also: |
| 1192 | /// * `resolveAppend` |
| 1193 | /// * `resolveAllocPosix` |
| 1194 | pub fn resolveAppendPosix(gpa: Allocator, al: *std.ArrayList(u8), paths: []const []const u8) Allocator.Error!void { |
| 1109 | 1195 | assert(paths.len > 0); |
| 1110 | 1196 | |
| 1111 | | var result: std.ArrayList(u8) = .empty; |
| 1112 | | defer result.deinit(gpa); |
| 1197 | const prev_len = al.items.len; |
| 1198 | errdefer al.shrinkRetainingCapacity(prev_len); |
| 1113 | 1199 | |
| 1114 | 1200 | var negative_count: usize = 0; |
| 1115 | 1201 | var is_abs = false; |
| ... | ... | @@ -1118,63 +1204,52 @@ pub fn resolvePosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![ |
| 1118 | 1204 | if (isAbsolutePosix(p)) { |
| 1119 | 1205 | is_abs = true; |
| 1120 | 1206 | negative_count = 0; |
| 1121 | | result.clearRetainingCapacity(); |
| 1207 | al.shrinkRetainingCapacity(prev_len); |
| 1122 | 1208 | } |
| 1123 | 1209 | var it = mem.tokenizeScalar(u8, p, '/'); |
| 1124 | 1210 | while (it.next()) |component| { |
| 1125 | 1211 | if (mem.eql(u8, component, ".")) { |
| 1126 | 1212 | continue; |
| 1127 | 1213 | } else if (mem.eql(u8, component, "..")) { |
| 1128 | | if (result.items.len == 0) { |
| 1214 | if ((al.items.len - prev_len) == 0) { |
| 1129 | 1215 | negative_count += @intFromBool(!is_abs); |
| 1130 | 1216 | continue; |
| 1131 | 1217 | } |
| 1132 | 1218 | while (true) { |
| 1133 | | const ends_with_slash = result.items[result.items.len - 1] == '/'; |
| 1134 | | result.items.len -= 1; |
| 1135 | | if (ends_with_slash or result.items.len == 0) break; |
| 1219 | const ends_with_slash = al.items[al.items.len - 1] == '/'; |
| 1220 | al.items.len -= 1; |
| 1221 | if (ends_with_slash or (al.items.len - prev_len) == 0) break; |
| 1136 | 1222 | } |
| 1137 | | } else if (result.items.len > 0 or is_abs) { |
| 1138 | | try result.ensureUnusedCapacity(gpa, 1 + component.len); |
| 1139 | | result.appendAssumeCapacity('/'); |
| 1140 | | result.appendSliceAssumeCapacity(component); |
| 1223 | } else if ((al.items.len - prev_len) > 0 or is_abs) { |
| 1224 | try al.ensureUnusedCapacity(gpa, 1 + component.len); |
| 1225 | al.appendAssumeCapacity('/'); |
| 1226 | al.appendSliceAssumeCapacity(component); |
| 1141 | 1227 | } else { |
| 1142 | | try result.appendSlice(gpa, component); |
| 1228 | try al.appendSlice(gpa, component); |
| 1143 | 1229 | } |
| 1144 | 1230 | } |
| 1145 | 1231 | } |
| 1146 | 1232 | |
| 1147 | | if (result.items.len == 0) { |
| 1233 | if ((al.items.len - prev_len) == 0) { |
| 1148 | 1234 | if (is_abs) { |
| 1149 | | return gpa.dupe(u8, "/"); |
| 1150 | | } |
| 1151 | | if (negative_count == 0) { |
| 1152 | | return gpa.dupe(u8, "."); |
| 1235 | try al.resize(gpa, prev_len + 1); |
| 1236 | al.items[al.items.len - 1] = '/'; |
| 1237 | } else if (negative_count == 0) { |
| 1238 | try al.resize(gpa, prev_len + 1); |
| 1239 | al.items[al.items.len - 1] = '.'; |
| 1153 | 1240 | } else { |
| 1154 | | const real_result = try gpa.alloc(u8, 3 * negative_count - 1); |
| 1241 | try al.resize(gpa, prev_len + 3 * negative_count - 1); |
| 1155 | 1242 | var count = negative_count - 1; |
| 1156 | | var i: usize = 0; |
| 1243 | var i: usize = prev_len; |
| 1157 | 1244 | while (count > 0) : (count -= 1) { |
| 1158 | | real_result[i..][0..3].* = "../".*; |
| 1245 | al.items[i..][0..3].* = "../".*; |
| 1159 | 1246 | i += 3; |
| 1160 | 1247 | } |
| 1161 | | real_result[i..][0..2].* = "..".*; |
| 1162 | | return real_result; |
| 1248 | al.items[i..][0..2].* = "..".*; |
| 1163 | 1249 | } |
| 1164 | | } |
| 1165 | | |
| 1166 | | if (negative_count == 0) { |
| 1167 | | return result.toOwnedSlice(gpa); |
| 1168 | | } else { |
| 1169 | | const real_result = try gpa.alloc(u8, 3 * negative_count + result.items.len); |
| 1170 | | var count = negative_count; |
| 1171 | | var i: usize = 0; |
| 1172 | | while (count > 0) : (count -= 1) { |
| 1173 | | real_result[i..][0..3].* = "../".*; |
| 1174 | | i += 3; |
| 1175 | | } |
| 1176 | | @memcpy(real_result[i..][0..result.items.len], result.items); |
| 1177 | | return real_result; |
| 1250 | } else if (negative_count != 0) { |
| 1251 | const dest = try al.addManyAt(gpa, prev_len, 3 * negative_count); |
| 1252 | for (0..negative_count) |i| dest[i * 3 ..][0..3].* = "../".*; |
| 1178 | 1253 | } |
| 1179 | 1254 | } |
| 1180 | 1255 | |
| ... | ... | @@ -1192,7 +1267,7 @@ test resolve { |
| 1192 | 1267 | try testResolvePosix(&[_][]const u8{""}, "."); |
| 1193 | 1268 | } |
| 1194 | 1269 | |
| 1195 | | test resolveWindows { |
| 1270 | test resolveAllocWindows { |
| 1196 | 1271 | try testResolveWindows( |
| 1197 | 1272 | &[_][]const u8{ "Z:\\", "/usr/local", "lib\\zig\\std\\array_list.zig" }, |
| 1198 | 1273 | "Z:\\usr\\local\\lib\\zig\\std\\array_list.zig", |
| ... | ... | @@ -1280,7 +1355,7 @@ test resolveWindows { |
| 1280 | 1355 | try testResolveWindows(&[_][]const u8{ "C:\\", "\\??\\C:\\foo", "bar" }, "C:\\??\\C:\\foo\\bar"); |
| 1281 | 1356 | } |
| 1282 | 1357 | |
| 1283 | | test resolvePosix { |
| 1358 | test resolveAllocPosix { |
| 1284 | 1359 | try testResolvePosix(&.{ "/a/b", "c" }, "/a/b/c"); |
| 1285 | 1360 | try testResolvePosix(&.{ "/a/b", "c", "//d", "e///" }, "/d/e"); |
| 1286 | 1361 | try testResolvePosix(&.{ "/a/b/c", "..", "../" }, "/a"); |
| ... | ... | @@ -1299,15 +1374,45 @@ test resolvePosix { |
| 1299 | 1374 | } |
| 1300 | 1375 | |
| 1301 | 1376 | fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void { |
| 1302 | | const actual = try resolveWindows(testing.allocator, paths); |
| 1303 | | defer testing.allocator.free(actual); |
| 1304 | | try testing.expectEqualStrings(expected, actual); |
| 1377 | { |
| 1378 | const actual = try resolveAllocWindows(testing.allocator, paths); |
| 1379 | defer testing.allocator.free(actual); |
| 1380 | try testing.expectEqualStrings(expected, actual); |
| 1381 | } |
| 1382 | |
| 1383 | { |
| 1384 | var buffer: std.ArrayList(u8) = .empty; |
| 1385 | defer buffer.deinit(testing.allocator); |
| 1386 | try buffer.append(testing.allocator, 99); |
| 1387 | |
| 1388 | try resolveAppendWindows(testing.allocator, &buffer, paths); |
| 1389 | // Test that it preserved the previous contents of the array list. |
| 1390 | try testing.expectEqual(expected.len + 1, buffer.items.len); |
| 1391 | try testing.expectEqual(99, buffer.items[0]); |
| 1392 | const result = buffer.items[1..]; |
| 1393 | try testing.expectEqualStrings(expected, result); |
| 1394 | } |
| 1305 | 1395 | } |
| 1306 | 1396 | |
| 1307 | 1397 | fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { |
| 1308 | | const actual = try resolvePosix(testing.allocator, paths); |
| 1309 | | defer testing.allocator.free(actual); |
| 1310 | | try testing.expectEqualStrings(expected, actual); |
| 1398 | { |
| 1399 | const actual = try resolveAllocPosix(testing.allocator, paths); |
| 1400 | defer testing.allocator.free(actual); |
| 1401 | try testing.expectEqualStrings(expected, actual); |
| 1402 | } |
| 1403 | |
| 1404 | { |
| 1405 | var buffer: std.ArrayList(u8) = .empty; |
| 1406 | defer buffer.deinit(testing.allocator); |
| 1407 | try buffer.append(testing.allocator, 99); |
| 1408 | |
| 1409 | try resolveAppendPosix(testing.allocator, &buffer, paths); |
| 1410 | // Test that it preserved the previous contents of the array list. |
| 1411 | try testing.expectEqual(expected.len + 1, buffer.items.len); |
| 1412 | try testing.expectEqual(99, buffer.items[0]); |
| 1413 | const result = buffer.items[1..]; |
| 1414 | try testing.expectEqualStrings(expected, result); |
| 1415 | } |
| 1311 | 1416 | } |
| 1312 | 1417 | |
| 1313 | 1418 | /// Strip the last component from a file path. |
| ... | ... | @@ -1498,17 +1603,18 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void { |
| 1498 | 1603 | try testing.expectEqualSlices(u8, expected_output, basenameWindows(input)); |
| 1499 | 1604 | } |
| 1500 | 1605 | |
| 1501 | | /// Returns the non-absolute path from `from` to `to`. |
| 1606 | /// Returns the non-absolute path from `from` to `to` as an allocated slice. |
| 1607 | /// |
| 1608 | /// Other than memory allocation, this is a pure function; the result solely depends on the input parameters. |
| 1502 | 1609 | /// |
| 1503 | | /// Other than memory allocation, this is a pure function; the result solely |
| 1504 | | /// depends on the input parameters. |
| 1610 | /// If `from` and `to` each resolve to the same path (after calling `resolve` on each), a zero-length string is |
| 1611 | /// returned. |
| 1505 | 1612 | /// |
| 1506 | | /// If `from` and `to` each resolve to the same path (after calling `resolve` |
| 1507 | | /// on each), a zero-length string is returned. |
| 1613 | /// See `relativePosix` and `relativeWindows` for operating system specific details and for how `environ_map` is used. |
| 1508 | 1614 | /// |
| 1509 | | /// See `relativePosix` and `relativeWindows` for operating system specific |
| 1510 | | /// details and for how `environ_map` is used. |
| 1511 | | pub fn relative( |
| 1615 | /// See also: |
| 1616 | /// * `relativeAppend` |
| 1617 | pub fn relativeAlloc( |
| 1512 | 1618 | gpa: Allocator, |
| 1513 | 1619 | cwd: []const u8, |
| 1514 | 1620 | environ_map: ?*const std.process.Environ.Map, |
| ... | ... | @@ -1516,30 +1622,59 @@ pub fn relative( |
| 1516 | 1622 | to: []const u8, |
| 1517 | 1623 | ) Allocator.Error![]u8 { |
| 1518 | 1624 | if (native_os == .windows) { |
| 1519 | | return relativeWindows(gpa, cwd, environ_map, from, to); |
| 1625 | return relativeAllocWindows(gpa, cwd, environ_map, from, to); |
| 1626 | } else { |
| 1627 | return relativeAllocPosix(gpa, cwd, from, to); |
| 1628 | } |
| 1629 | } |
| 1630 | |
| 1631 | /// Appends the non-absolute path from `from` to `to` to the provided array list. |
| 1632 | /// |
| 1633 | /// Other than modifying the provided array list, this is a pure function; the result solely depends on the input |
| 1634 | /// parameters and no file system operations are performed. |
| 1635 | /// |
| 1636 | /// If `from` and `to` each resolve to the same path (after calling `resolveAppend` on each), a zero-length string is |
| 1637 | /// returned. |
| 1638 | /// |
| 1639 | /// See `relativeAppendPosix` and `relativeAppendWindows` for operating system specific details and for how |
| 1640 | /// `environ_map` is used. |
| 1641 | /// |
| 1642 | /// See also: |
| 1643 | /// * `relativeAlloc` |
| 1644 | pub fn relativeAppend( |
| 1645 | gpa: Allocator, |
| 1646 | al: *std.ArrayList(u8), |
| 1647 | cwd: []const u8, |
| 1648 | environ_map: ?*const std.process.Environ.Map, |
| 1649 | from: []const u8, |
| 1650 | to: []const u8, |
| 1651 | ) Allocator.Error!void { |
| 1652 | if (native_os == .windows) { |
| 1653 | return relativeAppendWindows(gpa, al, cwd, environ_map, from, to); |
| 1520 | 1654 | } else { |
| 1521 | | return relativePosix(gpa, cwd, from, to); |
| 1655 | return relativeAppendPosix(gpa, al, cwd, from, to); |
| 1522 | 1656 | } |
| 1523 | 1657 | } |
| 1524 | 1658 | |
| 1525 | | /// Returns the non-absolute path from `from` to `to` according to Windows rules. |
| 1659 | /// Returns the non-absolute path from `from` to `to` according to Windows rules as an allocated slice. |
| 1526 | 1660 | /// |
| 1527 | | /// Other than memory allocation, this is a pure function; the result solely |
| 1528 | | /// depends on the input parameters. |
| 1661 | /// Other than memory allocation, this is a pure function; the result solely depends on the input parameters. |
| 1529 | 1662 | /// |
| 1530 | | /// If `from` and `to` each resolve to the same path (after calling `resolve` |
| 1531 | | /// on each), a zero-length string is returned. |
| 1663 | /// If `from` and `to` each resolve to the same path (after calling `resolve` on each), a zero-length string is |
| 1664 | /// returned. |
| 1532 | 1665 | /// |
| 1533 | | /// The result is not guaranteed to be relative, as the paths may be on |
| 1534 | | /// different volumes. In that case, the result will be the canonicalized |
| 1535 | | /// absolute path of `to`. |
| 1666 | /// The result is not guaranteed to be relative, as the paths may be on different volumes. In that case, the result will |
| 1667 | /// be the canonicalized absolute path of `to`. |
| 1536 | 1668 | /// |
| 1537 | | /// Per-drive CWDs are stored in special semi-hidden environment variables of |
| 1538 | | /// the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is purely a |
| 1539 | | /// shell concept, so there's no guarantee that it'll be set or that it'll even |
| 1540 | | /// be accurate. This is the only reason for the `environ_map` parameter. `null` is |
| 1541 | | /// treated equivalent to the environment variable missing. |
| 1542 | | pub fn relativeWindows( |
| 1669 | /// Per-drive CWDs are stored in special semi-hidden environment variables of the format `=<drive-letter>:`, e.g. `=C:`. |
| 1670 | /// 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 |
| 1671 | /// accurate. This is the only reason for the `environ_map` parameter. `null` is treated equivalent to the environment |
| 1672 | /// variable missing. |
| 1673 | /// |
| 1674 | /// See also: |
| 1675 | /// `relativeAlloc` |
| 1676 | /// `relativeAppendWindows` |
| 1677 | pub fn relativeAllocWindows( |
| 1543 | 1678 | gpa: Allocator, |
| 1544 | 1679 | cwd: []const u8, |
| 1545 | 1680 | environ_map: ?*const std.process.Environ.Map, |
| ... | ... | @@ -1635,6 +1770,37 @@ pub fn relativeWindows( |
| 1635 | 1770 | return [_]u8{}; |
| 1636 | 1771 | } |
| 1637 | 1772 | |
| 1773 | /// Appends the non-absolute path from `from` to `to` according to Windows rules to the provided array list. |
| 1774 | /// |
| 1775 | /// Other than memory allocation, this is a pure function; the result solely depends on the input parameters. |
| 1776 | /// |
| 1777 | /// If `from` and `to` each resolve to the same path (after calling `resolve` on each), a zero-length string is |
| 1778 | /// returned. |
| 1779 | /// |
| 1780 | /// The result is not guaranteed to be relative, as the paths may be on different volumes. In that case, the result will |
| 1781 | /// be the canonicalized absolute path of `to`. |
| 1782 | /// |
| 1783 | /// Per-drive CWDs are stored in special semi-hidden environment variables of the format `=<drive-letter>:`, e.g. `=C:`. |
| 1784 | /// 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 |
| 1785 | /// accurate. This is the only reason for the `environ_map` parameter. `null` is treated equivalent to the environment |
| 1786 | /// variable missing. |
| 1787 | /// |
| 1788 | /// See also: |
| 1789 | /// `relativeAppend` |
| 1790 | /// `relativeAllocWindows` |
| 1791 | pub fn relativeAppendWindows( |
| 1792 | gpa: Allocator, |
| 1793 | al: *std.ArrayList(u8), |
| 1794 | cwd: []const u8, |
| 1795 | environ_map: ?*const std.process.Environ.Map, |
| 1796 | from: []const u8, |
| 1797 | to: []const u8, |
| 1798 | ) Allocator.Error!void { |
| 1799 | const result = try relativeAllocWindows(gpa, cwd, environ_map, from, to); |
| 1800 | defer gpa.free(result); |
| 1801 | try al.appendSlice(gpa, result); |
| 1802 | } |
| 1803 | |
| 1638 | 1804 | fn windowsResolveAgainstCwd( |
| 1639 | 1805 | gpa: Allocator, |
| 1640 | 1806 | cwd: []const u8, |
| ... | ... | @@ -1650,9 +1816,9 @@ fn windowsResolveAgainstCwd( |
| 1650 | 1816 | .unc_absolute, |
| 1651 | 1817 | .root_local_device, |
| 1652 | 1818 | .local_device, |
| 1653 | | => try resolveWindows(gpa, &.{path}), |
| 1819 | => try resolveAllocWindows(gpa, &.{path}), |
| 1654 | 1820 | |
| 1655 | | .relative => try resolveWindows(gpa, &.{ cwd, path }), |
| 1821 | .relative => try resolveAllocWindows(gpa, &.{ cwd, path }), |
| 1656 | 1822 | |
| 1657 | 1823 | .rooted => blk: { |
| 1658 | 1824 | const parsed_cwd = parsePathWindows(u8, cwd); |
| ... | ... | @@ -1660,13 +1826,13 @@ fn windowsResolveAgainstCwd( |
| 1660 | 1826 | .drive_absolute => { |
| 1661 | 1827 | var drive_buf = "_:\\".*; |
| 1662 | 1828 | drive_buf[0] = cwd[0]; |
| 1663 | | break :blk try resolveWindows(gpa, &.{ &drive_buf, path }); |
| 1829 | break :blk try resolveAllocWindows(gpa, &.{ &drive_buf, path }); |
| 1664 | 1830 | }, |
| 1665 | 1831 | .unc_absolute => { |
| 1666 | | break :blk try resolveWindows(gpa, &.{ parsed_cwd.root, path }); |
| 1832 | break :blk try resolveAllocWindows(gpa, &.{ parsed_cwd.root, path }); |
| 1667 | 1833 | }, |
| 1668 | 1834 | // Effectively a malformed CWD, give up and just return a normalized path |
| 1669 | | else => break :blk try resolveWindows(gpa, &.{path}), |
| 1835 | else => break :blk try resolveAllocWindows(gpa, &.{path}), |
| 1670 | 1836 | } |
| 1671 | 1837 | }, |
| 1672 | 1838 | .drive_relative => blk: { |
| ... | ... | @@ -1695,41 +1861,76 @@ fn windowsResolveAgainstCwd( |
| 1695 | 1861 | break :drive_cwd drive_buf; |
| 1696 | 1862 | }; |
| 1697 | 1863 | defer temp_allocator.free(drive_cwd); |
| 1698 | | break :blk try resolveWindows(gpa, &.{ drive_cwd, path }); |
| 1864 | break :blk try resolveAllocWindows(gpa, &.{ drive_cwd, path }); |
| 1699 | 1865 | }, |
| 1700 | 1866 | }; |
| 1701 | 1867 | } |
| 1702 | 1868 | |
| 1703 | | /// Returns the non-absolute path from `from` to `to` according to Windows rules. |
| 1869 | /// Returns the non-absolute path from `from` to `to` according to POSIX rules as an allocated slice. |
| 1870 | /// |
| 1871 | /// Other than memory allocation, this is a pure function; the result solely depends on the input parameters. |
| 1872 | /// |
| 1873 | /// If `from` and `to` each resolve to the same path (after calling `resolve` on each), a zero-length string is |
| 1874 | /// returned. |
| 1875 | /// |
| 1876 | /// See also: |
| 1877 | /// * `relativeAlloc` |
| 1878 | /// * `relativeAppendPosix` |
| 1879 | pub fn relativeAllocPosix(gpa: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 { |
| 1880 | var buffer: std.ArrayList(u8) = .empty; |
| 1881 | defer buffer.deinit(gpa); |
| 1882 | try relativeAppendPosix(gpa, &buffer, cwd, from, to); |
| 1883 | try buffer.shrinkToLen(gpa); |
| 1884 | return buffer.toOwnedSliceAssert(); |
| 1885 | } |
| 1886 | |
| 1887 | /// Appends the non-absolute path from `from` to `to` according to POSIX rules to the provided array list. |
| 1704 | 1888 | /// |
| 1705 | | /// Other than memory allocation, this is a pure function; the result solely |
| 1706 | | /// depends on the input parameters. |
| 1889 | /// Other than modifying the provided array list, this is a pure function; the result solely depends on the input |
| 1890 | /// parameters and it does no file system operations. |
| 1707 | 1891 | /// |
| 1708 | | /// If `from` and `to` each resolve to the same path (after calling `resolve` |
| 1709 | | /// on each), a zero-length string is returned. |
| 1892 | /// If `from` and `to` each resolve to the same path (after calling `resolveAppend` on each), nothing is appended to the |
| 1893 | /// array list. |
| 1710 | 1894 | /// |
| 1711 | | pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 { |
| 1712 | | const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from }); |
| 1713 | | defer allocator.free(resolved_from); |
| 1714 | | const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to }); |
| 1715 | | defer allocator.free(resolved_to); |
| 1716 | | |
| 1717 | | var from_it = mem.tokenizeScalar(u8, resolved_from, '/'); |
| 1718 | | var to_it = mem.tokenizeScalar(u8, resolved_to, '/'); |
| 1895 | /// See also: |
| 1896 | /// * `relativeAppend` |
| 1897 | /// * `relativeAllocPosix` |
| 1898 | pub fn relativeAppendPosix( |
| 1899 | gpa: Allocator, |
| 1900 | al: *std.ArrayList(u8), |
| 1901 | cwd: []const u8, |
| 1902 | from: []const u8, |
| 1903 | to: []const u8, |
| 1904 | ) Allocator.Error!void { |
| 1905 | const orig_len = al.items.len; |
| 1906 | errdefer al.items.len = orig_len; |
| 1907 | |
| 1908 | const resolved_from_start = al.items.len; |
| 1909 | try resolveAppendPosix(gpa, al, &.{ cwd, from }); |
| 1910 | const resolved_from_end = al.items.len; |
| 1911 | |
| 1912 | const resolved_to_start = al.items.len; |
| 1913 | try resolveAppendPosix(gpa, al, &.{ cwd, to }); |
| 1914 | const resolved_to_end = al.items.len; |
| 1915 | |
| 1916 | var from_it = mem.tokenizeScalar(u8, al.items[resolved_from_start..resolved_from_end], '/'); |
| 1917 | var to_it = mem.tokenizeScalar(u8, al.items[resolved_to_start..resolved_to_end], '/'); |
| 1719 | 1918 | while (true) { |
| 1720 | | const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); |
| 1919 | const from_component = from_it.next() orelse { |
| 1920 | const result = to_it.rest(); |
| 1921 | @memmove(al.items[orig_len..][0..result.len], result); |
| 1922 | al.shrinkRetainingCapacity(orig_len + result.len); |
| 1923 | return; |
| 1924 | }; |
| 1721 | 1925 | const to_rest = to_it.rest(); |
| 1722 | 1926 | if (to_it.next()) |to_component| { |
| 1723 | 1927 | if (mem.eql(u8, from_component, to_component)) |
| 1724 | 1928 | continue; |
| 1725 | 1929 | } |
| 1726 | 1930 | var up_count: usize = 1; |
| 1727 | | while (from_it.next()) |_| { |
| 1728 | | up_count += 1; |
| 1729 | | } |
| 1931 | while (from_it.next()) |_| up_count += 1; |
| 1730 | 1932 | const up_index_end = up_count * "../".len; |
| 1731 | | const result = try allocator.alloc(u8, up_index_end + to_rest.len); |
| 1732 | | errdefer allocator.free(result); |
| 1933 | const result = al.items[orig_len..]; |
| 1733 | 1934 | |
| 1734 | 1935 | var result_index: usize = 0; |
| 1735 | 1936 | while (result_index < up_index_end) { |
| ... | ... | @@ -1737,15 +1938,16 @@ pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to |
| 1737 | 1938 | result_index += 3; |
| 1738 | 1939 | } |
| 1739 | 1940 | if (to_rest.len == 0) { |
| 1740 | | // shave off the trailing slash |
| 1741 | | return allocator.realloc(result, result_index - 1); |
| 1941 | // Shave off the trailing slash. |
| 1942 | al.shrinkRetainingCapacity(orig_len + result_index - 1); |
| 1943 | return; |
| 1742 | 1944 | } |
| 1743 | | |
| 1744 | | @memcpy(result[result_index..][0..to_rest.len], to_rest); |
| 1745 | | return result; |
| 1945 | @memmove(result[result_index..][0..to_rest.len], to_rest); |
| 1946 | al.shrinkRetainingCapacity(orig_len + result_index + to_rest.len); |
| 1947 | return; |
| 1746 | 1948 | } |
| 1747 | 1949 | |
| 1748 | | return [_]u8{}; |
| 1950 | al.shrinkRetainingCapacity(orig_len); |
| 1749 | 1951 | } |
| 1750 | 1952 | |
| 1751 | 1953 | test relative { |
| ... | ... | @@ -1803,15 +2005,44 @@ test relative { |
| 1803 | 2005 | } |
| 1804 | 2006 | |
| 1805 | 2007 | fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void { |
| 1806 | | const result = try relativePosix(testing.allocator, ".", from, to); |
| 1807 | | defer testing.allocator.free(result); |
| 1808 | | try testing.expectEqualStrings(expected_output, result); |
| 2008 | { |
| 2009 | const result = try relativePosix(testing.allocator, ".", from, to); |
| 2010 | defer testing.allocator.free(result); |
| 2011 | try testing.expectEqualStrings(expected_output, result); |
| 2012 | } |
| 2013 | |
| 2014 | { |
| 2015 | var buffer: std.ArrayList(u8) = .empty; |
| 2016 | defer buffer.deinit(testing.allocator); |
| 2017 | try buffer.append(testing.allocator, 99); |
| 2018 | |
| 2019 | try relativeAppendPosix(testing.allocator, &buffer, ".", from, to); |
| 2020 | // Test that it preserved the previous contents of the array list. |
| 2021 | try testing.expectEqual(expected_output.len + 1, buffer.items.len); |
| 2022 | try testing.expectEqual(99, buffer.items[0]); |
| 2023 | const result = buffer.items[1..]; |
| 2024 | try testing.expectEqualStrings(expected_output, result); |
| 2025 | } |
| 1809 | 2026 | } |
| 1810 | 2027 | |
| 1811 | 2028 | fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void { |
| 1812 | | const result = try relativeWindows(testing.allocator, ".", null, from, to); |
| 1813 | | defer testing.allocator.free(result); |
| 1814 | | try testing.expectEqualStrings(expected_output, result); |
| 2029 | { |
| 2030 | const result = try relativeAllocWindows(testing.allocator, ".", null, from, to); |
| 2031 | defer testing.allocator.free(result); |
| 2032 | try testing.expectEqualStrings(expected_output, result); |
| 2033 | } |
| 2034 | { |
| 2035 | var buffer: std.ArrayList(u8) = .empty; |
| 2036 | defer buffer.deinit(testing.allocator); |
| 2037 | try buffer.append(testing.allocator, 99); |
| 2038 | |
| 2039 | try relativeAppendWindows(testing.allocator, &buffer, ".", null, from, to); |
| 2040 | // Test that it preserved the previous contents of the array list. |
| 2041 | try testing.expectEqual(expected_output.len + 1, buffer.items.len); |
| 2042 | try testing.expectEqual(99, buffer.items[0]); |
| 2043 | const result = buffer.items[1..]; |
| 2044 | try testing.expectEqualStrings(expected_output, result); |
| 2045 | } |
| 1815 | 2046 | } |
| 1816 | 2047 | |
| 1817 | 2048 | /// Searches for a file extension separated by a `.` and returns the string after that `.`. |