| ... | @@ -41,6 +41,21 @@ pub fn isSep(byte: u8) bool { | ... | @@ -41,6 +41,21 @@ pub fn isSep(byte: u8) bool { |
| 41 | }; | 41 | }; |
| 42 | } | 42 | } |
| 43 | | 43 | |
| | 44 | pub 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 | |
| 44 | /// This is different from mem.join in that the separator will not be repeated if | 59 | /// This is different from mem.join in that the separator will not be repeated if |
| 45 | /// it is found at the end or beginning of a pair of consecutive paths. | 60 | /// it is found at the end or beginning of a pair of consecutive paths. |
| 46 | fn joinSepMaybeZ(allocator: Allocator, separator: u8, comptime sepPredicate: fn (u8) bool, paths: []const []const u8, zero: bool) ![]u8 { | 61 | fn joinSepMaybeZ(allocator: Allocator, separator: u8, comptime sepPredicate: fn (u8) bool, paths: []const []const u8, zero: bool) ![]u8 { |
| ... | @@ -1318,3 +1333,581 @@ test "stem" { | ... | @@ -1318,3 +1333,581 @@ test "stem" { |
| 1318 | try testStem(" ", " "); | 1333 | try testStem(" ", " "); |
| 1319 | try testStem("", ""); | 1334 | try testStem("", ""); |
| 1320 | } | 1335 | } |
| | 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. |
| | 1345 | pub 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 | |
| | 1553 | pub const NativeUtf8ComponentIterator = ComponentIterator(switch (native_os) { |
| | 1554 | .windows => .windows, |
| | 1555 | .uefi => .uefi, |
| | 1556 | else => .posix, |
| | 1557 | }, u8); |
| | 1558 | |
| | 1559 | pub fn componentIterator(path: []const u8) !NativeUtf8ComponentIterator { |
| | 1560 | return NativeUtf8ComponentIterator.init(path); |
| | 1561 | } |
| | 1562 | |
| | 1563 | test "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 | |
| | 1694 | test "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 | |
| | 1815 | test "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 | |
| | 1860 | test "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 | } |