From 760127a7608f671daa60668a3c60a18058adff13 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 24 Sep 2025 16:17:51 -0700 Subject: [PATCH 1/9] Add SelectiveWalker/walkSelectively and implement Walker in terms of it This is a breaking change, since the fields of Walker have changed. The function APIs are unchanged, though. --- lib/std/fs/Dir.zig | 162 +++++++++++++++++++++++++++++--------------- lib/std/fs/test.zig | 61 +++++++++++++++++ 2 files changed, 168 insertions(+), 55 deletions(-) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index d097573122eb9d9ae0e6e78936bb8720d3a9bc91..2d9057793c2dfb5e08226cca7a1fc24ee5ce718c 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -663,35 +663,17 @@ fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { } } -pub const Walker = struct { - stack: std.ArrayListUnmanaged(StackItem), +pub const SelectiveWalker = struct { + stack: std.ArrayListUnmanaged(Walker.StackItem), name_buffer: std.ArrayListUnmanaged(u8), allocator: Allocator, - pub const Entry = struct { - /// The containing directory. This can be used to operate directly on `basename` - /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. - /// The directory remains open until `next` or `deinit` is called. - dir: Dir, - basename: [:0]const u8, - path: [:0]const u8, - kind: Dir.Entry.Kind, - }; - - const StackItem = struct { - iter: Dir.Iterator, - dirname_len: usize, - }; - /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. - pub fn next(self: *Walker) !?Walker.Entry { - const gpa = self.allocator; - while (self.stack.items.len != 0) { - // `top` and `containing` become invalid after appending to `self.stack` - var top = &self.stack.items[self.stack.items.len - 1]; - var containing = top; + pub fn next(self: *SelectiveWalker) !?Walker.Entry { + while (self.stack.items.len > 0) { + const top = &self.stack.items[self.stack.items.len - 1]; var dirname_len = top.dirname_len; if (top.iter.next() catch |err| { // If we get an error, then we want the user to be able to continue @@ -703,36 +685,22 @@ pub const Walker = struct { item.iter.dir.close(); } return err; - }) |base| { + }) |entry| { self.name_buffer.shrinkRetainingCapacity(dirname_len); if (self.name_buffer.items.len != 0) { - try self.name_buffer.append(gpa, fs.path.sep); + try self.name_buffer.append(self.allocator, fs.path.sep); dirname_len += 1; } - try self.name_buffer.ensureUnusedCapacity(gpa, base.name.len + 1); - self.name_buffer.appendSliceAssumeCapacity(base.name); + try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1); + self.name_buffer.appendSliceAssumeCapacity(entry.name); self.name_buffer.appendAssumeCapacity(0); - if (base.kind == .directory) { - var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { - error.NameTooLong => unreachable, // no path sep in base.name - else => |e| return e, - }; - { - errdefer new_dir.close(); - try self.stack.append(gpa, .{ - .iter = new_dir.iterateAssumeFirstIteration(), - .dirname_len = self.name_buffer.items.len - 1, - }); - top = &self.stack.items[self.stack.items.len - 1]; - containing = &self.stack.items[self.stack.items.len - 2]; - } - } - return .{ - .dir = containing.iter.dir, + const walker_entry: Walker.Entry = .{ + .dir = top.iter.dir, .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], - .kind = base.kind, + .kind = entry.kind, }; + return walker_entry; } else { var item = self.stack.pop().?; if (self.stack.items.len != 0) { @@ -743,20 +711,44 @@ pub const Walker = struct { return null; } - pub fn deinit(self: *Walker) void { - const gpa = self.allocator; - // Close any remaining directories except the initial one (which is always at index 0) - if (self.stack.items.len > 1) { - for (self.stack.items[1..]) |*item| { - item.iter.dir.close(); + /// Traverses into the directory, continuing walking one level down. + pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void { + if (entry.kind != .directory) { + @branchHint(.cold); + return; + } + + var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| { + switch (err) { + error.NameTooLong => unreachable, + else => |e| return e, } + }; + errdefer new_dir.close(); + + try self.stack.append(self.allocator, .{ + .iter = new_dir.iterateAssumeFirstIteration(), + .dirname_len = self.name_buffer.items.len - 1, + }); + } + + pub fn deinit(self: *SelectiveWalker) void { + self.name_buffer.deinit(self.allocator); + self.stack.deinit(self.allocator); + } + + /// Leaves the current directory, continuing walking one level up. + pub fn leave(self: *SelectiveWalker) void { + var item = self.stack.pop().?; + if (self.stack.items.len != 0) { + @branchHint(.likely); + item.iter.dir.close(); } - self.stack.deinit(gpa); - self.name_buffer.deinit(gpa); } }; -/// Recursively iterates over a directory. +/// Recursively iterates over a directory, but requires the user to +/// opt-in to recursing into each directory entry. /// /// `self` must have been opened with `OpenOptions{.iterate = true}`. /// @@ -765,7 +757,9 @@ pub const Walker = struct { /// The order of returned file system entries is undefined. /// /// `self` will not be closed after walking it. -pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { +/// +/// See also `walk`. +pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker { var stack: std.ArrayListUnmanaged(Walker.StackItem) = .empty; try stack.append(allocator, .{ @@ -780,6 +774,64 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { }; } +pub const Walker = struct { + inner: SelectiveWalker, + + pub const Entry = struct { + /// The containing directory. This can be used to operate directly on `basename` + /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. + /// The directory remains open until `next` or `deinit` is called. + dir: Dir, + basename: [:0]const u8, + path: [:0]const u8, + kind: Dir.Entry.Kind, + }; + + const StackItem = struct { + iter: Dir.Iterator, + dirname_len: usize, + }; + + /// After each call to this function, and on deinit(), the memory returned + /// from this function becomes invalid. A copy must be made in order to keep + /// a reference to the path. + pub fn next(self: *Walker) !?Walker.Entry { + const entry = try self.inner.next(); + if (entry != null and entry.?.kind == .directory) { + try self.inner.enter(entry.?); + } + return entry; + } + + pub fn deinit(self: *Walker) void { + self.inner.deinit(); + } + + /// Leaves the current directory, continuing walking one level up. + /// If the current entry is a directory entry, then the "current directory" + /// is the directory pertaining to the current entry. + pub fn leave(self: *Walker) void { + self.inner.leave(); + } +}; + +/// Recursively iterates over a directory. +/// +/// `self` must have been opened with `OpenOptions{.iterate = true}`. +/// +/// `Walker.deinit` releases allocated memory and directory handles. +/// +/// The order of returned file system entries is undefined. +/// +/// `self` will not be closed after walking it. +/// +/// See also `walkSelectively`. +pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { + return .{ + .inner = try walkSelectively(self, allocator), + }; +} + pub const OpenError = error{ FileNotFound, NotDir, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 65e86e4c2e40e7f89ba0888002656bb4de168064..7c28939bef3f528ed8233c86b3224c1fd9f7df9c 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1810,6 +1810,67 @@ test "walker" { try testing.expectEqual(expected_paths.kvs.len, num_walked); } +test "selective walker, skip entries that start with ." { + var tmp = tmpDir(.{ .iterate = true }); + defer tmp.cleanup(); + + const paths_to_create: []const []const u8 = &.{ + "dir1/foo/.git/ignored", + ".hidden/bar", + "a/b/c", + "a/baz", + }; + + // iteration order of walker is undefined, so need lookup maps to check against + + const expected_paths = std.StaticStringMap(void).initComptime(.{ + .{"dir1"}, + .{"dir1" ++ fs.path.sep_str ++ "foo"}, + .{"a"}, + .{"a" ++ fs.path.sep_str ++ "b"}, + .{"a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"}, + .{"a" ++ fs.path.sep_str ++ "baz"}, + }); + + const expected_basenames = std.StaticStringMap(void).initComptime(.{ + .{"dir1"}, + .{"foo"}, + .{"a"}, + .{"b"}, + .{"c"}, + .{"baz"}, + }); + + for (paths_to_create) |path| { + try tmp.dir.makePath(path); + } + + var walker = try tmp.dir.walkSelectively(testing.allocator); + defer walker.deinit(); + + var num_walked: usize = 0; + while (try walker.next()) |entry| { + if (entry.basename[0] == '.') continue; + if (entry.kind == .directory) { + try walker.enter(entry); + } + + testing.expect(expected_basenames.has(entry.basename)) catch |err| { + std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); + return err; + }; + testing.expect(expected_paths.has(entry.path)) catch |err| { + std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + // make sure that the entry.dir is the containing dir + var entry_dir = try entry.dir.openDir(entry.basename, .{}); + defer entry_dir.close(); + num_walked += 1; + } + try testing.expectEqual(expected_paths.kvs.len, num_walked); +} + test "walker without fully iterating" { var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); -- 2.54.0 From df394faf776aa7012649dc1724ccb66d78a1439e Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Thu, 25 Sep 2025 22:20:52 +0200 Subject: [PATCH 2/9] feat: Reintroduce depth Some decision-making might depend on the level of the traversal, so it makes sense to expose depth here since it's stable, and not in the automatic walker where it's not. --- lib/std/fs/Dir.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 2d9057793c2dfb5e08226cca7a1fc24ee5ce718c..99d62b9d9db4bbcc1979a8deb4d1db7c48c7a277 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -732,6 +732,10 @@ pub const SelectiveWalker = struct { }); } + pub fn depth(self: *SelectiveWalker) usize { + return self.stack.items.len; + } + pub fn deinit(self: *SelectiveWalker) void { self.name_buffer.deinit(self.allocator); self.stack.deinit(self.allocator); -- 2.54.0 From ec4514def41db988f89c2da180f7a8f87168f5d1 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Thu, 25 Sep 2025 22:22:08 +0200 Subject: [PATCH 3/9] refactor: Reimplement tool using SelectiveWalker This skips directory trees where top-level directories do not match the defined ones --- tools/update_mingw.zig | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index 9c67b27375bbd626c8e5a40eba71e4717f1f5eae..3f820936817d6f203d5536bcea33a60ad1503256 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -109,13 +109,31 @@ pub fn main() !void { { // Also add all new def and def.in files. - var walker = try src_crt_dir.walk(arena); + var walker = try src_crt_dir.walkSelectively(arena); defer walker.deinit(); var fail = false; while (try walker.next()) |entry| { - if (entry.kind != .file) continue; + switch (entry.kind) { + .directory => { + switch (walker.depth()) { + 1 => for (def_dirs) |p| { + if (std.mem.eql(u8, entry.basename, p)) { + try walker.enter(entry); + continue; + } + }, + else => { + // The top-level directory was already validated + try walker.enter(entry); + continue; + }, + } + }, + .file => {}, + else => continue, + } const ok_ext = for (def_exts) |ext| { if (std.mem.endsWith(u8, entry.path, ext)) break true; @@ -123,12 +141,6 @@ pub fn main() !void { if (!ok_ext) continue; - const ok_prefix = for (def_dirs) |p| { - if (std.mem.startsWith(u8, entry.path, p)) break true; - } else false; - - if (!ok_prefix) continue; - const blacklisted = for (blacklisted_defs) |item| { if (std.mem.eql(u8, entry.basename, item)) break true; } else false; -- 2.54.0 From a26a654c55e0f3015e03b745b4c22b2bc90d3dc9 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Thu, 25 Sep 2025 22:39:11 +0200 Subject: [PATCH 4/9] test: Include expected depth test --- lib/std/fs/test.zig | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 7c28939bef3f528ed8233c86b3224c1fd9f7df9c..7354b91c702a025438d1892e60c7fcb13247907b 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1823,13 +1823,13 @@ test "selective walker, skip entries that start with ." { // iteration order of walker is undefined, so need lookup maps to check against - const expected_paths = std.StaticStringMap(void).initComptime(.{ - .{"dir1"}, - .{"dir1" ++ fs.path.sep_str ++ "foo"}, - .{"a"}, - .{"a" ++ fs.path.sep_str ++ "b"}, - .{"a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"}, - .{"a" ++ fs.path.sep_str ++ "baz"}, + const expected_paths = std.StaticStringMap(usize).initComptime(.{ + .{ "dir1", 1 }, + .{ "dir1" ++ fs.path.sep_str ++ "foo", 2 }, + .{ "a", 1 }, + .{ "a" ++ fs.path.sep_str ++ "b", 2 }, + .{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 }, + .{ "a" ++ fs.path.sep_str ++ "baz", 2 }, }); const expected_basenames = std.StaticStringMap(void).initComptime(.{ @@ -1863,6 +1863,11 @@ test "selective walker, skip entries that start with ." { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; + + testing.expectEqual(expected_paths.get(entry.path).?, walker.depth()) catch |err| { + std.debug.print("path reported unexpected depth: {f}, {d}", .{ std.ascii.hexEscape(entry.path, .lower), walker.depth() }); + return err; + }; // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); defer entry_dir.close(); -- 2.54.0 From 7bee39c1fdf19303f8c174727ce84ae787ad35bf Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Thu, 25 Sep 2025 23:27:13 +0200 Subject: [PATCH 5/9] test: enter after check to preserve depth --- lib/std/fs/test.zig | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 7354b91c702a025438d1892e60c7fcb13247907b..77b16c1164164238c9b4eb1128d0e7e4f7d674b5 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1851,23 +1851,24 @@ test "selective walker, skip entries that start with ." { var num_walked: usize = 0; while (try walker.next()) |entry| { if (entry.basename[0] == '.') continue; + testing.expect(expected_basenames.has(entry.basename)) catch |err| { + std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); + return err; + }; + testing.expect(expected_paths.has(entry.path)) catch |err| { + std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + + testing.expectEqual(expected_paths.get(entry.path).?, walker.depth()) catch |err| { + std.debug.print("path reported unexpected depth: {f}, {d}, expected {d}\n", .{ std.ascii.hexEscape(entry.path, .lower), walker.depth(), expected_paths.get(entry.path).? }); + return err; + }; + if (entry.kind == .directory) { try walker.enter(entry); } - testing.expect(expected_basenames.has(entry.basename)) catch |err| { - std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); - return err; - }; - testing.expect(expected_paths.has(entry.path)) catch |err| { - std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); - return err; - }; - - testing.expectEqual(expected_paths.get(entry.path).?, walker.depth()) catch |err| { - std.debug.print("path reported unexpected depth: {f}, {d}", .{ std.ascii.hexEscape(entry.path, .lower), walker.depth() }); - return err; - }; // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); defer entry_dir.close(); -- 2.54.0 From a0ec5d1c6ee38e8084e8293030804e4c84fa3b56 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Thu, 25 Sep 2025 23:29:34 +0200 Subject: [PATCH 6/9] fix: Match prefix with static string map Co-authored-by: Ryan Liptak --- tools/update_mingw.zig | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index 3f820936817d6f203d5536bcea33a60ad1503256..138179d2fa09ec23be31ebc6a481373358059970 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -118,11 +118,9 @@ pub fn main() !void { switch (entry.kind) { .directory => { switch (walker.depth()) { - 1 => for (def_dirs) |p| { - if (std.mem.eql(u8, entry.basename, p)) { - try walker.enter(entry); - continue; - } + 1 => if (def_dirs.get(entry.basename)) { + try walker.enter(entry); + continue; }, else => { // The top-level directory was already validated @@ -174,14 +172,14 @@ const def_exts = [_][]const u8{ ".def.in", }; -const def_dirs = [_][]const u8{ - "lib32" ++ std.fs.path.sep_str, - "lib64" ++ std.fs.path.sep_str, - "libarm32" ++ std.fs.path.sep_str, - "libarm64" ++ std.fs.path.sep_str, - "lib-common" ++ std.fs.path.sep_str, - "def-include" ++ std.fs.path.sep_str, -}; +const def_dirs = std.StaticStringMap(void).initComptime(.{ + .{"lib32"}, + .{"lib64"}, + .{"libarm32"}, + .{"libarm64"}, + .{"lib-common"}, + .{"def-include"}, +}); const blacklisted_defs = [_][]const u8{ "crtdll.def.in", -- 2.54.0 From 83f8441c4f26bdc2ca630aa6225512f9d3927a66 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Thu, 25 Sep 2025 23:33:45 +0200 Subject: [PATCH 7/9] fix: Check if key exists instead of trying to match on null Co-Authored-By: Ryan Liptak --- tools/update_mingw.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index 138179d2fa09ec23be31ebc6a481373358059970..8e5b403be699c1586ab48801d5a3f2defadb475a 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -118,7 +118,7 @@ pub fn main() !void { switch (entry.kind) { .directory => { switch (walker.depth()) { - 1 => if (def_dirs.get(entry.basename)) { + 1 => if (def_dirs.has(entry.basename)) { try walker.enter(entry); continue; }, -- 2.54.0 From 98dd8856ef901b99f198d992146abd0545adb5b2 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 3 Oct 2025 14:22:11 -0700 Subject: [PATCH 8/9] std.mem: Add `countScalar` --- build.zig | 2 +- lib/std/mem.zig | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index c57c3e713ad186ed5e72dd641b386f2109affda1..7208d1fbb9961bd0c350163f1a6dbf38f2268de5 100644 --- a/build.zig +++ b/build.zig @@ -260,7 +260,7 @@ pub fn build(b: *std.Build) !void { }; const git_describe = mem.trim(u8, git_describe_untrimmed, " \n\r"); - switch (mem.count(u8, git_describe, "-")) { + switch (mem.countScalar(u8, git_describe, '-')) { 0 => { // Tagged release version (e.g. 0.10.0). if (!mem.eql(u8, git_describe, version_string)) { diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 3cd0507bee9546ddba12c2f00ac14890916aaa31..3763de5180d623c1d58b0e0c3be210736e2f98e6 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1704,6 +1704,26 @@ test count { try testing.expect(count(u8, "owowowu", "owowu") == 1); } +/// Returns the number of needles inside the haystack +pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize { + var i: usize = 0; + var found: usize = 0; + + while (findScalarPos(T, haystack, i, needle)) |idx| { + i = idx + 1; + found += 1; + } + + return found; +} + +test countScalar { + try testing.expectEqual(0, countScalar(u8, "", 'h')); + try testing.expectEqual(1, countScalar(u8, "h", 'h')); + try testing.expectEqual(2, countScalar(u8, "hh", 'h')); + try testing.expectEqual(3, countScalar(u8, " abcabc abc", 'b')); +} + /// Returns true if the haystack contains expected_count or more needles /// needle.len must be > 0 /// does not count overlapping needles -- 2.54.0 From 894a99171491c9b054ae93b10ca3dc6fa6d8c125 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 3 Oct 2025 14:25:12 -0700 Subject: [PATCH 9/9] Add depth function to `Walker.Entry` This enables depth-related use cases without any dependency on the Walker's internal stack which doesn't always pertain to the actual depth of the current entry (i.e. recursing into a directory immediately affects the stack). --- lib/std/fs/Dir.zig | 13 ++++++++---- lib/std/fs/test.zig | 47 ++++++++++++++++++++++-------------------- tools/update_mingw.zig | 2 +- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 99d62b9d9db4bbcc1979a8deb4d1db7c48c7a277..f5df4a969fbfae78e2135b00d86683dcbcbd9225 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -732,16 +732,14 @@ pub const SelectiveWalker = struct { }); } - pub fn depth(self: *SelectiveWalker) usize { - return self.stack.items.len; - } - pub fn deinit(self: *SelectiveWalker) void { self.name_buffer.deinit(self.allocator); self.stack.deinit(self.allocator); } /// Leaves the current directory, continuing walking one level up. + /// If the current entry is a directory entry, then the "current directory" + /// will pertain to that entry if `enter` is called before `leave`. pub fn leave(self: *SelectiveWalker) void { var item = self.stack.pop().?; if (self.stack.items.len != 0) { @@ -789,6 +787,13 @@ pub const Walker = struct { basename: [:0]const u8, path: [:0]const u8, kind: Dir.Entry.Kind, + + /// Returns the depth of the entry relative to the initial directory. + /// Returns 1 for a direct child of the initial directory, 2 for an entry + /// within a direct child of the initial directory, etc. + pub fn depth(self: Walker.Entry) usize { + return mem.countScalar(u8, self.path, fs.path.sep) + 1; + } }; const StackItem = struct { diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 77b16c1164164238c9b4eb1128d0e7e4f7d674b5..b88456ef7c1f7995619f70491954f0372e991092 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1765,14 +1765,14 @@ test "walker" { // iteration order of walker is undefined, so need lookup maps to check against - const expected_paths = std.StaticStringMap(void).initComptime(.{ - .{"dir1"}, - .{"dir2"}, - .{"dir3"}, - .{"dir4"}, - .{"dir3" ++ fs.path.sep_str ++ "sub1"}, - .{"dir3" ++ fs.path.sep_str ++ "sub2"}, - .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"}, + const expected_paths = std.StaticStringMap(usize).initComptime(.{ + .{ "dir1", 1 }, + .{ "dir2", 1 }, + .{ "dir3", 1 }, + .{ "dir4", 1 }, + .{ "dir3" ++ fs.path.sep_str ++ "sub1", 2 }, + .{ "dir3" ++ fs.path.sep_str ++ "sub2", 2 }, + .{ "dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1", 3 }, }); const expected_basenames = std.StaticStringMap(void).initComptime(.{ @@ -1802,6 +1802,10 @@ test "walker" { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; + testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { + std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); defer entry_dir.close(); @@ -1851,24 +1855,23 @@ test "selective walker, skip entries that start with ." { var num_walked: usize = 0; while (try walker.next()) |entry| { if (entry.basename[0] == '.') continue; - testing.expect(expected_basenames.has(entry.basename)) catch |err| { - std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); - return err; - }; - testing.expect(expected_paths.has(entry.path)) catch |err| { - std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); - return err; - }; - - testing.expectEqual(expected_paths.get(entry.path).?, walker.depth()) catch |err| { - std.debug.print("path reported unexpected depth: {f}, {d}, expected {d}\n", .{ std.ascii.hexEscape(entry.path, .lower), walker.depth(), expected_paths.get(entry.path).? }); - return err; - }; - if (entry.kind == .directory) { try walker.enter(entry); } + testing.expect(expected_basenames.has(entry.basename)) catch |err| { + std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); + return err; + }; + testing.expect(expected_paths.has(entry.path)) catch |err| { + std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { + std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); defer entry_dir.close(); diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index 8e5b403be699c1586ab48801d5a3f2defadb475a..6288807cf1fabd290820165189d07df2df42179c 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -117,7 +117,7 @@ pub fn main() !void { while (try walker.next()) |entry| { switch (entry.kind) { .directory => { - switch (walker.depth()) { + switch (entry.depth()) { 1 => if (def_dirs.has(entry.basename)) { try walker.enter(entry); continue; -- 2.54.0