Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions st3/sublime_lib/_util/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,45 @@


GLOB_RE = re.compile(r"""(?x)(
(?:^|/) \*\* (?:$|/)
| (?<!\*)\*(?!\*)
\*
| \?
| \[ .*? \]
)""")


@lru_cache()
def get_glob_matcher(pattern: str) -> 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)
13 changes: 13 additions & 0 deletions tests/test_glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def test_recursive(self):
self._test_matches(
'Foo/**',
[
'Packages/Foo/',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we match this at all?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because a UNIX glob would and I don't think we need to deviate. It's a corner case that probably won't come up very often, but if it does then I think handling it like a UNIX glob is the least surprising thing to do.

'Packages/Foo/bar',
'Packages/Foo/bar/baz',
],
Expand Down Expand Up @@ -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?',
Expand Down