Skip to content

Commit 8ca523c

Browse files
committed
feat(version-scanner): fix root anchoring in ignores, parametrize tests, and update docs
1 parent 055975c commit 8ca523c

3 files changed

Lines changed: 59 additions & 29 deletions

File tree

scripts/version_scanner/README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,32 @@ pip install -r scripts/version_scanner/requirements.txt
4545

4646
The scanner uses a YAML configuration file (`regex_config.yaml`) to define rules and regex patterns.
4747

48-
## Ignoring Directories
48+
## Matrix File Format
4949

50-
You can create a `.scannerignore` file in the directory you are scanning (usually the repo root) to list directories to skip, one per line.
50+
When using `--matrix-file`, you must provide a YAML file specifying dependencies and versions.
5151

52-
## Known Issues & Future Investigations
53-
- **Binary Ignores in `.scannerignore`**: Recursive wildcard ignores (e.g., `*.jpg`) currently do not effectively ignore deeply nested binary files. The scanner logic should be investigated to support robust globbing or full-path suffix matching.
52+
### Example
53+
```yaml
54+
python:
55+
- "3.10"
56+
- "3.11"
57+
protobuf: "4.25.8"
58+
```
59+
60+
> [!IMPORTANT]
61+
> **Versions must be specified as quoted strings** (e.g., `"3.10"`, not `3.10`). This prevents YAML parsers from converting them to floats (which would truncate `3.10` to `3.1`).
62+
63+
## Ignoring Directories and Files
64+
65+
In order to ignore files OR entire directories, you can add ignore patterns to the `.scannerignore` file located in the same directory as the script (`scripts/version_scanner/.scannerignore`). Ignore patterns should be added one per line.
66+
67+
### Features
68+
- **Case-insensitive**: All patterns are matched case-insensitively.
69+
- **Globbing**: Supports standard shell globbing patterns (e.g., `*.jpg`, `test_*`).
70+
- **Subpaths**: You can specify subpaths (e.g., `packages/pkg_a/.nox`).
71+
- **Root Anchoring**: Patterns starting with a slash `/` are anchored to the root of the scan (e.g., `/packages` ignores the `packages` directory at root, but not `some/other/packages`).
72+
73+
---
5474

5575
---
5676

scripts/version_scanner/tests/unit/test_version_scanner.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -388,29 +388,25 @@ def test_scan_repository_wildcard_ignores(tmp_path):
388388
assert not match["file_path"].endswith("test.jpg")
389389

390390

391-
def test__should_ignore():
392-
from version_scanner import _should_ignore
393-
394-
ignore_patterns = [
395-
".git",
396-
"*.jpg",
397-
"packages/pkg_a/.nox",
398-
"*.egg-info"
391+
DEFAULT_IGNORE_PATTERNS = [".git", "*.jpg", "packages/pkg_a/.nox", "*.egg-info"]
392+
393+
@pytest.mark.parametrize(
394+
"rel_path, name, ignore_patterns, expected",
395+
[
396+
pytest.param(".git", ".git", DEFAULT_IGNORE_PATTERNS, True, id="exact_match"),
397+
pytest.param(".GIT", ".GIT", DEFAULT_IGNORE_PATTERNS, True, id="case_insensitive_match"),
398+
pytest.param("some/path/image.jpg", "image.jpg", DEFAULT_IGNORE_PATTERNS, True, id="wildcard_subpath_match"),
399+
pytest.param("image.JPG", "image.JPG", DEFAULT_IGNORE_PATTERNS, True, id="wildcard_case_insensitive_match"),
400+
pytest.param("packages/pkg_a/.nox", ".nox", DEFAULT_IGNORE_PATTERNS, True, id="subpath_exact_match"),
401+
pytest.param("google_cloud_pubsub.egg-info", "google_cloud_pubsub.egg-info", DEFAULT_IGNORE_PATTERNS, True, id="wildcard_directory_match"),
402+
pytest.param("setup.py", "setup.py", DEFAULT_IGNORE_PATTERNS, False, id="no_match"),
403+
pytest.param("packages", "packages", ["/packages"], True, id="anchored_root_match"),
404+
pytest.param("some/other/packages", "packages", ["/packages"], False, id="anchored_root_nested_no_match"),
399405
]
400-
401-
# Exact match
402-
assert _should_ignore(".git", ".git", ignore_patterns) is True
403-
# Case insensitivity
404-
assert _should_ignore(".GIT", ".GIT", ignore_patterns) is True
405-
# Wildcard match
406-
assert _should_ignore("some/path/image.jpg", "image.jpg", ignore_patterns) is True
407-
assert _should_ignore("image.JPG", "image.JPG", ignore_patterns) is True
408-
# Subpath match
409-
assert _should_ignore("packages/pkg_a/.nox", ".nox", ignore_patterns) is True
410-
# Wildcard directory match
411-
assert _should_ignore("google_cloud_pubsub.egg-info", "google_cloud_pubsub.egg-info", ignore_patterns) is True
412-
# Negative match
413-
assert _should_ignore("setup.py", "setup.py", ignore_patterns) is False
406+
)
407+
def test__should_ignore(rel_path, name, ignore_patterns, expected):
408+
from version_scanner import _should_ignore
409+
assert _should_ignore(rel_path, name, ignore_patterns) is expected
414410

415411

416412
def test_load_ignore_file(tmp_path):

scripts/version_scanner/version_scanner.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,19 @@ def read_package_file(file_path: str) -> List[str]:
500500

501501

502502
def _should_ignore(rel_path: str, name: str, ignore_patterns: List[str]) -> bool:
503-
"""Check if a file or directory matches any of the ignore patterns."""
503+
"""Check if a file or directory matches any of the ignore patterns.
504+
505+
Directories and files can be ignored by providing an ignore pattern in the
506+
.scannerignore file.
507+
508+
Args:
509+
rel_path: The relative path of the file or directory from the scan root.
510+
name: The name of the file or directory (basename).
511+
ignore_patterns: A list of ignore patterns (glob-like or subpaths).
512+
513+
Returns:
514+
True if the file or directory should be ignored, False otherwise.
515+
"""
504516
if not ignore_patterns:
505517
return False
506518
name_lower = name.lower()
@@ -511,10 +523,12 @@ def _should_ignore(rel_path: str, name: str, ignore_patterns: List[str]) -> bool
511523
if '/' in pattern:
512524
if pattern_lower.startswith('/'):
513525
p = pattern_lower[1:]
526+
if fnmatch.fnmatchcase(rel_path_norm, p):
527+
return True
514528
else:
515529
p = pattern_lower
516-
if fnmatch.fnmatchcase(rel_path_norm, p) or fnmatch.fnmatchcase(rel_path_norm, f"*/{p}"):
517-
return True
530+
if fnmatch.fnmatchcase(rel_path_norm, p) or fnmatch.fnmatchcase(rel_path_norm, f"*/{p}"):
531+
return True
518532
else:
519533
if fnmatch.fnmatchcase(name_lower, pattern_lower):
520534
return True

0 commit comments

Comments
 (0)