Skip to content

Exclude grammar (exclude)

The --exclude/-X match grammar shared by install, scan and dbdump. See the guide for the syntax and how each command prunes once a statement matches the input.

pkgforge.exclude.PathMatchStmt

Bases: Namespace

One parsed --exclude statement: negate, tests (a list of :class:PathTest) and pattern. :meth:parse builds one from [!](?name:arg)*<glob>; :meth:match evaluates it against a path.

anchored property

True when pattern is rooted (a leading /, or a Windows drive): it is matched from the root of the input rather than at any depth.

negate instance-attribute

pattern instance-attribute

tests instance-attribute

match(path, fileentry)

Evaluate this statement: True/False decide, None defers.

A statement that does not apply returns None so the caller keeps evaluating later statements — never False, which would veto them. Requires an already-built fileentry -- :class:PathMatch is the one that builds it lazily, only for a statement whose glob already matched and that actually has inline tests.

Source code in src/pkgforge/exclude.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def match(
    self, path: typing.Union[str, Path], fileentry: FileEntry
) -> typing.Optional[bool]:
    """Evaluate this statement: ``True``/``False`` decide, ``None`` defers.

    A statement that does not apply returns ``None`` so the caller keeps
    evaluating later statements — never ``False``, which would veto them.
    Requires an already-built ``fileentry`` -- :class:`PathMatch` is the
    one that builds it lazily, only for a statement whose glob already
    matched and that actually has inline tests.
    """
    text = path if isinstance(path, str) else PurePath(path).as_posix()
    if (not self.pattern or self._pattern_matches(text)) and self._tests_pass(
        text, fileentry
    ):
        return not self.negate
    return None

parse(pattern) classmethod

Source code in src/pkgforge/exclude.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
@classmethod
def parse(cls, pattern: str) -> PathMatchStmt:
    original = pattern
    tests: typing.List[PathTest] = []

    if pattern.startswith("!"):
        negate = True
        pattern = pattern[1:]
    else:
        negate = False

    while True:
        test = _FILTER_TEST_RE.match(pattern)
        if not test:
            break
        name = test[1]
        arg = test[2]
        if name.startswith("!"):
            name = name[1:]
            inversed = True
        else:
            inversed = False
        tests.append(_make_test(name, arg, inversed))
        pattern = pattern[test.span()[1] :]

    if pattern.startswith("(?"):
        # A malformed "(?..." would otherwise become a literal glob that
        # excludes nothing; a literal leading "(" is written "[(]".
        raise ExcludeSyntaxError(
            f"unterminated or malformed inline test in {original!r}"
        )

    return cls(negate=negate, tests=tests, pattern=pattern)

pkgforge.exclude.PathMatch(stmts, root=None)

Bases: List[PathMatchStmt]

An ordered set of :class:PathMatchStmt, matched against the names of the input a command reads, the way tar --exclude matches member names. :meth:match evaluates each statement in order and returns the first non-None result.

With a root, a path is matched as / plus its root-relative POSIX text (/ for the root itself), so an absolute pattern is relative to the root: install roots at each source (a directory, or an archive's extraction directory) and scan at PATH. With no root (dbdump) a path is matched as its own POSIX text, which for a DB key is already /-rooted. A relative pattern matches trailing segments at any depth either way. Statements are stored exactly as parsed.

Source code in src/pkgforge/exclude.py
305
306
307
308
309
310
311
312
def __init__(
    self,
    stmts: typing.Iterable[PathMatchStmt],
    root: typing.Optional[Path] = None,
) -> None:
    # Absolutized, never resolved (no symlink following).
    self.root = Path(root).absolute() if root is not None else None
    super().__init__(stmts)

root = Path(root).absolute() if root is not None else None instance-attribute

match(path, entry=None, _default=None, **overrides)

Source code in src/pkgforge/exclude.py
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def match(
    self,
    path: typing.Union[str, Path],
    entry: typing.Optional[FileEntry] = None,
    _default: typing.Optional[bool] = None,
    **overrides: typing.Any,
) -> typing.Optional[bool]:
    if not self:
        return True

    candidate = self._candidate(path)

    # Built at most once, only after a glob matched a statement with
    # tests, so a glob-only "-X '*.fifo'" never lstats a FIFO. Overrides
    # go onto a copy; an empty `entry` {} still counts as supplied.
    fileentry: typing.Optional[FileEntry] = None

    for stmt in self:
        if stmt.pattern and not stmt._pattern_matches(candidate):
            continue

        if stmt.tests:
            if fileentry is None:
                base = entry if entry is not None else entry_from_path(Path(path))
                fileentry = typing.cast(
                    FileEntry, {**base, **overrides} if overrides else base
                )
            if not stmt._tests_pass(candidate, fileentry):
                continue

        return not stmt.negate
    return _default

pkgforge.exclude.PathTest

Bases: Protocol

A single inline (?name:arg) test: __call__(path, entry) -> bool.

path is typed loosely (no test actually reads it, only entry): once a statement is matched against a root, path may be a plain root-relative string rather than the real filesystem Path.

__call__(path, entry)

Source code in src/pkgforge/exclude.py
187
def __call__(self, path: typing.Any, entry: FileEntry) -> bool: ...

pkgforge.exceptions.ExcludeSyntaxError

Bases: UsageError

A malformed --exclude statement: an unknown inline test name, a bad (?type:...)/(?meta:...) argument, or an unterminated (?... that never closed.

Subclasses :class:~pkgforge.exceptions.UsageError, so pkgforge.cli.main()'s error boundary maps it to a clean one-line message and exit 2; it is also a ValueError.