CODE-0002 · claude-sonnet-4-6 (high)import re
def parse_semver(s):
"""Parse a Semantic Versioning 2.0.0 string.
Returns a dict with keys major, minor, patch, prerelease, build on success,
or None if the input is not a valid SemVer string.
"""
if not isinstance(s, str):
return None
# Pattern breakdown:
# - MAJOR.MINOR.PATCH: non-negative integers with no leading zeros
# - optional prerelease: starts with '-', dot-separated identifiers of [0-9A-Za-z-]
# where numeric identifiers must not have leading zeros
# - optional build metadata: starts with '+', dot-separated identifiers of [0-9A-Za-z-]
# (leading zeros allowed in build)
# Numeric version core: no leading zeros (0 is fine, 01 is not)
numeric_no_leading_zeros = r'(?:0|[1-9][0-9]*)'
# An identifier character (alphanumeric or hyphen)
ident_char = r'[0-9A-Za-z-]'
# A prerelease identifier: alphanumeric+hyphen sequence, where if purely
# numeric it must not have leading zeros.
# A purely numeric identifier: digits only, no leading zero
numeric_ident = r'(?:0|[1-9][0-9]*)'
# An alphanumeric identifier: has at least one non-digit char (always ok)
alphanum_ident = r'[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*'
# A prerelease identifier is either alphanumeric or a valid numeric
pre_ident = r'(?:' + alphanum_ident + r'|' + numeric_ident + r')'
# Build identifiers: any non-empty sequence of [0-9A-Za-z-] (leading zeros ok)
build_ident = r'[0-9A-Za-z-]+'
# Full pattern
pattern = (
r'^'
r'(' + numeric_no_leading_zeros + r')' # MAJOR
r'\.'
r'(' + numeric_no_leading_zeros + r')' # MINOR
r'\.'
r'(' + numeric_no_leading_zeros + r')' # PATCH
r'(?:-(' + pre_ident + r'(?:\.' + pre_ident + r')*))?' # optional prerelease
r'(?:\+(' + build_ident + r'(?:\.' + build_ident + r')*))?' # optional build
r'$'
)
m = re.match(pattern, s)
if not m:
return None
major_s, minor_s, patch_s, prerelease, build = m.groups()
return {
"major": int(major_s),
"minor": int(minor_s),
"patch": int(patch_s),
"prerelease": prerelease,
"build": build,
}