Skip to content

Commit 04f3d2d

Browse files
committed
feat(version-scanner): support globbing and subpath patterns in ignore file
1 parent 8fc7e61 commit 04f3d2d

2 files changed

Lines changed: 84 additions & 5 deletions

File tree

scripts/version_scanner/tests/unit/test_version_scanner.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,53 @@ def test_scan_repository_ignores_version_scanner(tmp_path):
364364
assert len(results) == 0
365365

366366

367+
def test_scan_repository_wildcard_ignores(tmp_path):
368+
# Create files
369+
(tmp_path / "test.jpg").write_text("dummy version 3.7\n")
370+
(tmp_path / "test.py").write_text("python_requires = '>=3.7'\n")
371+
372+
rules = [
373+
{"name": "python_requires_check", "pattern": "python_requires\\s*=\\s*['\"]>=3\\.7['\"]"},
374+
{"name": "explicit_version_string", "pattern": "3\\.7"}
375+
]
376+
377+
from version_scanner import scan_repository
378+
# Without ignore
379+
results = scan_repository(str(tmp_path), rules)
380+
assert len(results) >= 2
381+
382+
# With wildcard ignore for *.jpg
383+
results_ignored = scan_repository(str(tmp_path), rules, ignore_dirs=["*.jpg"])
384+
# test.jpg should be ignored completely
385+
for match in results_ignored:
386+
assert not match["file_path"].endswith("test.jpg")
387+
388+
389+
def test__should_ignore():
390+
from version_scanner import _should_ignore
391+
392+
ignore_patterns = [
393+
".git",
394+
"*.jpg",
395+
"packages/pkg_a/.nox",
396+
"*.egg-info"
397+
]
398+
399+
# Exact match
400+
assert _should_ignore(".git", ".git", ignore_patterns) is True
401+
# Case insensitivity
402+
assert _should_ignore(".GIT", ".GIT", ignore_patterns) is True
403+
# Wildcard match
404+
assert _should_ignore("some/path/image.jpg", "image.jpg", ignore_patterns) is True
405+
assert _should_ignore("image.JPG", "image.JPG", ignore_patterns) is True
406+
# Subpath match
407+
assert _should_ignore("packages/pkg_a/.nox", ".nox", ignore_patterns) is True
408+
# Wildcard directory match
409+
assert _should_ignore("google_cloud_pubsub.egg-info", "google_cloud_pubsub.egg-info", ignore_patterns) is True
410+
# Negative match
411+
assert _should_ignore("setup.py", "setup.py", ignore_patterns) is False
412+
413+
367414
def test_load_ignore_file(tmp_path):
368415
from version_scanner import load_ignore_file
369416

scripts/version_scanner/version_scanner.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import argparse
2121
import csv
2222
import datetime
23+
import fnmatch
2324
import os
2425
import re
2526
import sys
@@ -498,6 +499,28 @@ def read_package_file(file_path: str) -> List[str]:
498499
return packages
499500

500501

502+
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."""
504+
if not ignore_patterns:
505+
return False
506+
name_lower = name.lower()
507+
rel_path_norm = rel_path.replace(os.sep, '/').lower()
508+
509+
for pattern in ignore_patterns:
510+
pattern_lower = pattern.lower()
511+
if '/' in pattern:
512+
if pattern_lower.startswith('/'):
513+
p = pattern_lower[1:]
514+
else:
515+
p = pattern_lower
516+
if fnmatch.fnmatchcase(rel_path_norm, p) or fnmatch.fnmatchcase(rel_path_norm, f"*/{p}"):
517+
return True
518+
else:
519+
if fnmatch.fnmatchcase(name_lower, pattern_lower):
520+
return True
521+
return False
522+
523+
501524
def scan_repository(
502525
root_path: str,
503526
rules: List[Dict[str, Any]],
@@ -528,7 +551,6 @@ def scan_repository(
528551
Returns:
529552
A list of dictionaries detailing each match.
530553
"""
531-
ignore_lower = {i.lower() for i in ignore_dirs} if ignore_dirs else set()
532554
results = []
533555

534556
filename_targets = []
@@ -557,13 +579,23 @@ def scan_repository(
557579
print(f"Filtering for packages: {target_packages}")
558580

559581
for root, dirs, files in os.walk(root_path):
582+
rel_root = os.path.relpath(root, root_path)
583+
584+
# Helper to construct relative path for ignore matching
585+
def get_rel_path(name):
586+
return name if rel_root == "." else os.path.join(rel_root, name)
587+
560588
# Prune ignore directories (case-insensitive)
561-
dirs[:] = [d for d in dirs if d.lower() not in ignore_lower]
589+
dirs[:] = [
590+
d for d in dirs
591+
if not _should_ignore(get_rel_path(d), d, ignore_dirs)
592+
]
562593

563594
# Filter ignore files (case-insensitive)
564-
files = [f for f in files if f.lower() not in ignore_lower]
565-
566-
rel_root = os.path.relpath(root, root_path)
595+
files = [
596+
f for f in files
597+
if not _should_ignore(get_rel_path(f), f, ignore_dirs)
598+
]
567599

568600
# Layout-agnostic generic subdirectory filtering
569601
if target_packages:

0 commit comments

Comments
 (0)