mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 20:17:42 +00:00
Make GlobMatcher with cached regex patterns the only glob matching mechanism
This commit is contained in:
1 parent
7033e90306
commit
954d8554fd
5 files changed
+30
-39
No files matched your search
+1
-1
@@ -6,7 +6,7 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Add Grok Build support (context `grok`, setup CLI, hooks)
|
||||
- The `languages` key in project configurations was changed to `language_servers` to better reflect
|
||||
the actual semantics (configurations are automatically migrated)
|
||||
- Fix: `glob_match` bare `*` and `?` in non-`**` patterns matched across `/`, contradicting its documented behavior #1732
|
||||
- Fix: glob matching bare `*` and `?` in non-`**` patterns matched across `/`, contradicting documented behaviour #1732
|
||||
|
||||
* Language Servers:
|
||||
- Allow language server priorities to be configured in `serena_config.yml` (for auto-detection during
|
||||
|
||||
@@ -33,7 +33,7 @@ from serena.constants import (
|
||||
SERENA_MANAGED_DIR_NAME,
|
||||
)
|
||||
from serena.util.inspection import compute_language_server_support_composition
|
||||
from serena.util.text_utils import glob_match
|
||||
from serena.util.text_utils import GlobMatcher
|
||||
from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_yaml_comments
|
||||
from solidlsp.ls_config import LanguageServerId
|
||||
|
||||
@@ -1437,7 +1437,7 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
|
||||
"""
|
||||
project_root_str = str(project_root)
|
||||
for pattern in self.trusted_project_path_patterns:
|
||||
if glob_match(pattern, project_root_str):
|
||||
if GlobMatcher(pattern).matches(project_root_str):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -16,10 +16,9 @@ from serena.tools import SUCCESS_RESULT, EditedFileContext, EditingToolWithDiagn
|
||||
from serena.util.file_system import scan_directory
|
||||
from serena.util.text_utils import (
|
||||
ContentReplacer,
|
||||
GlobMatcher,
|
||||
MultiFileContentReplacer,
|
||||
ReplacementOccurrence,
|
||||
expand_braces,
|
||||
glob_match,
|
||||
)
|
||||
from solidlsp.ls_utils import TextUtils
|
||||
|
||||
@@ -350,13 +349,13 @@ class ReplaceInFilesTool(EditingToolWithDiagnostics):
|
||||
is_ignored_file=self.project.is_ignored_path,
|
||||
relative_to=self.get_project_root(),
|
||||
)
|
||||
include_patterns = expand_braces(paths_include_glob.strip()) if paths_include_glob.strip() else None
|
||||
exclude_patterns = expand_braces(paths_exclude_glob.strip()) if paths_exclude_glob.strip() else None
|
||||
include_glob_matcher = GlobMatcher(paths_include_glob.strip()) if paths_include_glob.strip() else None
|
||||
exclude_glob_matcher = GlobMatcher(paths_exclude_glob.strip()) if paths_exclude_glob.strip() else None
|
||||
files: list[tuple[str, str]] = []
|
||||
for path in sorted(rel_paths):
|
||||
if include_patterns and not any(glob_match(p, path) for p in include_patterns):
|
||||
if include_glob_matcher and not include_glob_matcher.matches(path):
|
||||
continue
|
||||
if exclude_patterns and any(glob_match(p, path) for p in exclude_patterns):
|
||||
if exclude_glob_matcher and exclude_glob_matcher.matches(path):
|
||||
continue
|
||||
try:
|
||||
files.append((path, self.project.read_file(path)))
|
||||
|
||||
@@ -4,11 +4,11 @@ import re
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum
|
||||
from functools import lru_cache
|
||||
from typing import Any, Literal, Self
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from joblib import Parallel, delayed
|
||||
from sensai.util.string import ToStringMixin
|
||||
|
||||
from serena.util.file_proxy import FileCollection, FileProxy
|
||||
from solidlsp.ls_utils import TextUtils
|
||||
@@ -185,7 +185,7 @@ def search_text(
|
||||
return matches
|
||||
|
||||
|
||||
def expand_braces(pattern: str) -> list[str]:
|
||||
def _expand_braces(pattern: str) -> list[str]:
|
||||
"""
|
||||
Expands brace patterns in a glob string.
|
||||
For example, "**/*.{js,jsx,ts,tsx}" becomes ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"].
|
||||
@@ -212,7 +212,6 @@ def expand_braces(pattern: str) -> list[str]:
|
||||
return patterns
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def _translate_glob_to_regex(pattern: str) -> str:
|
||||
res = []
|
||||
i = 0
|
||||
@@ -263,9 +262,9 @@ def _translate_glob_to_regex(pattern: str) -> str:
|
||||
return f"(?s:{inner})\\Z"
|
||||
|
||||
|
||||
def glob_match(pattern: str, path: str) -> bool:
|
||||
class GlobMatcher(ToStringMixin):
|
||||
"""
|
||||
Match a file path against a glob pattern.
|
||||
Supports matching a file path against a glob pattern.
|
||||
|
||||
Supports standard glob patterns:
|
||||
- * matches any number of characters except /
|
||||
@@ -275,28 +274,23 @@ def glob_match(pattern: str, path: str) -> bool:
|
||||
|
||||
Supports brace expansion:
|
||||
- {a,b,c} expands to multiple patterns (including nesting)
|
||||
|
||||
Unsupported patterns:
|
||||
- Bash extended glob features are unavailable in Python's fnmatch
|
||||
- Extended globs like !(), ?(), +(), *(), @() are not supported
|
||||
|
||||
:param pattern: Glob pattern (e.g., 'src/**/*.py', '**agent.py')
|
||||
:param path: File path to match against
|
||||
:return: True if path matches pattern
|
||||
"""
|
||||
# normalise backslashes to forward slashes in both path and pattern
|
||||
pattern = pattern.replace("\\", "/")
|
||||
path = path.replace("\\", "/")
|
||||
|
||||
return bool(re.match(_translate_glob_to_regex(pattern), path))
|
||||
|
||||
|
||||
class GlobMatcher:
|
||||
def __init__(self, expr: str):
|
||||
self._patterns = expand_braces(expr)
|
||||
"""
|
||||
:param expr: a glob pattern which may make use of brace expansion (e.g., "src/**/*.{js,jsx,ts,tsx}")
|
||||
"""
|
||||
expr = expr.replace("\\", "/") # normalise backslashes to forward slashes
|
||||
self._glob_expr = expr
|
||||
self._glob_patterns = _expand_braces(expr)
|
||||
self._regex_patterns = [re.compile(_translate_glob_to_regex(p)) for p in self._glob_patterns]
|
||||
|
||||
def _tostring_includes(self) -> list[str]:
|
||||
return ["_glob_expr"]
|
||||
|
||||
def matches(self, path: str) -> bool:
|
||||
return any(glob_match(p, path) for p in self._patterns)
|
||||
path = path.replace("\\", "/") # normalise backslashes to forward slashes
|
||||
return any(regex.match(path) for regex in self._regex_patterns)
|
||||
|
||||
|
||||
def search_files(
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections.abc import Callable
|
||||
import pytest
|
||||
|
||||
from serena.util.file_proxy import FileCollection, FileProxy
|
||||
from serena.util.text_utils import LineType, MultiFileContentReplacer, search_files, search_text
|
||||
from serena.util.text_utils import GlobMatcher, LineType, MultiFileContentReplacer, search_files, search_text
|
||||
|
||||
|
||||
class TestSearchText:
|
||||
@@ -555,9 +555,7 @@ class TestGlobMatch:
|
||||
)
|
||||
def test_glob_match(self, pattern, path, expected):
|
||||
"""Test glob_match function with various patterns."""
|
||||
from serena.util.text_utils import glob_match
|
||||
|
||||
assert glob_match(pattern, path) == expected
|
||||
assert GlobMatcher(pattern).matches(path) == expected
|
||||
|
||||
|
||||
class TestExpandBraces:
|
||||
@@ -582,9 +580,9 @@ class TestExpandBraces:
|
||||
)
|
||||
def test_expand_braces(self, pattern, expected):
|
||||
"""Test brace expansion for glob patterns."""
|
||||
from serena.util.text_utils import expand_braces
|
||||
from serena.util.text_utils import _expand_braces
|
||||
|
||||
assert sorted(expand_braces(pattern)) == sorted(expected)
|
||||
assert sorted(_expand_braces(pattern)) == sorted(expected)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pattern",
|
||||
@@ -596,10 +594,10 @@ class TestExpandBraces:
|
||||
)
|
||||
def test_expand_braces_rejects_malformed_braces(self, pattern):
|
||||
"""Malformed brace globs should fail instead of looping forever."""
|
||||
from serena.util.text_utils import expand_braces
|
||||
from serena.util.text_utils import _expand_braces
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid glob brace expression"):
|
||||
expand_braces(pattern)
|
||||
_expand_braces(pattern)
|
||||
|
||||
|
||||
class TestMultiFileContentReplacer:
|
||||
|
||||
Reference in new issue
Block a user