44
55import re
66
7+ from typing import (
8+ Literal )
9+
710from pathspec .pattern import (
811 RegexPattern )
912from pathspec ._typing import (
10- AnyStr ) # Removed in 3.18.
13+ AnyStr , # Removed in 3.18.
14+ assert_unreachable )
1115
1216_BYTES_ENCODING = 'latin1'
1317"""
@@ -55,14 +59,25 @@ def escape(s: AnyStr) -> AnyStr:
5559 return out_string
5660
5761 @staticmethod
58- def _translate_segment_glob (pattern : str ) -> str :
62+ def _translate_segment_glob (
63+ pattern : str ,
64+ range_error : Literal ['literal' , 'raise' ],
65+ ) -> str :
5966 """
6067 Translates the glob pattern to a regular expression. This is used in the
6168 constructor to translate a path segment glob pattern to its corresponding
6269 regular expression.
6370
6471 *pattern* (:class:`str`) is the glob pattern.
6572
73+ *range_error* (:class:`int`) is how to handle invalid range notation in the
74+ pattern:
75+
76+ - :data:`"literal"`: Invalid notation will be treated as a literal string.
77+
78+ - :data:`"raise"`: Invalid notation will cause a :class:`_RangeError` to be
79+ raised.
80+
6681 Returns the regular expression (:class:`str`).
6782 """
6883 # NOTE: This is derived from `fnmatch.translate()` and is similar to the
@@ -96,9 +111,9 @@ def _translate_segment_glob(pattern: str) -> str:
96111 regex += '[^/]'
97112
98113 elif char == '[' :
99- # Bracket expression wildcard. Except for the beginning exclamation
100- # mark, the whole bracket expression can be used directly as regex, but
101- # we have to find where the expression ends.
114+ # Bracket expression (range notation) wildcard. Except for the beginning
115+ # exclamation mark, the whole bracket expression can be used directly as
116+ # regex, but we have to find where the expression ends.
102117 # - "[][!]" matches ']', '[' and '!'.
103118 # - "[]-]" matches ']' and '-'.
104119 # - "[!]a-]" matches any character except ']', 'a' and '-'.
@@ -152,9 +167,19 @@ def _translate_segment_glob(pattern: str) -> str:
152167 i = j
153168
154169 else :
155- # Failed to find closing bracket, treat opening bracket as a bracket
156- # literal instead of as an expression.
157- regex += '\\ ['
170+ # Failed to find closing bracket.
171+ if range_error == 'literal' :
172+ # Treat opening bracket as a bracket literal instead of as an
173+ # expression.
174+ regex += '\\ ['
175+ elif range_error == 'raise' :
176+ # Treat invalid range notation as an error.
177+ raise _RangeError ((
178+ f"Invalid range notation={ pattern [i :j ]!r} found in pattern="
179+ f"{ pattern !r} ."
180+ ))
181+ else :
182+ assert_unreachable (f"{ range_error = !r} is invalid." )
158183
159184 else :
160185 # Regular character, escape it for regex.
@@ -174,3 +199,11 @@ class GitIgnorePatternError(ValueError):
174199 pattern.
175200 """
176201 pass
202+
203+
204+ class _RangeError (GitIgnorePatternError ):
205+ """
206+ The :class:`_RangeError` class indicates an invalid range notation was found
207+ in a gitignore pattern.
208+ """
209+ pass
0 commit comments