blob: 5a8297fb73380021eaf2423d42824f50922cde76 [file] [log] [blame]
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Path filter module to support --include, --exclude flags in commands."""
import fnmatch
import re
from typing import Any, List, NamedTuple, Optional, TYPE_CHECKING, Union
if TYPE_CHECKING:
import os
class _Rule(NamedTuple):
"""A rule for PathFilter.
When `regex` matches, the `includes` of the rule is applied.
"""
regex: Any
includes: bool
def match(self, path: Union[str, "os.PathLike[str]"]) -> Optional[bool]:
"""Returns whether path should be included, None if indecisive."""
return self.includes if self.regex.fullmatch(str(path)) else None
def exclude(pattern: str) -> _Rule:
"""Returns an exclusion rule for the pattern."""
return _Rule(re.compile(fnmatch.translate(pattern)), includes=False)
def include(pattern: str) -> _Rule:
"""Returns an inclusion rule for the pattern."""
return _Rule(re.compile(fnmatch.translate(pattern)), includes=True)
class PathFilter(NamedTuple):
"""A path pattern filter.
- the first rule to match is used
- any unmatched paths will default to "include"
"""
rules: List[_Rule]
def match(self, path: Union[str, "os.PathLike[str]"]) -> bool:
"""Returns whether the given path name is included"""
for rule in self.rules:
result = rule.match(path)
if result is not None:
return result
# If no rules matched, include by default.
return True
def filter(
self, paths: List[Union[str, "os.PathLike[str]"]]
) -> List[Union[str, "os.PathLike[str]"]]:
"""Returns a subset of names that should be included"""
return [x for x in paths if self.match(x)]