| ... | @@ -860,21 +860,84 @@ fn testCompareDiskDesignators(expected_result: bool, kind: DiskDesignatorKind, p | ... | @@ -860,21 +860,84 @@ fn testCompareDiskDesignators(expected_result: bool, kind: DiskDesignatorKind, p |
| 860 | try std.testing.expectEqual(expected_result, compareDiskDesignators(u16, kind, wtf16_buf1[0..w1_len], wtf16_buf2[0..w2_len])); | 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`. | 863 | /// Deprecated in favor of `resolveAlloc`. |
| 864 | pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 { | 864 | pub const resolve = resolveAlloc; |
| 865 | if (native_os == .windows) { | 865 | /// Deprecated in favor of `resolveAllocWindows`. |
| 866 | return resolveWindows(allocator, paths); | 866 | pub const resolveWindows = resolveAllocWindows; |
| 867 | } else { | 867 | /// Deprecated in favor of `resolveAllocPosix`. |
| 868 | return resolvePosix(allocator, paths); | 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. | 899 | /// Simulates a series of relative directory changes on a virtual filesystem that has no symlinks, returning the result |
| 873 | /// It resolves "." and ".." to the best of its ability, but will not convert relative paths to | 900 | /// as an allocated slice. |
| 874 | /// an absolute path, use Io.Dir.realpath instead. | 901 | /// |
| 875 | /// ".." components may persist in the resolved path if the resolved path is relative or drive-relative. | 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 | /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. | 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 | /// The result will not have a trailing path separator, except for the following scenarios: | 941 | /// The result will not have a trailing path separator, except for the following scenarios: |
| 879 | /// - The resolved path is drive-absolute with no components (e.g. `C:\`). | 942 | /// - The resolved path is drive-absolute with no components (e.g. `C:\`). |
| 880 | /// - The resolved path is a UNC path with only a server name, and the input path contained a trailing separator | 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,20 +945,22 @@ pub fn resolve(allocator: Allocator, paths: []const []const u8) Allocator.Error! |
| 882 | /// - The resolved path is a UNC path with no components after the share name, and the input path contained a | 945 | /// - The resolved path is a UNC path with no components after the share name, and the input path contained a |
| 883 | /// trailing separator (e.g. `\\server\share\`). | 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. | 948 | /// Each drive has its own current working directory, which is only resolved via the paths provided. In the scenario |
| 886 | /// In the scenario that the resolved path contains a drive-relative path that can't be resolved using the paths alone, | 949 | /// that the resolved path contains a drive-relative path that can't be resolved using the paths alone, the result will |
| 887 | /// the result will be a drive-relative path. | 950 | /// be a drive-relative path. Similarly, in the scenario that the resolved path contains a rooted path that can't be |
| 888 | /// Similarly, in the scenario that the resolved path contains a rooted path that can't be resolved using the paths alone, | 951 | /// resolved using the paths alone, the result will be a rooted path. |
| 889 | /// 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. | 956 | /// See also: |
| 892 | /// Without performing actual syscalls, resolving `..` could be incorrect. | 957 | /// * `resolveAppend` |
| 893 | /// This API may break in the future: https://github.com/ziglang/zig/issues/13613 | 958 | /// * `resolveAllocWindows` |
| 894 | pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 { | 959 | pub fn resolveAppendWindows(gpa: Allocator, al: *std.ArrayList(u8), paths: []const []const u8) Allocator.Error!void { |
| 895 | // Avoid heap allocation when paths.len is <= @bitSizeOf(usize) * 2 | 960 | // Avoid heap allocation when paths.len is <= @bitSizeOf(usize) * 2 |
| 896 | // (we use `* 3` because stackFallback uses 1 usize as a length) | 961 | // (we use `* 3` because stackFallback uses 1 usize as a length) |
| 897 | var buf: [3]usize = undefined; | 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 | const bit_set_allocator = bit_set_allocator_state.allocator(); | 964 | const bit_set_allocator = bit_set_allocator_state.allocator(); |
| 900 | var relevant_paths: std.bit_set.Dynamic = try .initEmpty(bit_set_allocator, paths.len); | 965 | var relevant_paths: std.bit_set.Dynamic = try .initEmpty(bit_set_allocator, paths.len); |
| 901 | defer relevant_paths.deinit(bit_set_allocator); | 966 | defer relevant_paths.deinit(bit_set_allocator); |
| ... | @@ -984,23 +1049,23 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator | ... | @@ -984,23 +1049,23 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator |
| 984 | break :root last_effective_root_path; | 1049 | break :root last_effective_root_path; |
| 985 | }; | 1050 | }; |
| 986 | | 1051 | |
| 987 | var result: std.ArrayList(u8) = .empty; | 1052 | const prev_len = al.items.len; |
| 988 | defer result.deinit(allocator); | 1053 | errdefer al.shrinkRetainingCapacity(prev_len); |
| 989 | | 1054 | |
| 990 | var want_path_sep_between_root_and_component = false; | 1055 | var want_path_sep_between_root_and_component = false; |
| 991 | switch (effective_root_path.kind) { | 1056 | switch (effective_root_path.kind) { |
| 992 | .root_local_device, .local_device => { | 1057 | .root_local_device, .local_device => { |
| 993 | try result.ensureUnusedCapacity(allocator, 3); | 1058 | try al.ensureUnusedCapacity(gpa, 3); |
| 994 | result.appendSliceAssumeCapacity("\\\\"); | 1059 | al.appendSliceAssumeCapacity("\\\\"); |
| 995 | result.appendAssumeCapacity(effective_root_path.root[2]); // . or ? | 1060 | al.appendAssumeCapacity(effective_root_path.root[2]); // . or ? |
| 996 | want_path_sep_between_root_and_component = true; | 1061 | want_path_sep_between_root_and_component = true; |
| 997 | }, | 1062 | }, |
| 998 | .drive_absolute, .drive_relative => { | 1063 | .drive_absolute, .drive_relative => { |
| 999 | try result.ensureUnusedCapacity(allocator, effective_root_path.root.len); | 1064 | try al.ensureUnusedCapacity(gpa, effective_root_path.root.len); |
| 1000 | result.appendAssumeCapacity(std.ascii.toUpper(effective_root_path.root[0])); | 1065 | al.appendAssumeCapacity(std.ascii.toUpper(effective_root_path.root[0])); |
| 1001 | result.appendAssumeCapacity(':'); | 1066 | al.appendAssumeCapacity(':'); |
| 1002 | if (effective_root_path.kind == .drive_absolute) { | 1067 | if (effective_root_path.kind == .drive_absolute) { |
| 1003 | result.appendAssumeCapacity('\\'); | 1068 | al.appendAssumeCapacity('\\'); |
| 1004 | } | 1069 | } |
| 1005 | }, | 1070 | }, |
| 1006 | .unc_absolute => { | 1071 | .unc_absolute => { |
| ... | @@ -1012,30 +1077,30 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator | ... | @@ -1012,30 +1077,30 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator |
| 1012 | if (unc.sep_after_share) len += 1; | 1077 | if (unc.sep_after_share) len += 1; |
| 1013 | break :len len; | 1078 | break :len len; |
| 1014 | }; | 1079 | }; |
| 1015 | try result.ensureUnusedCapacity(allocator, root_len); | 1080 | try al.ensureUnusedCapacity(gpa, root_len); |
| 1016 | result.appendSliceAssumeCapacity("\\\\"); | 1081 | al.appendSliceAssumeCapacity("\\\\"); |
| 1017 | if (unc.server.len > 0 or unc.sep_after_server) { | 1082 | if (unc.server.len > 0 or unc.sep_after_server) { |
| 1018 | result.appendSliceAssumeCapacity(unc.server); | 1083 | al.appendSliceAssumeCapacity(unc.server); |
| 1019 | if (unc.sep_after_server) | 1084 | if (unc.sep_after_server) |
| 1020 | result.appendAssumeCapacity('\\') | 1085 | al.appendAssumeCapacity('\\') |
| 1021 | else | 1086 | else |
| 1022 | want_path_sep_between_root_and_component = true; | 1087 | want_path_sep_between_root_and_component = true; |
| 1023 | } | 1088 | } |
| 1024 | if (unc.share.len > 0) { | 1089 | if (unc.share.len > 0) { |
| 1025 | result.appendSliceAssumeCapacity(unc.share); | 1090 | al.appendSliceAssumeCapacity(unc.share); |
| 1026 | if (unc.sep_after_share) | 1091 | if (unc.sep_after_share) |
| 1027 | result.appendAssumeCapacity('\\') | 1092 | al.appendAssumeCapacity('\\') |
| 1028 | else | 1093 | else |
| 1029 | want_path_sep_between_root_and_component = true; | 1094 | want_path_sep_between_root_and_component = true; |
| 1030 | } | 1095 | } |
| 1031 | }, | 1096 | }, |
| 1032 | .rooted => { | 1097 | .rooted => { |
| 1033 | try result.append(allocator, '\\'); | 1098 | try al.append(gpa, '\\'); |
| 1034 | }, | 1099 | }, |
| 1035 | .relative => {}, | 1100 | .relative => {}, |
| 1036 | } | 1101 | } |
| 1037 | | 1102 | |
| 1038 | const root_len = result.items.len; | 1103 | const root_len = al.items.len - prev_len; |
| 1039 | var negative_count: usize = 0; | 1104 | var negative_count: usize = 0; |
| 1040 | for (paths[first_path_i..], first_path_i..) |path, i| { | 1105 | for (paths[first_path_i..], first_path_i..) |path, i| { |
| 1041 | if (!relevant_paths.isSet(i)) continue; | 1106 | if (!relevant_paths.isSet(i)) continue; |
| ... | @@ -1047,69 +1112,90 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator | ... | @@ -1047,69 +1112,90 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator |
| 1047 | if (mem.eql(u8, component, ".")) { | 1112 | if (mem.eql(u8, component, ".")) { |
| 1048 | continue; | 1113 | continue; |
| 1049 | } else if (mem.eql(u8, component, "..")) { | 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 | negative_count += 1; | 1118 | negative_count += 1; |
| 1052 | continue; | 1119 | continue; |
| 1053 | } | 1120 | } |
| 1054 | while (true) { | 1121 | while ((al.items.len - prev_len) != root_len) { |
| 1055 | if (result.items.len == root_len) { | 1122 | const end_with_sep = PathType.windows.isSep(u8, al.items[al.items.len - 1]); |
| 1056 | break; | 1123 | al.items.len -= 1; |
| 1057 | } | | |
| 1058 | const end_with_sep = PathType.windows.isSep(u8, result.items[result.items.len - 1]); | | |
| 1059 | result.items.len -= 1; | | |
| 1060 | if (end_with_sep) break; | 1124 | if (end_with_sep) break; |
| 1061 | } | 1125 | } |
| 1062 | } else if (result.items.len == root_len and !want_path_sep_between_root_and_component) { | 1126 | } else if ((al.items.len - prev_len) == root_len and !want_path_sep_between_root_and_component) { |
| 1063 | try result.appendSlice(allocator, component); | 1127 | try al.appendSlice(gpa, component); |
| 1064 | } else { | 1128 | } else { |
| 1065 | try result.ensureUnusedCapacity(allocator, 1 + component.len); | 1129 | try al.ensureUnusedCapacity(gpa, 1 + component.len); |
| 1066 | result.appendAssumeCapacity('\\'); | 1130 | al.appendAssumeCapacity('\\'); |
| 1067 | result.appendSliceAssumeCapacity(component); | 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) { | 1136 | if (root_len != 0 and (al.items.len - prev_len) == root_len and negative_count == 0) return; |
| 1073 | return result.toOwnedSlice(allocator); | | |
| 1074 | } | | |
| 1075 | | 1137 | |
| 1076 | if (result.items.len == root_len) { | 1138 | if ((al.items.len - prev_len) == root_len) { |
| 1077 | if (negative_count == 0) { | 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 | for (0..negative_count - 1) |_| { | 1146 | for (0..negative_count - 1) |_| { |
| 1083 | result.appendSliceAssumeCapacity("..\\"); | 1147 | al.appendSliceAssumeCapacity("..\\"); |
| 1084 | } | 1148 | } |
| 1085 | result.appendSliceAssumeCapacity(".."); | 1149 | al.appendSliceAssumeCapacity(".."); |
| 1086 | } else { | 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 | for (0..negative_count) |i| { | 1152 | for (0..negative_count) |i| { |
| 1089 | dest[i * 3 ..][0..3].* = "..\\".*; | 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 | 1158 | /// Simulates a series of relative directory changes on a virtual filesystem that has no symlinks, returning the result |
| 1097 | /// that has no symlinks. | 1159 | /// as an allocated slice. |
| 1098 | /// | 1160 | /// |
| 1099 | /// "." and ".." are resolved but will not make relative paths absolute. ".." | 1161 | /// "." and ".." are resolved but will not make relative paths absolute. ".." components remain in the resolved path |
| 1100 | /// components remain in the resolved path when the resolved path is relative | 1162 | /// when the resolved path is relative and there are not previous components to cancel out. |
| 1101 | /// and there are not previous components to cancel out. | | |
| 1102 | /// | 1163 | /// |
| 1103 | /// The result does not have a trailing path separator. | 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 | 1166 | /// This function does not perform any syscalls. Executing this series of path lookups on an actual filesystem may |
| 1106 | /// lookups on an actual filesystem may produce different results due to | 1167 | /// produce different results due to symlinks. |
| 1107 | /// symlinks. | 1168 | /// |
| 1108 | pub fn resolvePosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![]u8 { | 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 | assert(paths.len > 0); | 1195 | assert(paths.len > 0); |
| 1110 | | 1196 | |
| 1111 | var result: std.ArrayList(u8) = .empty; | 1197 | const prev_len = al.items.len; |
| 1112 | defer result.deinit(gpa); | 1198 | errdefer al.shrinkRetainingCapacity(prev_len); |
| 1113 | | 1199 | |
| 1114 | var negative_count: usize = 0; | 1200 | var negative_count: usize = 0; |
| 1115 | var is_abs = false; | 1201 | var is_abs = false; |
| ... | @@ -1118,63 +1204,52 @@ pub fn resolvePosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![ | ... | @@ -1118,63 +1204,52 @@ pub fn resolvePosix(gpa: Allocator, paths: []const []const u8) Allocator.Error![ |
| 1118 | if (isAbsolutePosix(p)) { | 1204 | if (isAbsolutePosix(p)) { |
| 1119 | is_abs = true; | 1205 | is_abs = true; |
| 1120 | negative_count = 0; | 1206 | negative_count = 0; |
| 1121 | result.clearRetainingCapacity(); | 1207 | al.shrinkRetainingCapacity(prev_len); |
| 1122 | } | 1208 | } |
| 1123 | var it = mem.tokenizeScalar(u8, p, '/'); | 1209 | var it = mem.tokenizeScalar(u8, p, '/'); |
| 1124 | while (it.next()) |component| { | 1210 | while (it.next()) |component| { |
| 1125 | if (mem.eql(u8, component, ".")) { | 1211 | if (mem.eql(u8, component, ".")) { |
| 1126 | continue; | 1212 | continue; |
| 1127 | } else if (mem.eql(u8, component, "..")) { | 1213 | } else if (mem.eql(u8, component, "..")) { |
| 1128 | if (result.items.len == 0) { | 1214 | if ((al.items.len - prev_len) == 0) { |
| 1129 | negative_count += @intFromBool(!is_abs); | 1215 | negative_count += @intFromBool(!is_abs); |
| 1130 | continue; | 1216 | continue; |
| 1131 | } | 1217 | } |
| 1132 | while (true) { | 1218 | while (true) { |
| 1133 | const ends_with_slash = result.items[result.items.len - 1] == '/'; | 1219 | const ends_with_slash = al.items[al.items.len - 1] == '/'; |
| 1134 | result.items.len -= 1; | 1220 | al.items.len -= 1; |
| 1135 | if (ends_with_slash or result.items.len == 0) break; | 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) { | 1223 | } else if ((al.items.len - prev_len) > 0 or is_abs) { |
| 1138 | try result.ensureUnusedCapacity(gpa, 1 + component.len); | 1224 | try al.ensureUnusedCapacity(gpa, 1 + component.len); |
| 1139 | result.appendAssumeCapacity('/'); | 1225 | al.appendAssumeCapacity('/'); |
| 1140 | result.appendSliceAssumeCapacity(component); | 1226 | al.appendSliceAssumeCapacity(component); |
| 1141 | } else { | 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 | if (is_abs) { | 1234 | if (is_abs) { |
| 1149 | return gpa.dupe(u8, "/"); | 1235 | try al.resize(gpa, prev_len + 1); |
| 1150 | } | 1236 | al.items[al.items.len - 1] = '/'; |
| 1151 | if (negative_count == 0) { | 1237 | } else if (negative_count == 0) { |
| 1152 | return gpa.dupe(u8, "."); | 1238 | try al.resize(gpa, prev_len + 1); |
| | 1239 | al.items[al.items.len - 1] = '.'; |
| 1153 | } else { | 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 | var count = negative_count - 1; | 1242 | var count = negative_count - 1; |
| 1156 | var i: usize = 0; | 1243 | var i: usize = prev_len; |
| 1157 | while (count > 0) : (count -= 1) { | 1244 | while (count > 0) : (count -= 1) { |
| 1158 | real_result[i..][0..3].* = "../".*; | 1245 | al.items[i..][0..3].* = "../".*; |
| 1159 | i += 3; | 1246 | i += 3; |
| 1160 | } | 1247 | } |
| 1161 | real_result[i..][0..2].* = "..".*; | 1248 | al.items[i..][0..2].* = "..".*; |
| 1162 | return real_result; | | |
| 1163 | } | 1249 | } |
| 1164 | } | 1250 | } else if (negative_count != 0) { |
| 1165 | | 1251 | const dest = try al.addManyAt(gpa, prev_len, 3 * negative_count); |
| 1166 | if (negative_count == 0) { | 1252 | for (0..negative_count) |i| dest[i * 3 ..][0..3].* = "../".*; |
| 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; | | |
| 1178 | } | 1253 | } |
| 1179 | } | 1254 | } |
| 1180 | | 1255 | |
| ... | @@ -1192,7 +1267,7 @@ test resolve { | ... | @@ -1192,7 +1267,7 @@ test resolve { |
| 1192 | try testResolvePosix(&[_][]const u8{""}, "."); | 1267 | try testResolvePosix(&[_][]const u8{""}, "."); |
| 1193 | } | 1268 | } |
| 1194 | | 1269 | |
| 1195 | test resolveWindows { | 1270 | test resolveAllocWindows { |
| 1196 | try testResolveWindows( | 1271 | try testResolveWindows( |
| 1197 | &[_][]const u8{ "Z:\\", "/usr/local", "lib\\zig\\std\\array_list.zig" }, | 1272 | &[_][]const u8{ "Z:\\", "/usr/local", "lib\\zig\\std\\array_list.zig" }, |
| 1198 | "Z:\\usr\\local\\lib\\zig\\std\\array_list.zig", | 1273 | "Z:\\usr\\local\\lib\\zig\\std\\array_list.zig", |
| ... | @@ -1280,7 +1355,7 @@ test resolveWindows { | ... | @@ -1280,7 +1355,7 @@ test resolveWindows { |
| 1280 | try testResolveWindows(&[_][]const u8{ "C:\\", "\\??\\C:\\foo", "bar" }, "C:\\??\\C:\\foo\\bar"); | 1355 | try testResolveWindows(&[_][]const u8{ "C:\\", "\\??\\C:\\foo", "bar" }, "C:\\??\\C:\\foo\\bar"); |
| 1281 | } | 1356 | } |
| 1282 | | 1357 | |
| 1283 | test resolvePosix { | 1358 | test resolveAllocPosix { |
| 1284 | try testResolvePosix(&.{ "/a/b", "c" }, "/a/b/c"); | 1359 | try testResolvePosix(&.{ "/a/b", "c" }, "/a/b/c"); |
| 1285 | try testResolvePosix(&.{ "/a/b", "c", "//d", "e///" }, "/d/e"); | 1360 | try testResolvePosix(&.{ "/a/b", "c", "//d", "e///" }, "/d/e"); |
| 1286 | try testResolvePosix(&.{ "/a/b/c", "..", "../" }, "/a"); | 1361 | try testResolvePosix(&.{ "/a/b/c", "..", "../" }, "/a"); |
| ... | @@ -1299,15 +1374,45 @@ test resolvePosix { | ... | @@ -1299,15 +1374,45 @@ test resolvePosix { |
| 1299 | } | 1374 | } |
| 1300 | | 1375 | |
| 1301 | fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void { | 1376 | fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void { |
| 1302 | const actual = try resolveWindows(testing.allocator, paths); | 1377 | { |
| 1303 | defer testing.allocator.free(actual); | 1378 | const actual = try resolveAllocWindows(testing.allocator, paths); |
| 1304 | try testing.expectEqualStrings(expected, actual); | 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 | fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { | 1397 | fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { |
| 1308 | const actual = try resolvePosix(testing.allocator, paths); | 1398 | { |
| 1309 | defer testing.allocator.free(actual); | 1399 | const actual = try resolveAllocPosix(testing.allocator, paths); |
| 1310 | try testing.expectEqualStrings(expected, actual); | 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 | /// Strip the last component from a file path. | 1418 | /// Strip the last component from a file path. |
| ... | @@ -1498,17 +1603,18 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void { | ... | @@ -1498,17 +1603,18 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void { |
| 1498 | try testing.expectEqualSlices(u8, expected_output, basenameWindows(input)); | 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 | 1610 | /// If `from` and `to` each resolve to the same path (after calling `resolve` on each), a zero-length string is |
| 1504 | /// depends on the input parameters. | 1611 | /// returned. |
| 1505 | /// | 1612 | /// |
| 1506 | /// If `from` and `to` each resolve to the same path (after calling `resolve` | 1613 | /// See `relativePosix` and `relativeWindows` for operating system specific details and for how `environ_map` is used. |
| 1507 | /// on each), a zero-length string is returned. | | |
| 1508 | /// | 1614 | /// |
| 1509 | /// See `relativePosix` and `relativeWindows` for operating system specific | 1615 | /// See also: |
| 1510 | /// details and for how `environ_map` is used. | 1616 | /// * `relativeAppend` |
| 1511 | pub fn relative( | 1617 | pub fn relativeAlloc( |
| 1512 | gpa: Allocator, | 1618 | gpa: Allocator, |
| 1513 | cwd: []const u8, | 1619 | cwd: []const u8, |
| 1514 | environ_map: ?*const std.process.Environ.Map, | 1620 | environ_map: ?*const std.process.Environ.Map, |
| ... | @@ -1516,30 +1622,59 @@ pub fn relative( | ... | @@ -1516,30 +1622,59 @@ pub fn relative( |
| 1516 | to: []const u8, | 1622 | to: []const u8, |
| 1517 | ) Allocator.Error![]u8 { | 1623 | ) Allocator.Error![]u8 { |
| 1518 | if (native_os == .windows) { | 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 | } else { | 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 | 1661 | /// Other than memory allocation, this is a pure function; the result solely depends on the input parameters. |
| 1528 | /// depends on the input parameters. | | |
| 1529 | /// | 1662 | /// |
| 1530 | /// If `from` and `to` each resolve to the same path (after calling `resolve` | 1663 | /// If `from` and `to` each resolve to the same path (after calling `resolve` on each), a zero-length string is |
| 1531 | /// on each), a zero-length string is returned. | 1664 | /// returned. |
| 1532 | /// | 1665 | /// |
| 1533 | /// The result is not guaranteed to be relative, as the paths may be on | 1666 | /// The result is not guaranteed to be relative, as the paths may be on different volumes. In that case, the result will |
| 1534 | /// different volumes. In that case, the result will be the canonicalized | 1667 | /// be the canonicalized absolute path of `to`. |
| 1535 | /// absolute path of `to`. | | |
| 1536 | /// | 1668 | /// |
| 1537 | /// Per-drive CWDs are stored in special semi-hidden environment variables of | 1669 | /// Per-drive CWDs are stored in special semi-hidden environment variables of the format `=<drive-letter>:`, e.g. `=C:`. |
| 1538 | /// the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is purely a | 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 |
| 1539 | /// shell concept, so there's no guarantee that it'll be set or that it'll even | 1671 | /// accurate. This is the only reason for the `environ_map` parameter. `null` is treated equivalent to the environment |
| 1540 | /// be accurate. This is the only reason for the `environ_map` parameter. `null` is | 1672 | /// variable missing. |
| 1541 | /// treated equivalent to the environment variable missing. | 1673 | /// |
| 1542 | pub fn relativeWindows( | 1674 | /// See also: |
| | 1675 | /// `relativeAlloc` |
| | 1676 | /// `relativeAppendWindows` |
| | 1677 | pub fn relativeAllocWindows( |
| 1543 | gpa: Allocator, | 1678 | gpa: Allocator, |
| 1544 | cwd: []const u8, | 1679 | cwd: []const u8, |
| 1545 | environ_map: ?*const std.process.Environ.Map, | 1680 | environ_map: ?*const std.process.Environ.Map, |
| ... | @@ -1635,6 +1770,37 @@ pub fn relativeWindows( | ... | @@ -1635,6 +1770,37 @@ pub fn relativeWindows( |
| 1635 | return [_]u8{}; | 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 | fn windowsResolveAgainstCwd( | 1804 | fn windowsResolveAgainstCwd( |
| 1639 | gpa: Allocator, | 1805 | gpa: Allocator, |
| 1640 | cwd: []const u8, | 1806 | cwd: []const u8, |
| ... | @@ -1650,9 +1816,9 @@ fn windowsResolveAgainstCwd( | ... | @@ -1650,9 +1816,9 @@ fn windowsResolveAgainstCwd( |
| 1650 | .unc_absolute, | 1816 | .unc_absolute, |
| 1651 | .root_local_device, | 1817 | .root_local_device, |
| 1652 | .local_device, | 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 | .rooted => blk: { | 1823 | .rooted => blk: { |
| 1658 | const parsed_cwd = parsePathWindows(u8, cwd); | 1824 | const parsed_cwd = parsePathWindows(u8, cwd); |
| ... | @@ -1660,13 +1826,13 @@ fn windowsResolveAgainstCwd( | ... | @@ -1660,13 +1826,13 @@ fn windowsResolveAgainstCwd( |
| 1660 | .drive_absolute => { | 1826 | .drive_absolute => { |
| 1661 | var drive_buf = "_:\\".*; | 1827 | var drive_buf = "_:\\".*; |
| 1662 | drive_buf[0] = cwd[0]; | 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 | .unc_absolute => { | 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 | // Effectively a malformed CWD, give up and just return a normalized path | 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 | .drive_relative => blk: { | 1838 | .drive_relative => blk: { |
| ... | @@ -1695,41 +1861,76 @@ fn windowsResolveAgainstCwd( | ... | @@ -1695,41 +1861,76 @@ fn windowsResolveAgainstCwd( |
| 1695 | break :drive_cwd drive_buf; | 1861 | break :drive_cwd drive_buf; |
| 1696 | }; | 1862 | }; |
| 1697 | defer temp_allocator.free(drive_cwd); | 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 | 1889 | /// Other than modifying the provided array list, this is a pure function; the result solely depends on the input |
| 1706 | /// depends on the input parameters. | 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` | 1892 | /// If `from` and `to` each resolve to the same path (after calling `resolveAppend` on each), nothing is appended to the |
| 1709 | /// on each), a zero-length string is returned. | 1893 | /// array list. |
| 1710 | /// | 1894 | /// |
| 1711 | pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 { | 1895 | /// See also: |
| 1712 | const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from }); | 1896 | /// * `relativeAppend` |
| 1713 | defer allocator.free(resolved_from); | 1897 | /// * `relativeAllocPosix` |
| 1714 | const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to }); | 1898 | pub fn relativeAppendPosix( |
| 1715 | defer allocator.free(resolved_to); | 1899 | gpa: Allocator, |
| 1716 | | 1900 | al: *std.ArrayList(u8), |
| 1717 | var from_it = mem.tokenizeScalar(u8, resolved_from, '/'); | 1901 | cwd: []const u8, |
| 1718 | var to_it = mem.tokenizeScalar(u8, resolved_to, '/'); | 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 | while (true) { | 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 | const to_rest = to_it.rest(); | 1925 | const to_rest = to_it.rest(); |
| 1722 | if (to_it.next()) |to_component| { | 1926 | if (to_it.next()) |to_component| { |
| 1723 | if (mem.eql(u8, from_component, to_component)) | 1927 | if (mem.eql(u8, from_component, to_component)) |
| 1724 | continue; | 1928 | continue; |
| 1725 | } | 1929 | } |
| 1726 | var up_count: usize = 1; | 1930 | var up_count: usize = 1; |
| 1727 | while (from_it.next()) |_| { | 1931 | while (from_it.next()) |_| up_count += 1; |
| 1728 | up_count += 1; | | |
| 1729 | } | | |
| 1730 | const up_index_end = up_count * "../".len; | 1932 | const up_index_end = up_count * "../".len; |
| 1731 | const result = try allocator.alloc(u8, up_index_end + to_rest.len); | 1933 | const result = al.items[orig_len..]; |
| 1732 | errdefer allocator.free(result); | | |
| 1733 | | 1934 | |
| 1734 | var result_index: usize = 0; | 1935 | var result_index: usize = 0; |
| 1735 | while (result_index < up_index_end) { | 1936 | while (result_index < up_index_end) { |
| ... | @@ -1737,15 +1938,16 @@ pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to | ... | @@ -1737,15 +1938,16 @@ pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to |
| 1737 | result_index += 3; | 1938 | result_index += 3; |
| 1738 | } | 1939 | } |
| 1739 | if (to_rest.len == 0) { | 1940 | if (to_rest.len == 0) { |
| 1740 | // shave off the trailing slash | 1941 | // Shave off the trailing slash. |
| 1741 | return allocator.realloc(result, result_index - 1); | 1942 | al.shrinkRetainingCapacity(orig_len + result_index - 1); |
| | 1943 | return; |
| 1742 | } | 1944 | } |
| 1743 | | 1945 | @memmove(result[result_index..][0..to_rest.len], to_rest); |
| 1744 | @memcpy(result[result_index..][0..to_rest.len], to_rest); | 1946 | al.shrinkRetainingCapacity(orig_len + result_index + to_rest.len); |
| 1745 | return result; | 1947 | return; |
| 1746 | } | 1948 | } |
| 1747 | | 1949 | |
| 1748 | return [_]u8{}; | 1950 | al.shrinkRetainingCapacity(orig_len); |
| 1749 | } | 1951 | } |
| 1750 | | 1952 | |
| 1751 | test relative { | 1953 | test relative { |
| ... | @@ -1803,15 +2005,44 @@ test relative { | ... | @@ -1803,15 +2005,44 @@ test relative { |
| 1803 | } | 2005 | } |
| 1804 | | 2006 | |
| 1805 | fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void { | 2007 | fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void { |
| 1806 | const result = try relativePosix(testing.allocator, ".", from, to); | 2008 | { |
| 1807 | defer testing.allocator.free(result); | 2009 | const result = try relativePosix(testing.allocator, ".", from, to); |
| 1808 | try testing.expectEqualStrings(expected_output, result); | 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 | fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void { | 2028 | fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void { |
| 1812 | const result = try relativeWindows(testing.allocator, ".", null, from, to); | 2029 | { |
| 1813 | defer testing.allocator.free(result); | 2030 | const result = try relativeAllocWindows(testing.allocator, ".", null, from, to); |
| 1814 | try testing.expectEqualStrings(expected_output, result); | 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 | /// Searches for a file extension separated by a `.` and returns the string after that `.`. | 2048 | /// Searches for a file extension separated by a `.` and returns the string after that `.`. |