diff --git a/st3/sublime_lib/_util/glob.py b/st3/sublime_lib/_util/glob.py index 4dd6259..d6e178a 100644 --- a/st3/sublime_lib/_util/glob.py +++ b/st3/sublime_lib/_util/glob.py @@ -7,8 +7,7 @@ GLOB_RE = re.compile(r"""(?x)( - (?:^|/) \*\* (?:$|/) - | (? Callable[[str], bool]: - s = r'\A' if pattern.startswith('/'): pattern = pattern[1:] else: pattern = '**/' + pattern - for part in GLOB_RE.split(pattern): - if part == '': + expr_string = r'\A' + for component in pattern.split('/'): + if component == '': pass - elif part.strip('/') == '**': - s += r'(?:^|/)(?:(?:.*)(?:$|/))?' - elif part == '*': - s += r'(?:[^/]*)' - elif part == '?': - s += r'(?:[^/])' - elif part[0] == '[': - s += part - elif '**' in part: + elif component == '*': + # Component must not be empty. + expr_string += r'(?:[^/])+' + '/' + elif component == '**': + expr_string += r'(?:.*(?:\Z|/))?' + elif '**' in component: raise ValueError("Invalid pattern: '**' can only be an entire path component") else: - s += re.escape(part) - - expr = re.compile(s + r'\Z') - return lambda s: (expr.search(s) is not None) + for part in GLOB_RE.split(component): + if part == '': + pass + elif part == '*': + expr_string += r'(?:[^/])*' + elif part == '?': + expr_string += r'(?:[^/])' + elif part[0] == '[': + expr_string += part + else: + expr_string += re.escape(part) + expr_string += '/' + + expr_string = expr_string.rstrip('/') + r'\Z' + expr = re.compile(expr_string) + + return lambda path: (expr.search(path) is not None) diff --git a/tests/test_glob.py b/tests/test_glob.py index c4cb396..904ae14 100644 --- a/tests/test_glob.py +++ b/tests/test_glob.py @@ -115,6 +115,7 @@ def test_recursive(self): self._test_matches( 'Foo/**', [ + 'Packages/Foo/', 'Packages/Foo/bar', 'Packages/Foo/bar/baz', ], @@ -154,6 +155,18 @@ def test_recursive(self): [] ) + self._test_matches( + 'Foo/**/*', + [ + 'Foo/bar', + 'Foo/bar/baz', + ], + [ + 'Foo', + 'Foo/', + ] + ) + def test_placeholder(self): self._test_matches( '/Packages/Foo/ba?',