Type Function ComponentIterator [src]

A path component iterator that can move forwards and backwards. The 'root' of the path (/ for POSIX, things like C:\, \\server\share\, etc for Windows) is treated specially and will never be returned by any of the first, last, next, or previous functions. Multiple consecutive path separators are skipped (treated as a single separator) when iterating. All returned component names/paths are slices of the original path. There is no normalization of paths performed while iterating.

Prototype

pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type

Parameters

path_type: PathTypeT: type

Source

pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type { return struct { path: []const T, root_end_index: usize = 0, start_index: usize = 0, end_index: usize = 0, const Self = @This(); pub const Component = struct { /// The current component's path name, e.g. 'b'. /// This will never contain path separators. name: []const T, /// The full path up to and including the current component, e.g. '/a/b' /// This will never contain trailing path separators. path: []const T, }; const InitError = switch (path_type) { .windows => error{BadPathName}, else => error{}, }; /// After `init`, `next` will return the first component after the root /// (there is no need to call `first` after `init`). /// To iterate backwards (from the end of the path to the beginning), call `last` /// after `init` and then iterate via `previous` calls. /// For Windows paths, `error.BadPathName` is returned if the `path` has an explicit /// namespace prefix (`\\.\`, `\\?\`, or `\??\`) or if it is a UNC path with more /// than two path separators at the beginning. pub fn init(path: []const T) InitError!Self { const root_end_index: usize = switch (path_type) { .posix, .uefi => posix: { // Root on UEFI and POSIX only differs by the path separator var root_end_index: usize = 0; while (true) : (root_end_index += 1) { if (root_end_index >= path.len or !path_type.isSep(T, path[root_end_index])) { break; } } break :posix root_end_index; }, .windows => windows: { // Namespaces other than the Win32 file namespace are tricky // and basically impossible to determine a 'root' for, since it's // possible to construct an effectively arbitrarily long 'root', // e.g. `\\.\GLOBALROOT\??\UNC\localhost\C$\foo` is a // possible path that would be effectively equivalent to // `C:\foo`, and the `GLOBALROOT\??\` part can also be recursive, // so `GLOBALROOT\??\GLOBALROOT\??\...` would work for any number // of repetitions. Therefore, paths with an explicit namespace prefix // (\\.\, \??\, \\?\) are not allowed here. if (std.os.windows.getNamespacePrefix(T, path) != .none) { return error.BadPathName; } const windows_path_type = std.os.windows.getUnprefixedPathType(T, path); break :windows switch (windows_path_type) { .relative => 0, .root_local_device => path.len, .rooted => 1, .unc_absolute => unc: { var end_index: usize = 2; // Any extra separators between the first two and the server name are not allowed // and will always lead to STATUS_OBJECT_PATH_INVALID if it is attempted // to be used. if (end_index < path.len and path_type.isSep(T, path[end_index])) { return error.BadPathName; } // Server while (end_index < path.len and !path_type.isSep(T, path[end_index])) { end_index += 1; } // Slash(es) after server while (end_index < path.len and path_type.isSep(T, path[end_index])) { end_index += 1; } // Share while (end_index < path.len and !path_type.isSep(T, path[end_index])) { end_index += 1; } // Slash(es) after share while (end_index < path.len and path_type.isSep(T, path[end_index])) { end_index += 1; } break :unc end_index; }, .drive_absolute => drive: { var end_index: usize = 3; while (end_index < path.len and path_type.isSep(T, path[end_index])) { end_index += 1; } break :drive end_index; }, .drive_relative => 2, }; }, }; return .{ .path = path, .root_end_index = root_end_index, .start_index = root_end_index, .end_index = root_end_index, }; } /// Returns the root of the path if it is an absolute path, or null otherwise. /// For POSIX paths, this will be `/`. /// For Windows paths, this will be something like `C:\`, `\\server\share\`, etc. /// For UEFI paths, this will be `\`. pub fn root(self: Self) ?[]const T { if (self.root_end_index == 0) return null; return self.path[0..self.root_end_index]; } /// Returns the first component (from the beginning of the path). /// For example, if the path is `/a/b/c` then this will return the `a` component. /// After calling `first`, `previous` will always return `null`, and `next` will return /// the component to the right of the one returned by `first`, if any exist. pub fn first(self: *Self) ?Component { self.start_index = self.root_end_index; self.end_index = self.start_index; while (self.end_index < self.path.len and !path_type.isSep(T, self.path[self.end_index])) { self.end_index += 1; } if (self.end_index == self.start_index) return null; return .{ .name = self.path[self.start_index..self.end_index], .path = self.path[0..self.end_index], }; } /// Returns the last component (from the end of the path). /// For example, if the path is `/a/b/c` then this will return the `c` component. /// After calling `last`, `next` will always return `null`, and `previous` will return /// the component to the left of the one returned by `last`, if any exist. pub fn last(self: *Self) ?Component { self.end_index = self.path.len; while (true) { if (self.end_index == self.root_end_index) { self.start_index = self.end_index; return null; } if (!path_type.isSep(T, self.path[self.end_index - 1])) break; self.end_index -= 1; } self.start_index = self.end_index; while (true) { if (self.start_index == self.root_end_index) break; if (path_type.isSep(T, self.path[self.start_index - 1])) break; self.start_index -= 1; } if (self.start_index == self.end_index) return null; return .{ .name = self.path[self.start_index..self.end_index], .path = self.path[0..self.end_index], }; } /// Returns the next component (the component to the right of the most recently /// returned component), or null if no such component exists. /// For example, if the path is `/a/b/c` and the most recently returned component /// is `b`, then this will return the `c` component. pub fn next(self: *Self) ?Component { const peek_result = self.peekNext() orelse return null; self.start_index = peek_result.path.len - peek_result.name.len; self.end_index = peek_result.path.len; return peek_result; } /// Like `next`, but does not modify the iterator state. pub fn peekNext(self: Self) ?Component { var start_index = self.end_index; while (start_index < self.path.len and path_type.isSep(T, self.path[start_index])) { start_index += 1; } var end_index = start_index; while (end_index < self.path.len and !path_type.isSep(T, self.path[end_index])) { end_index += 1; } if (start_index == end_index) return null; return .{ .name = self.path[start_index..end_index], .path = self.path[0..end_index], }; } /// Returns the previous component (the component to the left of the most recently /// returned component), or null if no such component exists. /// For example, if the path is `/a/b/c` and the most recently returned component /// is `b`, then this will return the `a` component. pub fn previous(self: *Self) ?Component { const peek_result = self.peekPrevious() orelse return null; self.start_index = peek_result.path.len - peek_result.name.len; self.end_index = peek_result.path.len; return peek_result; } /// Like `previous`, but does not modify the iterator state. pub fn peekPrevious(self: Self) ?Component { var end_index = self.start_index; while (true) { if (end_index == self.root_end_index) return null; if (!path_type.isSep(T, self.path[end_index - 1])) break; end_index -= 1; } var start_index = end_index; while (true) { if (start_index == self.root_end_index) break; if (path_type.isSep(T, self.path[start_index - 1])) break; start_index -= 1; } if (start_index == end_index) return null; return .{ .name = self.path[start_index..end_index], .path = self.path[0..end_index], }; } }; }