bumpver/src/pycalver/parse.py

109 lines
3 KiB
Python
Raw Normal View History

2018-09-02 21:48:12 +02:00
# This file is part of the pycalver project
# https://github.com/mbarkhau/pycalver
#
# (C) 2018 Manuel Barkhau (@mbarkhau)
# SPDX-License-Identifier: MIT
import re
import logging
import typing as typ
2018-09-02 23:36:57 +02:00
import pkg_resources
2018-09-02 21:48:12 +02:00
log = logging.getLogger("pycalver.parse")
VALID_RELESE_VALUES = ("alpha", "beta", "dev", "rc", "post")
2018-09-02 23:36:57 +02:00
# https://regex101.com/r/fnj60p/10
2018-09-04 09:56:53 +02:00
PYCALVER_RE: typ.Pattern[str] = re.compile(r"""
2018-09-02 21:48:12 +02:00
\b
(?P<version>
(?P<calver>
v # "v" version prefix
(?P<year>\d{4})
(?P<month>\d{2})
)
2018-09-02 23:36:57 +02:00
(?P<build>
2018-09-02 21:48:12 +02:00
\. # "." build nr prefix
2018-09-02 23:36:57 +02:00
\d{4,}
2018-09-02 21:48:12 +02:00
)
2018-09-02 23:36:57 +02:00
(?P<release>
2018-09-02 21:48:12 +02:00
\- # "-" release prefix
2018-09-02 23:36:57 +02:00
(?:alpha|beta|dev|rc|post)
2018-09-02 21:48:12 +02:00
)?
)(?:\s|$)
""", flags=re.VERBOSE)
2018-09-03 09:19:27 +02:00
# NOTE (mb 2018-09-03): These are matchers for parts, which are
# used in the patterns, they're not for validation. This means
# that they may find strings, which are not valid pycalver
# strings, when parsed in their full context. For such cases,
# the patterns should be expanded.
2018-09-02 21:48:12 +02:00
RE_PATTERN_PARTS = {
2018-09-03 09:19:27 +02:00
"pep440_version" : r"\d{6}\.[1-9]\d*(a|b|dev|rc|post)?\d*",
"version" : r"v\d{6}\.\d{4,}(\-(alpha|beta|dev|rc|post))?",
2018-09-02 21:48:12 +02:00
"calver" : r"v\d{6}",
"build" : r"\.\d{4,}",
2018-09-03 09:19:27 +02:00
"release" : r"(\-(alpha|beta|dev|rc|post))?",
2018-09-02 21:48:12 +02:00
}
class PatternMatch(typ.NamedTuple):
2018-09-02 23:36:57 +02:00
lineno : int # zero based
2018-09-02 21:48:12 +02:00
line : str
pattern : str
span : typ.Tuple[int, int]
match : str
2018-09-02 23:36:57 +02:00
class VersionInfo(typ.NamedTuple):
pep440_version : str
version : str
calver : str
year : str
month : str
build : str
release : typ.Optional[str]
def parse_version_info(version: str) -> VersionInfo:
match = PYCALVER_RE.match(version)
2018-09-04 09:56:53 +02:00
if match is None:
raise ValueError(f"Invalid pycalver: {version}")
2018-09-03 09:19:27 +02:00
pep440_version = str(pkg_resources.parse_version(version))
2018-09-02 23:36:57 +02:00
return VersionInfo(pep440_version=pep440_version, **match.groupdict())
2018-09-02 21:48:12 +02:00
def iter_pattern_matches(lines: typ.List[str], pattern: str) -> typ.Iterable[PatternMatch]:
# The pattern is escaped, so that everything besides the format
# string variables is treated literally.
pattern_re = re.compile(
pattern
.replace("\\", "\\\\")
.replace("-", "\\-")
.replace(".", "\\.")
.replace("+", "\\+")
.replace("*", "\\*")
.replace("[", "\\[")
.replace("(", "\\(")
.format(**RE_PATTERN_PARTS)
)
2018-09-02 23:36:57 +02:00
for lineno, line in enumerate(lines):
2018-09-02 21:48:12 +02:00
match = pattern_re.search(line)
if match:
yield PatternMatch(lineno, line, pattern, match.span(), match.group(0))
def parse_patterns(lines: typ.List[str], patterns: typ.List[str]) -> typ.List[PatternMatch]:
all_matches: typ.List[PatternMatch] = []
for pattern in patterns:
all_matches.extend(iter_pattern_matches(lines, pattern))
return all_matches