NAME

App::FileSync::Ignore - .gitignore-style path exclusion

SYNOPSIS

my $ignore = App::FileSync::Ignore->new(patterns => [ '*.tmp', '!keep.tmp', 'build/' ]);
print "skip\n" if $ignore->match('src/a.tmp');

DESCRIPTION

Patterns follow .gitignore: * matches within one component, **/ matches any number of directories, ? one character, a leading ! re-includes, a leading / anchors at the root, and a pattern without a slash matches at any depth. Later patterns win. A path inside an ignored directory is ignored.

METHODS

new

Purpose: compile a list of patterns.

Args: patterns (arrayref of strings; undefined entries, blanks and comments are skipped), case_insensitive (boolean).

Returns: an App::FileSync::Ignore object.

Side Effects: none. The caller's list is not modified.

Usage:

my $ignore = App::FileSync::Ignore->new(patterns => $config->{ignore});

EXAMPLE

my $ignore = App::FileSync::Ignore->new(
	patterns         => [ '*.bak', '# editor files', '*~' ],
	case_insensitive => 1,
);

API SPECIFICATION

INPUT

{
	patterns         => { type => 'arrayref', optional => 1 },
	case_insensitive => { type => 'boolean', optional => 1 },
}

OUTPUT

{ type => 'object', isa => 'App::FileSync::Ignore' }

MESSAGES

Key            Meaning                     Resolution
args.invalid   patterns is not a list      pass an arrayref

FORMAL SPECIFICATION

new(ps, ci) = ⟨compiled ↦ ⟨(neg(p), re(p, ci)) | p ← ps, ¬ comment(p)⟩⟩
ps' = ps

match

Purpose: decide whether a path is excluded.

Args: $path, relative and /-separated.

Returns: 1 when the path, or any directory above it, is ignored; otherwise 0.

Side Effects: none.

Usage:

next if $ignore->match($relative_path);

EXAMPLE

my $ignore = App::FileSync::Ignore->new(patterns => [ 'build' ]);
print $ignore->match('build/out/a.o'), "\n";	# 1

API SPECIFICATION

INPUT

[
	{ name => 'self', type => 'object' },
	{ name => 'path', type => 'string', optional => 1 },
]

OUTPUT

{ type => 'integer', min => 0, max => 1 }

MESSAGES

None. Called once per path, so arguments are checked by hand.

FORMAL SPECIFICATION

ignored(c) ⇔ last ⟨ ¬neg(p) | p ← compiled, c ∈ re(p) ⟩ = true
match(path) ⇔ ∃ a : {path} ∪ ancestors(path) • ignored(a)

PSEUDOCODE

candidates := path, then each directory above it
for each candidate:
	state := not ignored
	for each pattern in order: if it matches, state := not negated
	if state is ignored: return 1
return 0

LIMITATIONS

  • Character classes ([abc]) and backslash escapes are taken literally. A trailing / is dropped, so build/ matches build at any depth; as match has no type information it matches a file of that name too.

  • Matching is per path and linear in the number of patterns. Combining them into one alternation would be faster but would lose "later patterns win".

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms. If you use it, please let me know.