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,
    }