Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pkg_resources/_vendor/packaging/_tokenizer.py: 41%
64 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1import contextlib
2import re
3from dataclasses import dataclass
4from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union
6from .specifiers import Specifier
9@dataclass
10class Token:
11 name: str
12 text: str
13 position: int
16class ParserSyntaxError(Exception):
17 """The provided source text could not be parsed correctly."""
19 def __init__(
20 self,
21 message: str,
22 *,
23 source: str,
24 span: Tuple[int, int],
25 ) -> None:
26 self.span = span
27 self.message = message
28 self.source = source
30 super().__init__()
32 def __str__(self) -> str:
33 marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
34 return "\n ".join([self.message, self.source, marker])
37DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
38 "LEFT_PARENTHESIS": r"\(",
39 "RIGHT_PARENTHESIS": r"\)",
40 "LEFT_BRACKET": r"\[",
41 "RIGHT_BRACKET": r"\]",
42 "SEMICOLON": r";",
43 "COMMA": r",",
44 "QUOTED_STRING": re.compile(
45 r"""
46 (
47 ('[^']*')
48 |
49 ("[^"]*")
50 )
51 """,
52 re.VERBOSE,
53 ),
54 "OP": r"(===|==|~=|!=|<=|>=|<|>)",
55 "BOOLOP": r"\b(or|and)\b",
56 "IN": r"\bin\b",
57 "NOT": r"\bnot\b",
58 "VARIABLE": re.compile(
59 r"""
60 \b(
61 python_version
62 |python_full_version
63 |os[._]name
64 |sys[._]platform
65 |platform_(release|system)
66 |platform[._](version|machine|python_implementation)
67 |python_implementation
68 |implementation_(name|version)
69 |extra
70 )\b
71 """,
72 re.VERBOSE,
73 ),
74 "SPECIFIER": re.compile(
75 Specifier._operator_regex_str + Specifier._version_regex_str,
76 re.VERBOSE | re.IGNORECASE,
77 ),
78 "AT": r"\@",
79 "URL": r"[^ \t]+",
80 "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
81 "WS": r"[ \t]+",
82 "END": r"$",
83}
86class Tokenizer:
87 """Context-sensitive token parsing.
89 Provides methods to examine the input stream to check whether the next token
90 matches.
91 """
93 def __init__(
94 self,
95 source: str,
96 *,
97 rules: "Dict[str, Union[str, re.Pattern[str]]]",
98 ) -> None:
99 self.source = source
100 self.rules: Dict[str, re.Pattern[str]] = {
101 name: re.compile(pattern) for name, pattern in rules.items()
102 }
103 self.next_token: Optional[Token] = None
104 self.position = 0
106 def consume(self, name: str) -> None:
107 """Move beyond provided token name, if at current position."""
108 if self.check(name):
109 self.read()
111 def check(self, name: str, *, peek: bool = False) -> bool:
112 """Check whether the next token has the provided name.
114 By default, if the check succeeds, the token *must* be read before
115 another check. If `peek` is set to `True`, the token is not loaded and
116 would need to be checked again.
117 """
118 assert (
119 self.next_token is None
120 ), f"Cannot check for {name!r}, already have {self.next_token!r}"
121 assert name in self.rules, f"Unknown token name: {name!r}"
123 expression = self.rules[name]
125 match = expression.match(self.source, self.position)
126 if match is None:
127 return False
128 if not peek:
129 self.next_token = Token(name, match[0], self.position)
130 return True
132 def expect(self, name: str, *, expected: str) -> Token:
133 """Expect a certain token name next, failing with a syntax error otherwise.
135 The token is *not* read.
136 """
137 if not self.check(name):
138 raise self.raise_syntax_error(f"Expected {expected}")
139 return self.read()
141 def read(self) -> Token:
142 """Consume the next token and return it."""
143 token = self.next_token
144 assert token is not None
146 self.position += len(token.text)
147 self.next_token = None
149 return token
151 def raise_syntax_error(
152 self,
153 message: str,
154 *,
155 span_start: Optional[int] = None,
156 span_end: Optional[int] = None,
157 ) -> NoReturn:
158 """Raise ParserSyntaxError at the given position."""
159 span = (
160 self.position if span_start is None else span_start,
161 self.position if span_end is None else span_end,
162 )
163 raise ParserSyntaxError(
164 message,
165 source=self.source,
166 span=span,
167 )
169 @contextlib.contextmanager
170 def enclosing_tokens(self, open_token: str, close_token: str) -> Iterator[bool]:
171 if self.check(open_token):
172 open_position = self.position
173 self.read()
174 else:
175 open_position = None
177 yield open_position is not None
179 if open_position is None:
180 return
182 if not self.check(close_token):
183 self.raise_syntax_error(
184 f"Expected closing {close_token}",
185 span_start=open_position,
186 )
188 self.read()