mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
Unify glob matching to always use regex translation
This commit is contained in:
1 parent
33125287dc
commit
7033e90306
2 files changed
+36
-35
No files matched your search
@@ -1,4 +1,3 @@
|
||||
import fnmatch
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
@@ -214,7 +213,7 @@ def expand_braces(pattern: str) -> list[str]:
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def _translate_glob_no_double_star(pattern: str) -> str:
|
||||
def _translate_glob_to_regex(pattern: str) -> str:
|
||||
res = []
|
||||
i = 0
|
||||
n = len(pattern)
|
||||
@@ -222,8 +221,22 @@ def _translate_glob_no_double_star(pattern: str) -> str:
|
||||
c = pattern[i]
|
||||
i += 1
|
||||
if c == "*":
|
||||
if not res or res[-1] != "[^/]*":
|
||||
res.append("[^/]*")
|
||||
if i < n and pattern[i] == "*": # **
|
||||
i += 1
|
||||
if i < n and pattern[i] == "/": # **/
|
||||
i += 1
|
||||
if res and res[-1] == "/": # /**/
|
||||
res = res[:-1]
|
||||
res.append("(?:/|/.*/)")
|
||||
elif res: # c**/ (with c != '/')
|
||||
res.append(".*/")
|
||||
else: # **/ at the start of the pattern
|
||||
res.append("(?:.*/)?")
|
||||
else: # c**d (with c, d != '/')
|
||||
res.append(".*")
|
||||
else: # single *
|
||||
if not res or res[-1] != "[^/]*":
|
||||
res.append("[^/]*")
|
||||
elif c == "?":
|
||||
res.append("[^/]")
|
||||
elif c == "[":
|
||||
@@ -271,36 +284,11 @@ def glob_match(pattern: str, path: str) -> bool:
|
||||
:param path: File path to match against
|
||||
:return: True if path matches pattern
|
||||
"""
|
||||
pattern = pattern.replace("\\", "/") # Normalize backslashes to forward slashes
|
||||
path = path.replace("\\", "/") # Normalize path backslashes to forward slashes
|
||||
# normalise backslashes to forward slashes in both path and pattern
|
||||
pattern = pattern.replace("\\", "/")
|
||||
path = path.replace("\\", "/")
|
||||
|
||||
# Handle ** patterns that should match zero or more directories
|
||||
if "**" in pattern:
|
||||
# Method 1: Standard fnmatch (matches one or more directories)
|
||||
regex1 = fnmatch.translate(pattern)
|
||||
if re.match(regex1, path):
|
||||
return True
|
||||
|
||||
# Method 2: Handle zero-directory case by removing /** entirely
|
||||
# Convert "src/**/test.py" to "src/test.py"
|
||||
if "/**/" in pattern:
|
||||
zero_dir_pattern = pattern.replace("/**/", "/")
|
||||
regex2 = fnmatch.translate(zero_dir_pattern)
|
||||
if re.match(regex2, path):
|
||||
return True
|
||||
|
||||
# Method 3: Handle leading ** case by removing **/
|
||||
# Convert "**/test.py" to "test.py"
|
||||
if pattern.startswith("**/"):
|
||||
zero_dir_pattern = pattern[3:] # Remove "**/"
|
||||
regex3 = fnmatch.translate(zero_dir_pattern)
|
||||
if re.match(regex3, path):
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
# Simple pattern without **, translate bare * and ? to not match /
|
||||
return bool(re.match(_translate_glob_no_double_star(pattern), path))
|
||||
return bool(re.match(_translate_glob_to_regex(pattern), path))
|
||||
|
||||
|
||||
class GlobMatcher:
|
||||
|
||||
@@ -517,17 +517,21 @@ class TestGlobMatch:
|
||||
("src/*.py", "src/sub/file.py", False),
|
||||
# Double asterisk patterns
|
||||
("**agent.py", "agent.py", True),
|
||||
("**/agent.py", "agent.py", True),
|
||||
("**/agent.py", "src/serena/agent.py", True),
|
||||
("src/**/agent.py", "src/agent.py", True),
|
||||
("src/**/agent.py", "src/serena/foo/agent.py", True),
|
||||
("src/s**a/agent.py", "src/serena/agent.py", True),
|
||||
("src/s**a/agent.py", "src/serena/a/agent.py", True),
|
||||
("**agent.py", "src/agent.py", True),
|
||||
("**agent.py", "src/serena/agent.py", True),
|
||||
("**agent.py", "src/serena/process_isolated_agent.py", True),
|
||||
("**agent.py", "agent_test.py", False),
|
||||
# Prefix with double asterisk
|
||||
("src/**agent.py", "src/agent.py", True),
|
||||
("src/**agent.py", "src/serena/agent.py", True),
|
||||
("src/**agent.py", "src/serena/process_isolated_agent.py", True),
|
||||
("src/**agent.py", "other/agent.py", False),
|
||||
("src/**agent.py", "src/agent_test.py", False),
|
||||
# Directory patterns
|
||||
("src/**", "src/file.py", True),
|
||||
("src/**", "src/dir/file.py", True),
|
||||
("src/**", "other/file.py", False),
|
||||
@@ -538,6 +542,15 @@ class TestGlobMatch:
|
||||
# Simple patterns without asterisks
|
||||
("src/file.py", "src/file.py", True),
|
||||
("src/file.py", "src/other.py", False),
|
||||
# Patterns with backslash
|
||||
("src\\file.py", "src/file.py", True),
|
||||
("src\\file.py", "src\\file.py", True),
|
||||
("src\\file.py", "src/other.py", False),
|
||||
# Patterns with []
|
||||
("file[0-9].py", "file1.py", True),
|
||||
("file[0-9].py", "filea.py", False),
|
||||
("file[!0-9].py", "filea.py", True),
|
||||
("file[!0-9].py", "file1.py", False),
|
||||
],
|
||||
)
|
||||
def test_glob_match(self, pattern, path, expected):
|
||||
|
||||
Reference in new issue
Block a user