|
3 | 3 | except ImportError: |
4 | 4 | from typing_extensions import Self |
5 | 5 |
|
| 6 | +from regex._regex_core import error as RegexError |
6 | 7 | from ..lazy_regex import RegexLazyIgnore |
7 | 8 | from .client_hints import ClientHints |
8 | | -from .extractors import ( |
9 | | - NameExtractor, |
10 | | - VersionExtractor, |
11 | | -) |
12 | 9 | from ..yaml_loader import RegexLoader, app_pretty_names_types_data |
13 | 10 |
|
14 | 11 | # Match regexes that ONLY values like: |
@@ -137,30 +134,40 @@ def extract_version(self) -> None: |
137 | 134 | Extract the version if UA Yaml files specify version regexes. |
138 | 135 | See oss.yml for example file structure. |
139 | 136 | """ |
140 | | - |
| 137 | + user_agent = self.user_agent |
141 | 138 | for version in self.ua_data.pop('versions', []): |
142 | | - if version['regex'].search(self.user_agent): |
143 | | - self.ua_data['version'] = version['version'] |
| 139 | + if version_regex_match := version['regex'].search(user_agent): |
| 140 | + self.ua_data['version'] = perform_substitutions( |
| 141 | + version['version'], version_regex_match, '.' |
| 142 | + ) |
144 | 143 | return |
145 | 144 |
|
| 145 | + self._set_data_from_field('version', '.') |
| 146 | + |
146 | 147 | def set_details(self) -> None: |
147 | 148 | """ |
148 | 149 | Override this method on subclasses. |
149 | 150 |
|
150 | 151 | Update fields with interpolated values from regex data |
151 | 152 | """ |
152 | | - groups = self.matched_regex and self.matched_regex.groups() or None |
153 | | - if groups: |
154 | | - if 'name' in self.ua_data: |
155 | | - self.ua_data['name'] = NameExtractor(self.ua_data, groups).extract() |
156 | | - |
157 | | - if 'version' in self.ua_data: |
158 | | - self.ua_data['version'] = VersionExtractor(self.ua_data, groups).extract() |
| 153 | + if self.matched_regex: |
| 154 | + self._set_data_from_field('name', '.') |
159 | 155 |
|
160 | 156 | # no version should be considered valid if the name can't be parsed |
161 | 157 | if not self.ua_data.get('name') and self.ua_data.get('version'): |
162 | 158 | self.ua_data['version'] = '' |
163 | 159 |
|
| 160 | + def _set_data_from_field(self, field: str, separator: str): |
| 161 | + """ |
| 162 | + Check specified field value to see if it has a regex separator, |
| 163 | + and if so, update the value to include the regex capture details. |
| 164 | + """ |
| 165 | + if substring := self.ua_data.get(field, ''): |
| 166 | + if "\\g<" in substring: |
| 167 | + self.ua_data[field] = perform_substitutions( |
| 168 | + substring, self.matched_regex, separator |
| 169 | + ) |
| 170 | + |
164 | 171 | def name(self) -> str: |
165 | 172 | return self.ua_data.get('name', '') |
166 | 173 |
|
@@ -198,8 +205,24 @@ def __repr__(self) -> str: |
198 | 205 | return f'{klass}({self.user_agent!r}, {self.ua_data!r})' |
199 | 206 |
|
200 | 207 |
|
| 208 | +def perform_substitutions(substring: str, regex_match, separator: str) -> str: |
| 209 | + """ |
| 210 | + Substitute the captured value from the regex for the regex placeholder. |
| 211 | + """ |
| 212 | + regex_pattern = regex_match.re |
| 213 | + capture = regex_match.captures()[0] |
| 214 | + try: |
| 215 | + value = regex_pattern.sub(substring, capture) |
| 216 | + if value.endswith(('\\g<1>', '\\g<2>')): |
| 217 | + value = value[: value.rfind('\\g<')] |
| 218 | + return value.replace('_', separator).strip(' .') |
| 219 | + except RegexError: |
| 220 | + return substring |
| 221 | + |
| 222 | + |
201 | 223 | __all__ = ( |
202 | 224 | 'Parser', |
| 225 | + 'perform_substitutions', |
203 | 226 | 'IPHONE_ONLY_UA', |
204 | 227 | 'ENDSWITH_DARWIN', |
205 | 228 | ) |
0 commit comments