authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2023-07-26 15:22:01-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-07-27 10:22:54-07:00
log49053cb1b4af7ee2973cf606def398c7eef6578b
treea8898664b0214883eec972418b5ee7c31395e4f7
parent7e1af51c4d04b82bd95323f46b8976deed90aad8

Add fs.path.ComponentIterator and use it in Dir.makePath

Before this commit, there were three issues with the makePath implementation: 1. The component iteration did not 'collapse' consecutive path separators; instead, it would treat `a/b//c` as `a/b//c`, `a/b/`, `a/b`, and `a`. 2. Trailing path separators led to redundant `makeDir` calls, e.g. with the path `a/b/` (if `a` doesn't exist), it would try to create `a/b/`, then try `a/b`, then try `a`, then try `a/b`, and finally try `a/b/` again. 3. The iteration did not treat the root of a path specially, so on Windows it could attempt to make a directory with a path like `X:` for something like `X:\a\b\c` if the `X:\` drive doesn't exist. This didn't lead to any problems that I could find, but there's no reason to try to make a drive letter as a directory (or any other root path). This commit fixes all three issues by introducing a ComponentIterator that is root-aware and handles both sequential path separators and trailing path separators and uses it in `Dir.makePath`. This reduces the number of `makeDir` calls for paths where (1) the root of the path doesn't exist, (2) there are consecutive path separators, or (3) there are trailing path separators As an example, here are the makeDir calls that would have been made before this commit when calling `makePath` for a relative path like `a/b//c//` (where the full path needs to be created): a/b//c// a/b//c/ a/b//c a/b/ a/b a a/b a/b/ a/b//c a/b//c/ a/b//c// And after this commit: a/b//c a/b a a/b a/b//c

3 files changed, 604 insertions(+), 24 deletions(-)

lib/std/fs.zig+7-17
......@@ -1462,32 +1462,22 @@ pub const Dir = struct {
14621462 /// This function is not atomic, and if it returns an error, the file system may
14631463 /// have been modified regardless.
14641464 pub fn makePath(self: Dir, sub_path: []const u8) !void {
1465 var end_index: usize = sub_path.len;
1465 var it = try path.componentIterator(sub_path);
1466 var component = it.last() orelse return;
14661467 while (true) {
1467 self.makeDir(sub_path[0..end_index]) catch |err| switch (err) {
1468 self.makeDir(component.path) catch |err| switch (err) {
14681469 error.PathAlreadyExists => {
14691470 // TODO stat the file and return an error if it's not a directory
14701471 // this is important because otherwise a dangling symlink
14711472 // could cause an infinite loop
1472 if (end_index == sub_path.len) return;
14731473 },
1474 error.FileNotFound => {
1475 // march end_index backward until next path component
1476 while (true) {
1477 if (end_index == 0) return err;
1478 end_index -= 1;
1479 if (path.isSep(sub_path[end_index])) break;
1480 }
1474 error.FileNotFound => |e| {
1475 component = it.previous() orelse return e;
14811476 continue;
14821477 },
1483 else => return err,
1478 else => |e| return e,
14841479 };
1485 if (end_index == sub_path.len) return;
1486 // march end_index forward until next path component
1487 while (true) {
1488 end_index += 1;
1489 if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break;
1490 }
1480 component = it.next() orelse return;
14911481 }
14921482 }
14931483
lib/std/fs/path.zig+593
......@@ -41,6 +41,21 @@ pub fn isSep(byte: u8) bool {
4141 };
4242}
4343
44pub const PathType = enum {
45 windows,
46 uefi,
47 posix,
48
49 /// Returns true if `c` is a valid path separator for the `path_type`.
50 pub inline fn isSep(comptime path_type: PathType, comptime T: type, c: T) bool {
51 return switch (path_type) {
52 .windows => c == '/' or c == '\\',
53 .posix => c == '/',
54 .uefi => c == '\\',
55 };
56 }
57};
58
4459/// This is different from mem.join in that the separator will not be repeated if
4560/// it is found at the end or beginning of a pair of consecutive paths.
4661fn joinSepMaybeZ(allocator: Allocator, separator: u8, comptime sepPredicate: fn (u8) bool, paths: []const []const u8, zero: bool) ![]u8 {
......@@ -1318,3 +1333,581 @@ test "stem" {
13181333 try testStem(" ", " ");
13191334 try testStem("", "");
13201335}
1336
1337/// A path component iterator that can move forwards and backwards.
1338/// The 'root' of the path (`/` for POSIX, things like `C:\`, `\\server\share\`, etc
1339/// for Windows) is treated specially and will never be returned by any of the
1340/// `first`, `last`, `next`, or `previous` functions.
1341/// Multiple consecutive path separators are skipped (treated as a single separator)
1342/// when iterating.
1343/// All returned component names/paths are slices of the original path.
1344/// There is no normalization of paths performed while iterating.
1345pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
1346 return struct {
1347 path: []const T,
1348 root_end_index: usize = 0,
1349 start_index: usize = 0,
1350 end_index: usize = 0,
1351
1352 const Self = @This();
1353
1354 pub const Component = struct {
1355 /// The current component's path name, e.g. 'b'.
1356 /// This will never contain path separators.
1357 name: []const T,
1358 /// The full path up to and including the current component, e.g. '/a/b'
1359 /// This will never contain trailing path separators.
1360 path: []const T,
1361 };
1362
1363 const InitError = switch (path_type) {
1364 .windows => error{BadPathName},
1365 else => error{},
1366 };
1367
1368 /// After `init`, `next` will return the first component after the root
1369 /// (there is no need to call `first` after `init`).
1370 /// To iterate backwards (from the end of the path to the beginning), call `last`
1371 /// after `init` and then iterate via `previous` calls.
1372 /// For Windows paths, `error.BadPathName` is returned if the `path` has an explicit
1373 /// namespace prefix (`\\.\`, `\\?\`, or `\??\`) or if it is a UNC path with more
1374 /// than two path separators at the beginning.
1375 pub fn init(path: []const T) InitError!Self {
1376 const root_end_index: usize = switch (path_type) {
1377 .posix, .uefi => posix: {
1378 // Root on UEFI and POSIX only differs by the path separator
1379 var root_end_index: usize = 0;
1380 while (true) : (root_end_index += 1) {
1381 if (root_end_index >= path.len or !path_type.isSep(T, path[root_end_index])) {
1382 break;
1383 }
1384 }
1385 break :posix root_end_index;
1386 },
1387 .windows => windows: {
1388 // Namespaces other than the Win32 file namespace are tricky
1389 // and basically impossible to determine a 'root' for, since it's
1390 // possible to construct an effectively arbitrarily long 'root',
1391 // e.g. `\\.\GLOBALROOT\??\UNC\localhost\C$\foo` is a
1392 // possible path that would be effectively equivalent to
1393 // `C:\foo`, and the `GLOBALROOT\??\` part can also be recursive,
1394 // so `GLOBALROOT\??\GLOBALROOT\??\...` would work for any number
1395 // of repetitions. Therefore, paths with an explicit namespace prefix
1396 // (\\.\, \??\, \\?\) are not allowed here.
1397 if (std.os.windows.getNamespacePrefix(T, path) != .none) {
1398 return error.BadPathName;
1399 }
1400 const windows_path_type = std.os.windows.getUnprefixedPathType(T, path);
1401 break :windows switch (windows_path_type) {
1402 .relative => 0,
1403 .root_local_device => path.len,
1404 .rooted => 1,
1405 .unc_absolute => unc: {
1406 var end_index: usize = 2;
1407 // Any extra separators between the first two and the server name are not allowed
1408 // and will always lead to STATUS_OBJECT_PATH_INVALID if it is attempted
1409 // to be used.
1410 if (end_index < path.len and path_type.isSep(T, path[end_index])) {
1411 return error.BadPathName;
1412 }
1413 // Server
1414 while (end_index < path.len and !path_type.isSep(T, path[end_index])) {
1415 end_index += 1;
1416 }
1417 // Slash(es) after server
1418 while (end_index < path.len and path_type.isSep(T, path[end_index])) {
1419 end_index += 1;
1420 }
1421 // Share
1422 while (end_index < path.len and !path_type.isSep(T, path[end_index])) {
1423 end_index += 1;
1424 }
1425 // Slash(es) after share
1426 while (end_index < path.len and path_type.isSep(T, path[end_index])) {
1427 end_index += 1;
1428 }
1429 break :unc end_index;
1430 },
1431 .drive_absolute => drive: {
1432 var end_index: usize = 3;
1433 while (end_index < path.len and path_type.isSep(T, path[end_index])) {
1434 end_index += 1;
1435 }
1436 break :drive end_index;
1437 },
1438 .drive_relative => 2,
1439 };
1440 },
1441 };
1442 return .{
1443 .path = path,
1444 .root_end_index = root_end_index,
1445 .start_index = root_end_index,
1446 .end_index = root_end_index,
1447 };
1448 }
1449
1450 /// Returns the root of the path if it is an absolute path, or null otherwise.
1451 /// For POSIX paths, this will be `/`.
1452 /// For Windows paths, this will be something like `C:\`, `\\server\share\`, etc.
1453 /// For UEFI paths, this will be `\`.
1454 pub fn root(self: Self) ?[]const T {
1455 if (self.root_end_index == 0) return null;
1456 return self.path[0..self.root_end_index];
1457 }
1458
1459 /// Returns the first component (from the beginning of the path).
1460 /// For example, if the path is `/a/b/c` then this will return the `a` component.
1461 /// After calling `first`, `previous` will always return `null`, and `next` will return
1462 /// the component to the right of the one returned by `first`, if any exist.
1463 pub fn first(self: *Self) ?Component {
1464 self.start_index = self.root_end_index;
1465 self.end_index = self.start_index;
1466 while (self.end_index < self.path.len and !path_type.isSep(T, self.path[self.end_index])) {
1467 self.end_index += 1;
1468 }
1469 if (self.end_index == self.start_index) return null;
1470 return .{
1471 .name = self.path[self.start_index..self.end_index],
1472 .path = self.path[0..self.end_index],
1473 };
1474 }
1475
1476 /// Returns the last component (from the end of the path).
1477 /// For example, if the path is `/a/b/c` then this will return the `c` component.
1478 /// After calling `last`, `next` will always return `null`, and `previous` will return
1479 /// the component to the left of the one returned by `last`, if any exist.
1480 pub fn last(self: *Self) ?Component {
1481 self.end_index = self.path.len;
1482 while (true) {
1483 if (self.end_index == self.root_end_index) {
1484 self.start_index = self.end_index;
1485 return null;
1486 }
1487 if (!path_type.isSep(T, self.path[self.end_index - 1])) break;
1488 self.end_index -= 1;
1489 }
1490 self.start_index = self.end_index;
1491 while (true) {
1492 if (self.start_index == self.root_end_index) break;
1493 if (path_type.isSep(T, self.path[self.start_index - 1])) break;
1494 self.start_index -= 1;
1495 }
1496 if (self.start_index == self.end_index) return null;
1497 return .{
1498 .name = self.path[self.start_index..self.end_index],
1499 .path = self.path[0..self.end_index],
1500 };
1501 }
1502
1503 /// Returns the next component (the component to the right of the most recently
1504 /// returned component), or null if no such component exists.
1505 /// For example, if the path is `/a/b/c` and the most recently returned component
1506 /// is `b`, then this will return the `c` component.
1507 pub fn next(self: *Self) ?Component {
1508 var start_index = self.end_index;
1509 while (start_index < self.path.len and path_type.isSep(T, self.path[start_index])) {
1510 start_index += 1;
1511 }
1512 var end_index = start_index;
1513 while (end_index < self.path.len and !path_type.isSep(T, self.path[end_index])) {
1514 end_index += 1;
1515 }
1516 if (start_index == end_index) return null;
1517 self.start_index = start_index;
1518 self.end_index = end_index;
1519 return .{
1520 .name = self.path[self.start_index..self.end_index],
1521 .path = self.path[0..self.end_index],
1522 };
1523 }
1524
1525 /// Returns the previous component (the component to the left of the most recently
1526 /// returned component), or null if no such component exists.
1527 /// For example, if the path is `/a/b/c` and the most recently returned component
1528 /// is `b`, then this will return the `a` component.
1529 pub fn previous(self: *Self) ?Component {
1530 var end_index = self.start_index;
1531 while (true) {
1532 if (end_index == self.root_end_index) return null;
1533 if (!path_type.isSep(T, self.path[end_index - 1])) break;
1534 end_index -= 1;
1535 }
1536 var start_index = end_index;
1537 while (true) {
1538 if (start_index == self.root_end_index) break;
1539 if (path_type.isSep(T, self.path[start_index - 1])) break;
1540 start_index -= 1;
1541 }
1542 if (start_index == end_index) return null;
1543 self.start_index = start_index;
1544 self.end_index = end_index;
1545 return .{
1546 .name = self.path[self.start_index..self.end_index],
1547 .path = self.path[0..self.end_index],
1548 };
1549 }
1550 };
1551}
1552
1553pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) {
1554 .windows => .windows,
1555 .uefi => .uefi,
1556 else => .posix,
1557}, u8);
1558
1559pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator {
1560 return NativeUtf8ComponentIterator.init(path);
1561}
1562
1563test "ComponentIterator posix" {
1564 const PosixComponentIterator = ComponentIterator(.posix, u8);
1565 {
1566 const path = "a/b/c/";
1567 var it = try PosixComponentIterator.init(path);
1568 try std.testing.expectEqual(@as(usize, 0), it.root_end_index);
1569 try std.testing.expect(null == it.root());
1570 {
1571 try std.testing.expect(null == it.previous());
1572
1573 const first_via_next = it.next().?;
1574 try std.testing.expectEqualStrings("a", first_via_next.name);
1575 try std.testing.expectEqualStrings("a", first_via_next.path);
1576
1577 const first = it.first().?;
1578 try std.testing.expectEqualStrings("a", first.name);
1579 try std.testing.expectEqualStrings("a", first.path);
1580
1581 try std.testing.expect(null == it.previous());
1582
1583 const second = it.next().?;
1584 try std.testing.expectEqualStrings("b", second.name);
1585 try std.testing.expectEqualStrings("a/b", second.path);
1586
1587 const third = it.next().?;
1588 try std.testing.expectEqualStrings("c", third.name);
1589 try std.testing.expectEqualStrings("a/b/c", third.path);
1590
1591 try std.testing.expect(null == it.next());
1592 }
1593 {
1594 const last = it.last().?;
1595 try std.testing.expectEqualStrings("c", last.name);
1596 try std.testing.expectEqualStrings("a/b/c", last.path);
1597
1598 try std.testing.expect(null == it.next());
1599
1600 const second_to_last = it.previous().?;
1601 try std.testing.expectEqualStrings("b", second_to_last.name);
1602 try std.testing.expectEqualStrings("a/b", second_to_last.path);
1603
1604 const third_to_last = it.previous().?;
1605 try std.testing.expectEqualStrings("a", third_to_last.name);
1606 try std.testing.expectEqualStrings("a", third_to_last.path);
1607
1608 try std.testing.expect(null == it.previous());
1609 }
1610 }
1611
1612 {
1613 const path = "/a/b/c/";
1614 var it = try PosixComponentIterator.init(path);
1615 try std.testing.expectEqual(@as(usize, 1), it.root_end_index);
1616 try std.testing.expectEqualStrings("/", it.root().?);
1617 {
1618 try std.testing.expect(null == it.previous());
1619
1620 const first_via_next = it.next().?;
1621 try std.testing.expectEqualStrings("a", first_via_next.name);
1622 try std.testing.expectEqualStrings("/a", first_via_next.path);
1623
1624 const first = it.first().?;
1625 try std.testing.expectEqualStrings("a", first.name);
1626 try std.testing.expectEqualStrings("/a", first.path);
1627
1628 try std.testing.expect(null == it.previous());
1629
1630 const second = it.next().?;
1631 try std.testing.expectEqualStrings("b", second.name);
1632 try std.testing.expectEqualStrings("/a/b", second.path);
1633
1634 const third = it.next().?;
1635 try std.testing.expectEqualStrings("c", third.name);
1636 try std.testing.expectEqualStrings("/a/b/c", third.path);
1637
1638 try std.testing.expect(null == it.next());
1639 }
1640 {
1641 const last = it.last().?;
1642 try std.testing.expectEqualStrings("c", last.name);
1643 try std.testing.expectEqualStrings("/a/b/c", last.path);
1644
1645 try std.testing.expect(null == it.next());
1646
1647 const second_to_last = it.previous().?;
1648 try std.testing.expectEqualStrings("b", second_to_last.name);
1649 try std.testing.expectEqualStrings("/a/b", second_to_last.path);
1650
1651 const third_to_last = it.previous().?;
1652 try std.testing.expectEqualStrings("a", third_to_last.name);
1653 try std.testing.expectEqualStrings("/a", third_to_last.path);
1654
1655 try std.testing.expect(null == it.previous());
1656 }
1657 }
1658
1659 {
1660 const path = "/";
1661 var it = try PosixComponentIterator.init(path);
1662 try std.testing.expectEqual(@as(usize, 1), it.root_end_index);
1663 try std.testing.expectEqualStrings("/", it.root().?);
1664
1665 try std.testing.expect(null == it.first());
1666 try std.testing.expect(null == it.previous());
1667 try std.testing.expect(null == it.first());
1668 try std.testing.expect(null == it.next());
1669
1670 try std.testing.expect(null == it.last());
1671 try std.testing.expect(null == it.previous());
1672 try std.testing.expect(null == it.last());
1673 try std.testing.expect(null == it.next());
1674 }
1675
1676 {
1677 const path = "";
1678 var it = try PosixComponentIterator.init(path);
1679 try std.testing.expectEqual(@as(usize, 0), it.root_end_index);
1680 try std.testing.expect(null == it.root());
1681
1682 try std.testing.expect(null == it.first());
1683 try std.testing.expect(null == it.previous());
1684 try std.testing.expect(null == it.first());
1685 try std.testing.expect(null == it.next());
1686
1687 try std.testing.expect(null == it.last());
1688 try std.testing.expect(null == it.previous());
1689 try std.testing.expect(null == it.last());
1690 try std.testing.expect(null == it.next());
1691 }
1692}
1693
1694test "ComponentIterator windows" {
1695 const WindowsComponentIterator = ComponentIterator(.windows, u8);
1696 {
1697 const path = "a/b\\c//";
1698 var it = try WindowsComponentIterator.init(path);
1699 try std.testing.expectEqual(@as(usize, 0), it.root_end_index);
1700 try std.testing.expect(null == it.root());
1701 {
1702 try std.testing.expect(null == it.previous());
1703
1704 const first_via_next = it.next().?;
1705 try std.testing.expectEqualStrings("a", first_via_next.name);
1706 try std.testing.expectEqualStrings("a", first_via_next.path);
1707
1708 const first = it.first().?;
1709 try std.testing.expectEqualStrings("a", first.name);
1710 try std.testing.expectEqualStrings("a", first.path);
1711
1712 try std.testing.expect(null == it.previous());
1713
1714 const second = it.next().?;
1715 try std.testing.expectEqualStrings("b", second.name);
1716 try std.testing.expectEqualStrings("a/b", second.path);
1717
1718 const third = it.next().?;
1719 try std.testing.expectEqualStrings("c", third.name);
1720 try std.testing.expectEqualStrings("a/b\\c", third.path);
1721
1722 try std.testing.expect(null == it.next());
1723 }
1724 {
1725 const last = it.last().?;
1726 try std.testing.expectEqualStrings("c", last.name);
1727 try std.testing.expectEqualStrings("a/b\\c", last.path);
1728
1729 try std.testing.expect(null == it.next());
1730
1731 const second_to_last = it.previous().?;
1732 try std.testing.expectEqualStrings("b", second_to_last.name);
1733 try std.testing.expectEqualStrings("a/b", second_to_last.path);
1734
1735 const third_to_last = it.previous().?;
1736 try std.testing.expectEqualStrings("a", third_to_last.name);
1737 try std.testing.expectEqualStrings("a", third_to_last.path);
1738
1739 try std.testing.expect(null == it.previous());
1740 }
1741 }
1742
1743 {
1744 const path = "C:\\a/b/c/";
1745 var it = try WindowsComponentIterator.init(path);
1746 try std.testing.expectEqual(@as(usize, 3), it.root_end_index);
1747 try std.testing.expectEqualStrings("C:\\", it.root().?);
1748 {
1749 const first = it.first().?;
1750 try std.testing.expectEqualStrings("a", first.name);
1751 try std.testing.expectEqualStrings("C:\\a", first.path);
1752
1753 const second = it.next().?;
1754 try std.testing.expectEqualStrings("b", second.name);
1755 try std.testing.expectEqualStrings("C:\\a/b", second.path);
1756
1757 const third = it.next().?;
1758 try std.testing.expectEqualStrings("c", third.name);
1759 try std.testing.expectEqualStrings("C:\\a/b/c", third.path);
1760
1761 try std.testing.expect(null == it.next());
1762 }
1763 {
1764 const last = it.last().?;
1765 try std.testing.expectEqualStrings("c", last.name);
1766 try std.testing.expectEqualStrings("C:\\a/b/c", last.path);
1767
1768 const second_to_last = it.previous().?;
1769 try std.testing.expectEqualStrings("b", second_to_last.name);
1770 try std.testing.expectEqualStrings("C:\\a/b", second_to_last.path);
1771
1772 const third_to_last = it.previous().?;
1773 try std.testing.expectEqualStrings("a", third_to_last.name);
1774 try std.testing.expectEqualStrings("C:\\a", third_to_last.path);
1775
1776 try std.testing.expect(null == it.previous());
1777 }
1778 }
1779
1780 {
1781 const path = "/";
1782 var it = try WindowsComponentIterator.init(path);
1783 try std.testing.expectEqual(@as(usize, 1), it.root_end_index);
1784 try std.testing.expectEqualStrings("/", it.root().?);
1785
1786 try std.testing.expect(null == it.first());
1787 try std.testing.expect(null == it.previous());
1788 try std.testing.expect(null == it.first());
1789 try std.testing.expect(null == it.next());
1790
1791 try std.testing.expect(null == it.last());
1792 try std.testing.expect(null == it.previous());
1793 try std.testing.expect(null == it.last());
1794 try std.testing.expect(null == it.next());
1795 }
1796
1797 {
1798 const path = "";
1799 var it = try WindowsComponentIterator.init(path);
1800 try std.testing.expectEqual(@as(usize, 0), it.root_end_index);
1801 try std.testing.expect(null == it.root());
1802
1803 try std.testing.expect(null == it.first());
1804 try std.testing.expect(null == it.previous());
1805 try std.testing.expect(null == it.first());
1806 try std.testing.expect(null == it.next());
1807
1808 try std.testing.expect(null == it.last());
1809 try std.testing.expect(null == it.previous());
1810 try std.testing.expect(null == it.last());
1811 try std.testing.expect(null == it.next());
1812 }
1813}
1814
1815test "ComponentIterator windows UTF-16" {
1816 // TODO: Fix on big endian architectures
1817 if (builtin.cpu.arch.endian() != .Little) {
1818 return error.SkipZigTest;
1819 }
1820
1821 const WindowsComponentIterator = ComponentIterator(.windows, u16);
1822 const L = std.unicode.utf8ToUtf16LeStringLiteral;
1823
1824 const path = L("C:\\a/b/c/");
1825 var it = try WindowsComponentIterator.init(path);
1826 try std.testing.expectEqual(@as(usize, 3), it.root_end_index);
1827 try std.testing.expectEqualSlices(u16, L("C:\\"), it.root().?);
1828 {
1829 const first = it.first().?;
1830 try std.testing.expectEqualSlices(u16, L("a"), first.name);
1831 try std.testing.expectEqualSlices(u16, L("C:\\a"), first.path);
1832
1833 const second = it.next().?;
1834 try std.testing.expectEqualSlices(u16, L("b"), second.name);
1835 try std.testing.expectEqualSlices(u16, L("C:\\a/b"), second.path);
1836
1837 const third = it.next().?;
1838 try std.testing.expectEqualSlices(u16, L("c"), third.name);
1839 try std.testing.expectEqualSlices(u16, L("C:\\a/b/c"), third.path);
1840
1841 try std.testing.expect(null == it.next());
1842 }
1843 {
1844 const last = it.last().?;
1845 try std.testing.expectEqualSlices(u16, L("c"), last.name);
1846 try std.testing.expectEqualSlices(u16, L("C:\\a/b/c"), last.path);
1847
1848 const second_to_last = it.previous().?;
1849 try std.testing.expectEqualSlices(u16, L("b"), second_to_last.name);
1850 try std.testing.expectEqualSlices(u16, L("C:\\a/b"), second_to_last.path);
1851
1852 const third_to_last = it.previous().?;
1853 try std.testing.expectEqualSlices(u16, L("a"), third_to_last.name);
1854 try std.testing.expectEqualSlices(u16, L("C:\\a"), third_to_last.path);
1855
1856 try std.testing.expect(null == it.previous());
1857 }
1858}
1859
1860test "ComponentIterator roots" {
1861 // UEFI
1862 {
1863 var it = try ComponentIterator(.uefi, u8).init("\\\\a");
1864 try std.testing.expectEqualStrings("\\\\", it.root().?);
1865
1866 it = try ComponentIterator(.uefi, u8).init("//a");
1867 try std.testing.expect(null == it.root());
1868 }
1869 // POSIX
1870 {
1871 var it = try ComponentIterator(.posix, u8).init("//a");
1872 try std.testing.expectEqualStrings("//", it.root().?);
1873
1874 it = try ComponentIterator(.posix, u8).init("\\\\a");
1875 try std.testing.expect(null == it.root());
1876 }
1877 // Windows
1878 {
1879 // Drive relative
1880 var it = try ComponentIterator(.windows, u8).init("C:a");
1881 try std.testing.expectEqualStrings("C:", it.root().?);
1882
1883 // Drive absolute
1884 it = try ComponentIterator(.windows, u8).init("C://a");
1885 try std.testing.expectEqualStrings("C://", it.root().?);
1886 it = try ComponentIterator(.windows, u8).init("C:\\a");
1887 try std.testing.expectEqualStrings("C:\\", it.root().?);
1888
1889 // Rooted
1890 it = try ComponentIterator(.windows, u8).init("\\a");
1891 try std.testing.expectEqualStrings("\\", it.root().?);
1892 it = try ComponentIterator(.windows, u8).init("/a");
1893 try std.testing.expectEqualStrings("/", it.root().?);
1894
1895 // Root local device
1896 it = try ComponentIterator(.windows, u8).init("\\\\.");
1897 try std.testing.expectEqualStrings("\\\\.", it.root().?);
1898 it = try ComponentIterator(.windows, u8).init("//?");
1899 try std.testing.expectEqualStrings("//?", it.root().?);
1900
1901 // UNC absolute
1902 it = try ComponentIterator(.windows, u8).init("//");
1903 try std.testing.expectEqualStrings("//", it.root().?);
1904 it = try ComponentIterator(.windows, u8).init("\\\\a");
1905 try std.testing.expectEqualStrings("\\\\a", it.root().?);
1906 it = try ComponentIterator(.windows, u8).init("\\\\a\\b\\\\c");
1907 try std.testing.expectEqualStrings("\\\\a\\b\\\\", it.root().?);
1908 it = try ComponentIterator(.windows, u8).init("//a");
1909 try std.testing.expectEqualStrings("//a", it.root().?);
1910 it = try ComponentIterator(.windows, u8).init("//a/b//c");
1911 try std.testing.expectEqualStrings("//a/b//", it.root().?);
1912 }
1913}
lib/std/os/windows.zig+4-7
......@@ -2319,10 +2319,6 @@ pub const UnprefixedPathType = enum {
23192319 root_local_device,
23202320};
23212321
2322inline fn isSepW(c: u16) bool {
2323 return c == '/' or c == '\\';
2324}
2325
23262322/// Get the path type of a path that is known to not have any namespace prefixes
23272323/// (`\\?\`, `\\.\`, `\??\`).
23282324pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
......@@ -2332,9 +2328,10 @@ pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathTy
23322328 std.debug.assert(getNamespacePrefix(T, path) == .none);
23332329 }
23342330
2335 if (isSepW(path[0])) {
2331 const windows_path = std.fs.path.PathType.windows;
2332 if (windows_path.isSep(T, path[0])) {
23362333 // \x
2337 if (path.len < 2 or !isSepW(path[1])) return .rooted;
2334 if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
23382335 // exactly \\. or \\? with nothing trailing
23392336 if (path.len == 3 and (path[2] == '.' or path[2] == '?')) return .root_local_device;
23402337 // \\x
......@@ -2343,7 +2340,7 @@ pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathTy
23432340 // x
23442341 if (path.len < 2 or path[1] != ':') return .relative;
23452342 // x:\
2346 if (path.len > 2 and isSepW(path[2])) return .drive_absolute;
2343 if (path.len > 2 and windows_path.isSep(T, path[2])) return .drive_absolute;
23472344 // x:
23482345 return .drive_relative;
23492346 }