return re.match(re_pattern, path)
+class Pattern(object):
+
+ def __init__(self, pattern):
+ self.pattern = pattern
+ if pattern[0:1] == b'!':
+ self.is_exclude = False
+ pattern = pattern[1:]
+ else:
+ if pattern[0:1] == b'\\':
+ pattern = pattern[1:]
+ self.is_exclude = True
+ self._re = re.compile(translate(pattern))
+
+ def __bytes__(self):
+ return self.pattern
+
+ def __eq__(self, other):
+ return (type(self) == type(other) and self.pattern == other.pattern)
+
+ def match(self, path):
+ return self._re.match(path)
+
+
class IgnoreFilter(object):
def __init__(self, patterns):
def append_pattern(self, pattern):
"""Add a pattern to the set."""
- pattern_str = pattern
- if pattern[0:1] == b'!':
- is_exclude = False
- pattern = pattern[1:]
- else:
- if pattern[0:1] == b'\\':
- pattern = pattern[1:]
- is_exclude = True
- self._patterns.append(
- (is_exclude, re.compile(translate(pattern)), pattern_str))
+ self._patterns.append(Pattern(pattern))
+
+ def find_matching(self, path):
+ """Yield all matching patterns for path.
+
+ :param path: Path to match
+ :return: Iterator over iterators
+ """
+ if not isinstance(path, bytes):
+ path = path.encode(sys.getfilesystemencoding())
+ for pattern in self._patterns:
+ if pattern.match(path):
+ yield pattern
def is_ignored(self, path):
"""Check whether a path is ignored.
:return: status is None if file is not mentioned, True if it is
included, False if it is explicitly excluded.
"""
- if not isinstance(path, bytes):
- path = path.encode(sys.getfilesystemencoding())
status = None
- matched = None
- for (is_exclude, compiled, pattern_str) in self._patterns:
- if compiled.match(path):
- status = is_exclude
+ for pattern in self.find_matching(path):
+ status = pattern.is_exclude
return status
@classmethod
self._path_filters[path] = None
return self._path_filters[path]
- def is_ignored(self, path):
- """Check whether a path is explicitly included or excluded in ignores.
+ def find_matching(self, path):
+ """Find matching patterns for path.
+
+ Stops after the first ignore file with matches.
:param path: Path to check
- :return: None if the file is not mentioned, True if it is included,
- False if it is explicitly excluded.
+ :return: Iterator over Pattern instances
"""
if os.path.isabs(path):
path = os.path.relpath(path, self._top_path)
dirname = '/'.join(parts[:i])
for s, f in filters:
relpath = '/'.join(parts[s:i])
- status = f.is_ignored(relpath)
- if status is not None:
- return status
+ matches = list(f.find_matching(relpath))
+ if matches:
+ return iter(matches)
ignore_filter = self._load_path(dirname)
if ignore_filter is not None:
filters.insert(0, (i, ignore_filter))
+ return iter([])
+
+ def is_ignored(self, path):
+ """Check whether a path is explicitly included or excluded in ignores.
+
+ :param path: Path to check
+ :return: None if the file is not mentioned, True if it is included,
+ False if it is explicitly excluded.
+ """
+ matches = list(self.find_matching(path))
+ if matches:
+ return matches[-1].is_exclude
return None
@classmethod
IgnoreFilter,
IgnoreFilterManager,
IgnoreFilterStack,
+ Pattern,
match_pattern,
read_ignore_patterns,
translate,
filter = IgnoreFilter([b'a.c', b'b.c'])
self.assertTrue(filter.is_ignored(b'a.c'))
self.assertIs(None, filter.is_ignored(b'c.c'))
+ self.assertEqual(
+ [Pattern(b'a.c')],
+ list(filter.find_matching(b'a.c')))
+ self.assertEqual(
+ [],
+ list(filter.find_matching(b'c.c')))
def test_excluded(self):
filter = IgnoreFilter([b'a.c', b'b.c', b'!c.c'])
self.assertFalse(filter.is_ignored(b'c.c'))
self.assertIs(None, filter.is_ignored(b'd.c'))
+ self.assertEqual(
+ [Pattern(b'!c.c')],
+ list(filter.find_matching(b'c.c')))
+ self.assertEqual([], list(filter.find_matching(b'd.c')))
def test_include_exclude_include(self):
filter = IgnoreFilter([b'a.c', b'!a.c', b'a.c'])
self.assertTrue(filter.is_ignored(b'a.c'))
+ self.assertEqual(
+ [Pattern(b'a.c'), Pattern(b'!a.c'), Pattern(b'a.c')],
+ list(filter.find_matching(b'a.c')))
class IgnoreFilterStackTests(TestCase):