blob: b560f9c16b9294a1b199b7fba2b827f282797106 [file] [log] [blame] [edit]
# 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 os
import re
from typing import Any, List, NamedTuple, Optional, Union
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]) -> 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):
"""Returns an exclusion rule for the pattern."""
return _Rule(re.compile(fnmatch.translate(pattern)), includes=False)
def include(pattern):
"""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]) -> 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]]
) -> List[Union[str, os.PathLike]]:
"""Returns a subset of names that should be included"""
return [x for x in paths if self.match(x)]