CODE-0002 · claude-opus-4-8 (high)import re
_NUM = r"0|[1-9]\d*"
_PR_IDENT = r"(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)"
_BUILD_IDENT = r"[0-9A-Za-z-]+"
_SEMVER_RE = re.compile(
r"^(?P<major>" + _NUM + r")"
r"\.(?P<minor>" + _NUM + r")"
r"\.(?P<patch>" + _NUM + r")"
r"(?:-(?P<prerelease>" + _PR_IDENT + r"(?:\." + _PR_IDENT + r")*))?"
r"(?:\+(?P<build>" + _BUILD_IDENT + r"(?:\." + _BUILD_IDENT + r")*))?"
r"$"
)
def parse_semver(s):
"""Parse a SemVer 2.0.0 string into a dict, or return None if invalid.
On success returns a dict with keys "major", "minor", "patch" (ints),
"prerelease" (str without leading "-", or None), and "build" (str
without leading "+", or None). Returns None for any non-matching input,
including non-strings.
"""
if not isinstance(s, str):
return None
match = _SEMVER_RE.match(s)
if match is None:
return None
return {
"major": int(match.group("major")),
"minor": int(match.group("minor")),
"patch": int(match.group("patch")),
"prerelease": match.group("prerelease"),
"build": match.group("build"),
}