Break out environment marker parsing into its own function and regex
This allows us to handle complex markers using both types of quotation
marks.
Stefano Rivera
2 years ago
0 | dh-python (5.20220101) UNRELEASED; urgency=medium | |
1 | ||
2 | * Handle complex environment markers with both quote types. | |
3 | ||
4 | -- Stefano Rivera <stefanor@debian.org> Sat, 01 Jan 2022 12:06:23 -0400 | |
5 | ||
0 | 6 | dh-python (5.20211231) unstable; urgency=medium |
1 | 7 | |
2 | 8 | * Handle parenthetical environment markers. |
0 | # Copyright © 2022 Stefano Rivera <stefanor@debian.org> | |
1 | # | |
2 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | # of this software and associated documentation files (the "Software"), to deal | |
4 | # in the Software without restriction, including without limitation the rights | |
5 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | # copies of the Software, and to permit persons to whom the Software is | |
7 | # furnished to do so, subject to the following conditions: | |
8 | # | |
9 | # The above copyright notice and this permission notice shall be included in | |
10 | # all copies or substantial portions of the Software. | |
11 | # | |
12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | # THE SOFTWARE. | |
19 | ||
20 | """ | |
21 | Handle Environment Markers | |
22 | https://www.python.org/dev/peps/pep-0508/#environment-markers | |
23 | ||
24 | TODO: Ideally replace with the packaging library, but the API is currently | |
25 | private: https://github.com/pypa/packaging/issues/496 | |
26 | """ | |
27 | ||
28 | import re | |
29 | ||
30 | ||
31 | SIMPLE_ENV_MARKER_RE = re.compile(r''' | |
32 | (?P<marker>[a-z_]+) | |
33 | \s* | |
34 | (?P<op><=?|>=?|[=!~]=|===) | |
35 | \s* | |
36 | (?P<quote>['"]) | |
37 | (?P<value>.*) # Could contain additional markers | |
38 | (?P=quote) | |
39 | ''', re.VERBOSE) | |
40 | COMPLEX_ENV_MARKER_RE = re.compile(r''' | |
41 | (?:\s|\)) | |
42 | (?:and|or) | |
43 | (?:\s|\() | |
44 | ''', re.VERBOSE) | |
45 | ||
46 | ||
47 | class ComplexEnvironmentMarker(Exception): | |
48 | pass | |
49 | ||
50 | ||
51 | def parse_environment_marker(marker): | |
52 | """Parse a simple marker of <= 1 environment restriction""" | |
53 | marker = marker.strip() | |
54 | if marker.startswith('(') and marker.endswith(')'): | |
55 | marker = marker[1:-1].strip() | |
56 | ||
57 | m = COMPLEX_ENV_MARKER_RE.search(marker) | |
58 | if m: | |
59 | raise ComplexEnvironmentMarker() | |
60 | ||
61 | m = SIMPLE_ENV_MARKER_RE.match(marker) | |
62 | if not m: | |
63 | raise ComplexEnvironmentMarker() | |
64 | ||
65 | return ( | |
66 | m.group('marker'), | |
67 | m.group('op'), | |
68 | m.group('value'), | |
69 | ) |
33 | 33 | |
34 | 34 | from dhpython import PKG_PREFIX_MAP, PUBLIC_DIR_RE,\ |
35 | 35 | PYDIST_DIRS, PYDIST_OVERRIDES_FNAMES, PYDIST_DPKG_SEARCH_TPLS |
36 | from dhpython.markers import ComplexEnvironmentMarker, parse_environment_marker | |
37 | from dhpython.tools import memoize | |
36 | 38 | from dhpython.version import get_requested_versions, Version |
37 | from dhpython.tools import memoize | |
38 | 39 | |
39 | 40 | log = logging.getLogger('dhpython') |
40 | 41 | |
73 | 74 | \)? # optional closing parenthesis |
74 | 75 | \s* |
75 | 76 | (?:; # optional environment markers |
76 | \s* | |
77 | \(? # optional parenthesis | |
78 | \s* | |
79 | (?P<environment_marker>[a-z_]+) | |
80 | \s* | |
81 | (?P<environment_marker_op><=?|>=?|[=!~]=|===) | |
82 | \s* | |
83 | (?P<environment_marker_quote>['"]) | |
84 | (?P<environment_marker_value>.*) | |
85 | (?P=environment_marker_quote) | |
86 | \)? # optional parenthesis | |
87 | \s* | |
77 | (?P<environment_marker>.+) | |
88 | 78 | )? |
89 | 79 | ''', re.VERBOSE) |
90 | 80 | EXTRA_RE = re.compile(r''' |
104 | 94 | (?P<section>[a-zA-Z0-9-_.]+)? |
105 | 95 | \s* |
106 | 96 | (?:: |
107 | \s* | |
108 | \(* | |
109 | \s* | |
110 | (?P<environment_marker>[a-z_]+) | |
111 | \s* | |
112 | (?P<environment_marker_op><=?|>=?|[=!~]=|===) | |
113 | \s* | |
114 | (?P<environment_marker_quote>['"]) | |
115 | (?P<environment_marker_value>.*) | |
116 | (?P=environment_marker_quote) | |
117 | \s* | |
118 | \)* | |
119 | \s* | |
97 | (?P<environment_marker>.+) | |
120 | 98 | )? |
121 | 99 | \] |
122 | 100 | \s* |
221 | 199 | action = check_environment_marker_restrictions( |
222 | 200 | req, |
223 | 201 | req_d['environment_marker'], |
224 | req_d['environment_marker_op'], | |
225 | req_d['environment_marker_value'], | |
226 | 202 | impl) |
227 | 203 | if action is False: |
228 | 204 | return |
321 | 297 | # return pname |
322 | 298 | |
323 | 299 | |
324 | def check_environment_marker_restrictions(req, marker, op, value, impl): | |
300 | def check_environment_marker_restrictions(req, marker_str, impl): | |
325 | 301 | """Check wither we should include or skip a dependency based on its |
326 | 302 | environment markers. |
327 | 303 | |
333 | 309 | log.info('Ignoring environment markers for non-Python 3.x: %s', req) |
334 | 310 | return False |
335 | 311 | |
336 | # TODO: Replace with an AST that can handle complex logic | |
337 | if ' or ' in value or ' and ' in value: | |
312 | try: | |
313 | marker, op, value = parse_environment_marker(marker_str) | |
314 | except ComplexEnvironmentMarker: | |
338 | 315 | log.info('Ignoring complex environment marker: %s', req) |
339 | 316 | return False |
340 | 317 | |
498 | 475 | env_action = check_environment_marker_restrictions( |
499 | 476 | line, |
500 | 477 | m.group('environment_marker'), |
501 | m.group('environment_marker_op'), | |
502 | m.group('environment_marker_value'), | |
503 | 478 | impl) |
504 | 479 | processed.append(line) |
505 | 480 | continue |
348 | 348 | "Requires-Dist: extra_test; extra == 'test'", |
349 | 349 | "Requires-Dist: complex_marker; os_name != 'windows' " |
350 | 350 | "and implementation_name == 'cpython'", |
351 | "Requires-Dist: complex_marker_2; (os_name != 'windows') " | |
351 | "Requires-Dist: complex_marker_2; (python_version > \"3.4\") " | |
352 | 352 | "and extra == 'test'", |
353 | 353 | "Requires-Dist: no_markers_2", |
354 | 354 | ), |