Skip to content

Commit 24374be

Browse files
committed
Allow empty pattern list
1 parent 3a94667 commit 24374be

File tree

6 files changed

+70
-21
lines changed

6 files changed

+70
-21
lines changed

pathspec/_backends/hyperscan/gitignore.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,15 @@ def _init_db(
9999
Returns a :class:`list` indexed by expression id (:class:`int`) to its data
100100
(:class:`HyperscanExprDat`).
101101
"""
102+
# WARNING: Hyperscan raises a `hyperscan.error` exception when compiled with
103+
# zero elements.
104+
assert patterns, patterns
105+
102106
# Prepare patterns.
103107
expr_data: list[HyperscanExprDat] = []
104108
exprs: list[bytes] = []
105109
for pattern_index, pattern in patterns:
106-
if pattern.include is None:
107-
continue
110+
assert pattern.include is not None, (pattern_index, pattern)
108111

109112
# Encode regex.
110113
assert isinstance(pattern, RegexPattern), pattern
@@ -189,8 +192,14 @@ def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
189192
"""
190193
# NOTICE: According to benchmarking, a method callback is 13% faster than
191194
# using a closure here.
195+
db = self._db
196+
if self._db is None:
197+
# Database was not initialized because there were no patterns. Return no
198+
# match.
199+
return (None, None)
200+
192201
self._out = (None, -1, 0)
193-
self._db.scan(file.encode('utf8'), match_event_handler=self.__on_match)
202+
db.scan(file.encode('utf8'), match_event_handler=self.__on_match)
194203

195204
out_include, out_index = self._out[:2]
196205
if out_index == -1:

pathspec/_backends/hyperscan/pathspec.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,32 +59,40 @@ def __init__(
5959
if hyperscan is None:
6060
raise hyperscan_error
6161

62-
if not patterns:
63-
raise ValueError(f"{patterns=!r} cannot be empty.")
64-
elif not isinstance(patterns[0], RegexPattern):
62+
if patterns and not isinstance(patterns[0], RegexPattern):
6563
raise TypeError(f"{patterns[0]=!r} must be a RegexPattern.")
6664

6765
use_patterns = enumerate_patterns(
6866
patterns, filter=True, reverse=False,
6967
)
7068

71-
self._db = self._make_db()
69+
debug_exprs = bool(_debug_exprs)
70+
if use_patterns:
71+
db = self._make_db()
72+
expr_data = self._init_db(
73+
db=db,
74+
debug=debug_exprs,
75+
patterns=use_patterns,
76+
sort_ids=_test_sort,
77+
)
78+
else:
79+
# WARNING: The hyperscan database cannot be initialized with zero
80+
# patterns.
81+
db = None
82+
expr_data = []
83+
84+
self._db: Optional[hyperscan.Database] = db
7285
"""
7386
*_db* (:class:`hyperscan.Database`) is the Hyperscan database.
7487
"""
7588

76-
self._debug_exprs = bool(_debug_exprs)
89+
self._debug_exprs = debug_exprs
7790
"""
7891
*_debug_exprs* (:class:`bool`) is whether to include additional debugging
7992
information for the expressions.
8093
"""
8194

82-
self._expr_data: list[HyperscanExprDat] = self._init_db(
83-
db=self._db,
84-
debug=self._debug_exprs,
85-
patterns=use_patterns,
86-
sort_ids=_test_sort,
87-
)
95+
self._expr_data: list[HyperscanExprDat] = expr_data
8896
"""
8997
*_expr_data* (:class:`list`) maps expression index (:class:`int`) to
9098
expression data (:class:`:class:`HyperscanExprDat`).
@@ -130,12 +138,15 @@ def _init_db(
130138
Returns a :class:`list` indexed by expression id (:class:`int`) to its data
131139
(:class:`HyperscanExprDat`).
132140
"""
141+
# WARNING: Hyperscan raises a `hyperscan.error` exception when compiled with
142+
# zero elements.
143+
assert patterns, patterns
144+
133145
# Prepare patterns.
134146
expr_data: list[HyperscanExprDat] = []
135147
exprs: list[bytes] = []
136148
for pattern_index, pattern in patterns:
137-
if pattern.include is None:
138-
continue
149+
assert pattern.include is not None, (pattern_index, pattern)
139150

140151
# Encode regex.
141152
assert isinstance(pattern, RegexPattern), pattern
@@ -176,6 +187,7 @@ def _init_db(
176187
elements=len(exprs),
177188
flags=HS_FLAGS,
178189
)
190+
179191
return expr_data
180192

181193
@override
@@ -191,8 +203,14 @@ def match_file(self, file: str) -> tuple[Optional[bool], Optional[int]]:
191203
"""
192204
# NOTICE: According to benchmarking, a method callback is 20% faster than
193205
# using a closure here.
206+
db = self._db
207+
if self._db is None:
208+
# Database was not initialized because there were no patterns. Return no
209+
# match.
210+
return (None, None)
211+
194212
self._out = (None, -1)
195-
self._db.scan(file.encode('utf8'), match_event_handler=self.__on_match)
213+
db.scan(file.encode('utf8'), match_event_handler=self.__on_match)
196214

197215
out_include, out_index = self._out
198216
if out_index == -1:

pathspec/_backends/re2/pathspec.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@ def __init__(
5757
if re2_error is not None:
5858
raise re2_error
5959

60-
if not patterns:
61-
raise ValueError(f"{patterns=!r} cannot be empty.")
62-
elif not isinstance(patterns[0], RegexPattern):
60+
if patterns and not isinstance(patterns[0], RegexPattern):
6361
raise TypeError(f"{patterns[0]=!r} must be a RegexPattern.")
6462

6563
use_patterns = dict(enumerate_patterns(

pathspec/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
This module defines the version.
33
"""
44

5-
__version__ = "1.0.0"
5+
__version__ = "1.0.1.dev1"

tests/test_04_pathspec.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,3 +1029,15 @@ def test_09_issue_80_b(self):
10291029

10301030
self.assertEqual(files - ignores, keeps)
10311031
self.assertEqual(files - keeps, ignores)
1032+
1033+
def test_10_issue_100(self):
1034+
"""
1035+
Test an empty list of patterns.
1036+
"""
1037+
for sub_test in self.parameterize_from_lines('gitignore', []):
1038+
with sub_test() as spec:
1039+
files = {'foo'}
1040+
results = list(spec.check_files(files))
1041+
includes = get_includes(results)
1042+
debug = debug_results(spec, results)
1043+
self.assertEqual(includes, set(), debug)

tests/test_05_gitignore.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,3 +677,15 @@ def test_08_issue_81_c(self):
677677
"libfoo/__init__.py",
678678
}, debug)
679679
self.assertEqual(files - ignores, set())
680+
681+
def test_09_issue_100(self):
682+
"""
683+
Test an empty list of patterns.
684+
"""
685+
for sub_test in self.parameterize_from_lines([]):
686+
with sub_test() as spec:
687+
files = {'foo'}
688+
results = list(spec.check_files(files))
689+
includes = get_includes(results)
690+
debug = debug_results(spec, results)
691+
self.assertEqual(includes, set(), debug)

0 commit comments

Comments
 (0)