| ... | @@ -663,35 +663,17 @@ fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { | ... | @@ -663,35 +663,17 @@ fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { |
| 663 | } | 663 | } |
| 664 | } | 664 | } |
| 665 | | 665 | |
| 666 | pub const Walker = struct { | 666 | pub const SelectiveWalker = struct { |
| 667 | stack: std.ArrayListUnmanaged(StackItem), | 667 | stack: std.ArrayListUnmanaged(Walker.StackItem), |
| 668 | name_buffer: std.ArrayListUnmanaged(u8), | 668 | name_buffer: std.ArrayListUnmanaged(u8), |
| 669 | allocator: Allocator, | 669 | allocator: Allocator, |
| 670 | | 670 | |
| 671 | pub const Entry = struct { | | |
| 672 | /// The containing directory. This can be used to operate directly on `basename` | | |
| 673 | /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. | | |
| 674 | /// The directory remains open until `next` or `deinit` is called. | | |
| 675 | dir: Dir, | | |
| 676 | basename: [:0]const u8, | | |
| 677 | path: [:0]const u8, | | |
| 678 | kind: Dir.Entry.Kind, | | |
| 679 | }; | | |
| 680 | | | |
| 681 | const StackItem = struct { | | |
| 682 | iter: Dir.Iterator, | | |
| 683 | dirname_len: usize, | | |
| 684 | }; | | |
| 685 | | | |
| 686 | /// After each call to this function, and on deinit(), the memory returned | 671 | /// After each call to this function, and on deinit(), the memory returned |
| 687 | /// from this function becomes invalid. A copy must be made in order to keep | 672 | /// from this function becomes invalid. A copy must be made in order to keep |
| 688 | /// a reference to the path. | 673 | /// a reference to the path. |
| 689 | pub fn next(self: *Walker) !?Walker.Entry { | 674 | pub fn next(self: *SelectiveWalker) !?Walker.Entry { |
| 690 | const gpa = self.allocator; | 675 | while (self.stack.items.len > 0) { |
| 691 | while (self.stack.items.len != 0) { | 676 | const top = &self.stack.items[self.stack.items.len - 1]; |
| 692 | // `top` and `containing` become invalid after appending to `self.stack` | | |
| 693 | var top = &self.stack.items[self.stack.items.len - 1]; | | |
| 694 | var containing = top; | | |
| 695 | var dirname_len = top.dirname_len; | 677 | var dirname_len = top.dirname_len; |
| 696 | if (top.iter.next() catch |err| { | 678 | if (top.iter.next() catch |err| { |
| 697 | // If we get an error, then we want the user to be able to continue | 679 | // If we get an error, then we want the user to be able to continue |
| ... | @@ -703,36 +685,22 @@ pub const Walker = struct { | ... | @@ -703,36 +685,22 @@ pub const Walker = struct { |
| 703 | item.iter.dir.close(); | 685 | item.iter.dir.close(); |
| 704 | } | 686 | } |
| 705 | return err; | 687 | return err; |
| 706 | }) |base| { | 688 | }) |entry| { |
| 707 | self.name_buffer.shrinkRetainingCapacity(dirname_len); | 689 | self.name_buffer.shrinkRetainingCapacity(dirname_len); |
| 708 | if (self.name_buffer.items.len != 0) { | 690 | if (self.name_buffer.items.len != 0) { |
| 709 | try self.name_buffer.append(gpa, fs.path.sep); | 691 | try self.name_buffer.append(self.allocator, fs.path.sep); |
| 710 | dirname_len += 1; | 692 | dirname_len += 1; |
| 711 | } | 693 | } |
| 712 | try self.name_buffer.ensureUnusedCapacity(gpa, base.name.len + 1); | 694 | try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1); |
| 713 | self.name_buffer.appendSliceAssumeCapacity(base.name); | 695 | self.name_buffer.appendSliceAssumeCapacity(entry.name); |
| 714 | self.name_buffer.appendAssumeCapacity(0); | 696 | self.name_buffer.appendAssumeCapacity(0); |
| 715 | if (base.kind == .directory) { | 697 | const walker_entry: Walker.Entry = .{ |
| 716 | var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { | 698 | .dir = top.iter.dir, |
| 717 | error.NameTooLong => unreachable, // no path sep in base.name | | |
| 718 | else => |e| return e, | | |
| 719 | }; | | |
| 720 | { | | |
| 721 | errdefer new_dir.close(); | | |
| 722 | try self.stack.append(gpa, .{ | | |
| 723 | .iter = new_dir.iterateAssumeFirstIteration(), | | |
| 724 | .dirname_len = self.name_buffer.items.len - 1, | | |
| 725 | }); | | |
| 726 | top = &self.stack.items[self.stack.items.len - 1]; | | |
| 727 | containing = &self.stack.items[self.stack.items.len - 2]; | | |
| 728 | } | | |
| 729 | } | | |
| 730 | return .{ | | |
| 731 | .dir = containing.iter.dir, | | |
| 732 | .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], | 699 | .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], |
| 733 | .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], | 700 | .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], |
| 734 | .kind = base.kind, | 701 | .kind = entry.kind, |
| 735 | }; | 702 | }; |
| | 703 | return walker_entry; |
| 736 | } else { | 704 | } else { |
| 737 | var item = self.stack.pop().?; | 705 | var item = self.stack.pop().?; |
| 738 | if (self.stack.items.len != 0) { | 706 | if (self.stack.items.len != 0) { |
| ... | @@ -743,20 +711,44 @@ pub const Walker = struct { | ... | @@ -743,20 +711,44 @@ pub const Walker = struct { |
| 743 | return null; | 711 | return null; |
| 744 | } | 712 | } |
| 745 | | 713 | |
| 746 | pub fn deinit(self: *Walker) void { | 714 | /// Traverses into the directory, continuing walking one level down. |
| 747 | const gpa = self.allocator; | 715 | pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void { |
| 748 | // Close any remaining directories except the initial one (which is always at index 0) | 716 | if (entry.kind != .directory) { |
| 749 | if (self.stack.items.len > 1) { | 717 | @branchHint(.cold); |
| 750 | for (self.stack.items[1..]) |*item| { | 718 | return; |
| 751 | item.iter.dir.close(); | 719 | } |
| | 720 | |
| | 721 | var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| { |
| | 722 | switch (err) { |
| | 723 | error.NameTooLong => unreachable, |
| | 724 | else => |e| return e, |
| 752 | } | 725 | } |
| | 726 | }; |
| | 727 | errdefer new_dir.close(); |
| | 728 | |
| | 729 | try self.stack.append(self.allocator, .{ |
| | 730 | .iter = new_dir.iterateAssumeFirstIteration(), |
| | 731 | .dirname_len = self.name_buffer.items.len - 1, |
| | 732 | }); |
| | 733 | } |
| | 734 | |
| | 735 | pub fn deinit(self: *SelectiveWalker) void { |
| | 736 | self.name_buffer.deinit(self.allocator); |
| | 737 | self.stack.deinit(self.allocator); |
| | 738 | } |
| | 739 | |
| | 740 | /// Leaves the current directory, continuing walking one level up. |
| | 741 | pub fn leave(self: *SelectiveWalker) void { |
| | 742 | var item = self.stack.pop().?; |
| | 743 | if (self.stack.items.len != 0) { |
| | 744 | @branchHint(.likely); |
| | 745 | item.iter.dir.close(); |
| 753 | } | 746 | } |
| 754 | self.stack.deinit(gpa); | | |
| 755 | self.name_buffer.deinit(gpa); | | |
| 756 | } | 747 | } |
| 757 | }; | 748 | }; |
| 758 | | 749 | |
| 759 | /// Recursively iterates over a directory. | 750 | /// Recursively iterates over a directory, but requires the user to |
| | 751 | /// opt-in to recursing into each directory entry. |
| 760 | /// | 752 | /// |
| 761 | /// `self` must have been opened with `OpenOptions{.iterate = true}`. | 753 | /// `self` must have been opened with `OpenOptions{.iterate = true}`. |
| 762 | /// | 754 | /// |
| ... | @@ -765,7 +757,9 @@ pub const Walker = struct { | ... | @@ -765,7 +757,9 @@ pub const Walker = struct { |
| 765 | /// The order of returned file system entries is undefined. | 757 | /// The order of returned file system entries is undefined. |
| 766 | /// | 758 | /// |
| 767 | /// `self` will not be closed after walking it. | 759 | /// `self` will not be closed after walking it. |
| 768 | pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { | 760 | /// |
| | 761 | /// See also `walk`. |
| | 762 | pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker { |
| 769 | var stack: std.ArrayListUnmanaged(Walker.StackItem) = .empty; | 763 | var stack: std.ArrayListUnmanaged(Walker.StackItem) = .empty; |
| 770 | | 764 | |
| 771 | try stack.append(allocator, .{ | 765 | try stack.append(allocator, .{ |
| ... | @@ -780,6 +774,64 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { | ... | @@ -780,6 +774,64 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { |
| 780 | }; | 774 | }; |
| 781 | } | 775 | } |
| 782 | | 776 | |
| | 777 | pub const Walker = struct { |
| | 778 | inner: SelectiveWalker, |
| | 779 | |
| | 780 | pub const Entry = struct { |
| | 781 | /// The containing directory. This can be used to operate directly on `basename` |
| | 782 | /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. |
| | 783 | /// The directory remains open until `next` or `deinit` is called. |
| | 784 | dir: Dir, |
| | 785 | basename: [:0]const u8, |
| | 786 | path: [:0]const u8, |
| | 787 | kind: Dir.Entry.Kind, |
| | 788 | }; |
| | 789 | |
| | 790 | const StackItem = struct { |
| | 791 | iter: Dir.Iterator, |
| | 792 | dirname_len: usize, |
| | 793 | }; |
| | 794 | |
| | 795 | /// After each call to this function, and on deinit(), the memory returned |
| | 796 | /// from this function becomes invalid. A copy must be made in order to keep |
| | 797 | /// a reference to the path. |
| | 798 | pub fn next(self: *Walker) !?Walker.Entry { |
| | 799 | const entry = try self.inner.next(); |
| | 800 | if (entry != null and entry.?.kind == .directory) { |
| | 801 | try self.inner.enter(entry.?); |
| | 802 | } |
| | 803 | return entry; |
| | 804 | } |
| | 805 | |
| | 806 | pub fn deinit(self: *Walker) void { |
| | 807 | self.inner.deinit(); |
| | 808 | } |
| | 809 | |
| | 810 | /// Leaves the current directory, continuing walking one level up. |
| | 811 | /// If the current entry is a directory entry, then the "current directory" |
| | 812 | /// is the directory pertaining to the current entry. |
| | 813 | pub fn leave(self: *Walker) void { |
| | 814 | self.inner.leave(); |
| | 815 | } |
| | 816 | }; |
| | 817 | |
| | 818 | /// Recursively iterates over a directory. |
| | 819 | /// |
| | 820 | /// `self` must have been opened with `OpenOptions{.iterate = true}`. |
| | 821 | /// |
| | 822 | /// `Walker.deinit` releases allocated memory and directory handles. |
| | 823 | /// |
| | 824 | /// The order of returned file system entries is undefined. |
| | 825 | /// |
| | 826 | /// `self` will not be closed after walking it. |
| | 827 | /// |
| | 828 | /// See also `walkSelectively`. |
| | 829 | pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { |
| | 830 | return .{ |
| | 831 | .inner = try walkSelectively(self, allocator), |
| | 832 | }; |
| | 833 | } |
| | 834 | |
| 783 | pub const OpenError = error{ | 835 | pub const OpenError = error{ |
| 784 | FileNotFound, | 836 | FileNotFound, |
| 785 | NotDir, | 837 | NotDir, |