Ignore
zpack.Ignore is the .zpackignore engine. pack uses it
internally, and it is public so a build script or asset pipeline can apply the
same rules without packing anything.
var ignore = try zpack.Ignore.load(gpa, io, src_dir, ".zpackignore");defer ignore.deinit();
if (ignore.match("art/hero.psd", false)) { // excluded}For the pattern syntax itself, see .zpackignore.
pub fn load(gpa: Allocator, io: std.Io, dir: std.Io.Dir, sub_path: []const u8) !IgnoreReads and parses an ignore file from dir. Returns Ignore.empty when the file
is absent - the common case, and not an error.
The file is read with a 1 MiB limit.
pub fn parse(gpa: Allocator, source: []const u8) !IgnoreParses ignore rules from memory, for a ruleset that does not come from a file:
var ignore = try zpack.Ignore.parse(gpa, \\*.psd \\/build/ \\!keep/keep.png);defer ignore.deinit();The result owns a copy of source, so source may be freed immediately after.
Returns error.TooManyRules beyond 1024 rules. That cap bounds how much work
matching a single path can cost.
pub fn match(self: Ignore, path: []const u8, is_dir: bool) boolWhether path is excluded. path must be /-separated and relative to the
archive root - the same form the archive stores.
is_dir matters: a rule written with a trailing / is directory-only and is
skipped entirely for files.
Rules are applied in order and the last one to match decides, so a ! rule
only undoes rules written above it.
ignore.match("art/hero.psd", false); // true, given `*.psd`ignore.match("art", true); // falsepub const empty: Ignore = .{ .gpa = undefined, .rules = &.{}, .text = &.{} };An Ignore with no rules. match always returns false. Safe to deinit,
which is a no-op.
This is what load returns when no ignore file exists, so callers do not need a
separate “no rules” path.
deinit
Section titled “deinit”pub fn deinit(self: *Ignore) voidFrees the rules and the pattern text. A no-op on empty.
pub const Rule = struct { /// Leading and trailing `/` already stripped. pattern: []const u8, /// The rule un-ignores anything it matches. negate: bool, /// Written with a trailing `/`, so it matches directories only. dir_only: bool, /// Matched against the whole path rather than the basename. anchored: bool,};rules is public, so you can inspect what a file parsed to:
for (ignore.rules) |rule| { std.debug.print("{s}{s}{s}\n", .{ if (rule.negate) "!" else "", rule.pattern, if (rule.dir_only) "/" else "", });}Patterns borrow from the Ignore’s internal text buffer and are invalidated by
deinit.
max_rules
Section titled “max_rules”pub const max_rules: usize = 1024;Enough for any sane ignore file, and a bound on how much work one path can cost
to match. Exceeding it is error.TooManyRules.
Directory pruning is the caller’s job
Section titled “Directory pruning is the caller’s job”Ignore answers one question about one path. It does not walk anything.
The pruning behaviour - an ignored directory is never descended into, so a
negation cannot resurrect a file underneath it - comes from how pack uses the
matcher, not from the matcher itself:
while (try walker.next(io)) |dir_entry| { const is_dir = dir_entry.kind == .directory; if (ignore.match(archive_path, is_dir)) continue; // never entered if (is_dir) { try walker.enter(io, dir_entry); continue; } // ...pack the file...}If you use Ignore in your own walk and want the same semantics, prune the same
way: check the directory before entering it, and skip entering when it matches.
Filtering afterwards gives different - and slower - results.
Differences from .gitignore
Section titled “Differences from .gitignore”Character classes ([a-z]) are not supported, and only one ignore file is read
per archive - nested ones in subdirectories are ignored. See
.zpackignore for the full comparison.