Skip to content

Commit b2c3926

Browse files
authored
fix: parsing local version with digit followed by non-digits (bazel-contrib#3032)
When parsing the local identifier segment of `<digit><letter>` the parser would give an error saying the letter was unexpected. What was happening was `accept_digits()` consumed up to the first non-digit, and considered this success, which prevented calling `accept_alnum()` to finish the parsing. To fix, only call `accept_alnum()`, then post-process the value to normalize an all-digit segment. I'm guessing `accept_digits()` stopping at the first non-digit is WAI because it expects to parse e.g. "3.14b", where the caller handles subsequent characters. Along the way, some minor doc improvements to the parser code. Fixes bazel-contrib#3030
1 parent 998e22e commit b2c3926

File tree

2 files changed

+46
-9
lines changed

2 files changed

+46
-9
lines changed

python/private/version.bzl

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ def _in(reference):
4444
return lambda token: token in reference
4545

4646
def _ctx(start):
47-
return {"norm": "", "start": start}
47+
"""Creates a context, which is state for parsing (or sub-parsing)."""
48+
return {
49+
# The result value from parsing
50+
"norm": "",
51+
# Where in the parser's input string this context starts.
52+
"start": start,
53+
}
4854

4955
def _open_context(self):
5056
"""Open an new parsing ctx.
@@ -60,7 +66,16 @@ def _open_context(self):
6066
return self.contexts[-1]
6167

6268
def _accept(self, key = None):
63-
"""Close the current ctx successfully and merge the results."""
69+
"""Close the current ctx successfully and merge the results.
70+
71+
Args:
72+
self: {type}`Parser}
73+
key: {type}`str | None` the key to store the result in
74+
the most recent context. If not set, the key is "norm".
75+
76+
Returns:
77+
{type}`bool` always True
78+
"""
6479
finished = self.contexts.pop()
6580
self.contexts[-1]["norm"] += finished["norm"]
6681
if key:
@@ -79,7 +94,14 @@ def _discard(self, key = None):
7994
return False
8095

8196
def _new(input):
82-
"""Create a new normalizer"""
97+
"""Create a new parser
98+
99+
Args:
100+
input: {type}`str` input to parse
101+
102+
Returns:
103+
{type}`Parser` a struct for a parser object.
104+
"""
83105
self = struct(
84106
input = input,
85107
contexts = [_ctx(0)],
@@ -167,7 +189,7 @@ def accept_placeholder(parser):
167189
return parser.accept()
168190

169191
def accept_digits(parser):
170-
"""Accept multiple digits (or placeholders).
192+
"""Accept multiple digits (or placeholders), up to a non-digit/placeholder.
171193
172194
Args:
173195
parser: The normalizer.
@@ -275,13 +297,20 @@ def accept_separator_alnum(parser):
275297
Returns:
276298
whether a separator and an alphanumeric string were accepted.
277299
"""
278-
parser.open_context()
300+
ctx = parser.open_context()
279301

280302
# PEP 440: Local version segments
281-
if (
282-
accept(parser, _in([".", "-", "_"]), ".") and
283-
(accept_digits(parser) or accept_alnum(parser))
284-
):
303+
if not accept(parser, _in([".", "-", "_"]), "."):
304+
return parser.discard()
305+
306+
if accept_alnum(parser):
307+
# First character is separator; skip it.
308+
value = ctx["norm"][1:]
309+
310+
# PEP 440: Integer Normalization
311+
if value.isdigit():
312+
value = str(int(value))
313+
ctx["norm"] = ctx["norm"][0] + value
285314
return parser.accept()
286315

287316
return parser.discard()

tests/version/version_test.bzl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ def _test_normalization(env):
105105

106106
_tests.append(_test_normalization)
107107

108+
def _test_normalize_local(env):
109+
# Verify a local with a [digit][non-digit] sequence parses ok
110+
in_str = "0.1.0+brt.9e"
111+
actual = version.normalize(in_str)
112+
env.expect.that_str(actual).equals(in_str)
113+
114+
_tests.append(_test_normalize_local)
115+
108116
def _test_ordering(env):
109117
want = [
110118
# Taken from https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy