moonsver

    Semantic Versioning 2.0.0 for MoonBit: versions, precedence and ranges.

    semver
    semantic-versioning
    version
    versioning
    version-range
    range
    constraint
    parser
    comparison
    precedence
    npm
    cargo
    node-semver
    cli
    moonbit
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    2 days ago
    Downloads
    48

    No dependencies

    SemVer 2.0.0 · arbitrary precision · Apache-2.0


    moonsver reads and writes semantic versions, compares them by the precedence rules of the spec, and evaluates version ranges. A version is a small, strict format, so the work is in the edges: leading zeroes, prerelease ordering, build metadata, and the many ways a range can be written. All of that is handled here.

    The three numbers are arbitrary-precision integers, so 18446744073709551616.0.0 parses, prints and compares exactly. A range is parsed into comparator sets that you can read back, and every range grammar — npm, Cargo, Ruby or the strict spec — is a setting on one parser.

    • Arbitrary precision. Numbers are BigInt, not a fixed-width integer.
    • One parser, four grammars. The grammar is a Syntax value, not a separate parser.
    • Ranges as structure. ^1.2.3 becomes >=1.2.3, <2.0.0; ask it anything.
    • Precedence per §11. Build metadata is excluded, exactly as the spec says.
    • Bumps. Major, minor, patch, the four prerelease forms, and stable.
    • JSON in and out. Versions and ranges serialise as strings.
    • No dependencies. Only moonbitlang/core.

    #Install

    Add the module to your package:

    // moon.pkg
    import {
    "moonbitstack/moonsver",
    }

    It exports as @moonsver. With the package manager, moon add moonbitstack/moonsver writes the same import.

    #Quick start

    fn demo() -> Unit raise @moonsver.Refused {
    // Parse and print
    let v = @moonsver.Version::parse("1.2.3-alpha.1+build.5")
    println(v.to_string()) // 1.2.3-alpha.1+build.5
    println(v.core_string()) // 1.2.3
    println(v.is_prerelease()) // true

    // Precedence (§11; build metadata stays out of it)
    let a = @moonsver.Version::parse("1.0.0-alpha")
    let b = @moonsver.Version::parse("1.0.0")
    println(a.precedence_cmp(b)) // -1

    // Ranges
    let r = @moonsver.Range::parse("^1.2.3")
    println(r.to_string()) // >=1.2.3, <2.0.0
    println(r.satisfies(@moonsver.Version::parse("1.9.0"))) // true
    println(r.satisfies(@moonsver.Version::parse("2.0.0"))) // false

    // Bumps
    println(@moonsver.Version::parse("1.2.3").inc(@moonsver.Major).to_string()) // 2.0.0
    }

    Input that does not parse raises Refused, which names what was wrong and where.

    #Interface

    #Version

    MethodWhat it does
    Version::parse(s, syntax?)Parse a string, raising Refused; the npm grammar is the default
    Version::new(major, minor, patch)Build from three Ints
    Version::from_bigint(major, minor, patch)Build from three BigInts
    major() / minor() / patch()The numbers, as BigInt
    prerelease() / build()Identifier lists, as ArrayView
    is_prerelease() / is_stable()Whether a prerelease is present
    same_core(other)Do the three numbers match?
    core() / core_string()Drop prerelease and build metadata
    precedence_cmp(other)§11 precedence: -1, 0 or 1
    compare(other)Total order: precedence, then build metadata
    inc(release)Apply a bump
    to_string()The canonical text form

    Equality is full equality: 1.0.0+a and 1.0.0+b are not equal, though they share a precedence.

    #Range

    MethodWhat it does
    Range::parse(s, syntax?)Parse a range, raising Refused
    Range::any()Any version (*)
    satisfies(version) / satisfies_with(version, policy)Does the version fit?
    max_satisfying(versions) / min_satisfying(versions)The extreme versions that fit
    satisfying(versions)Everything that fits, sorted ascending
    sets()The disjuncts, as ArrayView[ComparatorSet]
    policy()The prerelease policy the range was parsed with
    is_any()Whether this is the same as any version
    to_string()Canonical text that re-parses under the same grammar

    A Range is the parsed structure, not the original string. Canonical output joins comparators with , and disjuncts with || .

    #Release

    Major, Minor, Patch, PreMajor, PreMinor, PrePatch, Prerelease, Stable. Release::parse("major") and to_string() serve config files and the command line.

    InputBumpResult
    1.0.0-preMajor1.0.0
    1.2.0-preMajor2.0.0
    1.2.3-prePatch1.2.3
    1.2.3Prerelease1.2.4-0
    1.2.3-alpha.1.2Prerelease1.2.3-alpha.1.3
    1.2.3-alpha.betaPrerelease1.2.3-alpha.beta.0
    1.2.3-rc.1+buildStable1.2.3

    #One-liner and errors

    satisfies(version, range, syntax?) parses two strings and answers; both are read with the same grammar.

    Refused names what was wrong: Empty, Unexpected, Truncated, Number, LeadingZero, EmptyIdentifier, Identifier, Unsupported or Range. Each carries at, the character index of the first offending character. at() and message() read them without matching.

    #Grammars

    A version has one spelling in SemVer 2.0.0. Ranges are not in the spec, so different ecosystems settled on different rules. Syntax collects those differences as settings, and four presets are built from them:

    PresetA bare version means^~\|\|Hyphen1.x!=
    Syntax::npm()exactyesyesyesyesyesno
    Syntax::cargo()caretyesyesnonoyesno
    Syntax::ruby()exactno~>nononoyes
    Syntax::strict()exactnononononono

    Syntax::from_name("cargo") and name() serve config files and the CLI. Default is the npm preset. You can also build a Syntax literal of your own.

    FieldWhat it controls
    default_opWhat a bare version means: Exact, Caret or Tilde
    tilde_bumpHow ~ finds its upper bound: Patch or LastSpecified
    allow_caretAccept ^
    allow_tildeAccept ~ (~> only under LastSpecified)
    allow_neAccept !=
    allow_orAccept \|\|
    allow_hyphen_rangeAccept 1.2.3 - 2.0.0
    allow_xrangeAccept 1.x, 1.2.* and bare 1.2
    allow_emptyTreat an empty string as any version
    allow_v_prefixAccept v1.2.3
    allow_equals_prefixAccept =1.2.3 as a version literal
    allow_build_metadataAccept a +build segment
    trimAllow whitespace around a version literal
    prerelease_policyHow prereleases are matched

    #The same string, read differently

    WrittennpmCargoRuby
    1.2.3=1.2.3>=1.2.3, <2.0.0=1.2.3
    ~1.2>=1.2.0, <1.3.0>=1.2.0, <1.3.0~> 1.2 gives >=1.2.0, <2.0.0
    1.2.*>=1.2.0, <1.3.0>=1.2.0, <1.3.0not supported

    An explicit wildcard wins over default_op: under the Cargo preset, 1.2.* is >=1.2.0, <1.3.0, not a caret range.

    #Precedence

    1. Compare major, minor and patch in turn, at arbitrary precision.
    2. A version with a prerelease ranks below the same numbers without one.
    3. Prerelease identifiers compare one by one: numeric by value, numeric below alphanumeric, alphanumeric in ASCII order.
    4. A prefix ranks below the longer list.
    5. Build metadata takes no part.

    1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0

    #Prereleases in ranges

    The spec says nothing about how a range should treat prereleases. Syntax makes it a three-way setting:

    PolicyMeaningExample
    SameTupleOnly through a comparator with the same major.minor.patch that carries a prerelease>=1.2.3-alpha takes 1.2.3-beta, refuses 1.2.4-beta
    AlwaysCompare a prerelease like any other version>=1.0.0 takes 1.5.0-beta
    NeverPrereleases never match>=1.0.0 refuses 1.5.0-beta

    The policy is recorded in the Range when it is parsed, and satisfies_with can override it for one call.

    #Command line

    The src/bin package wraps the same functions.

    $ moon run src/bin -- range "^0.2.3" >=0.2.3, <0.3.0 $ moon run src/bin -- satisfies 1.9.0 "^1.2.3" true $ moon run src/bin -- sort 2.0.0 1.0.0-beta 1.0.0 0.9.9 0.9.9 1.0.0-beta 1.0.0 2.0.0

    A command exits 0 when it ran, even if the answer is false or nothing fits; read stdout for the answer. Bad arguments and parse failures exit non-zero with the detail on stderr.

    #JSON

    Version and Range implement ToJson / FromJson, serialised as strings, so they drop into config files and API responses:

    let j = @json.to_json(@moonsver.Range::parse("^1.2.3"))
    // Json::string(">=1.2.3, <2.0.0")

    #Examples

    ExampleWhat it shows
    src/examples/tourThe smallest useful walk through the package
    src/examples/rangesThe same range under each preset, wildcards, hyphen ranges and policies
    src/examples/bumpsEvery Release form against a set of inputs
    src/examples/jsonioA JSON round trip for a version and a range

    $ moon run src/examples/tour

    #License

    Apache-2.0. See LICENSE.

    Refused

    pub(all) suberror Refused {
    Empty(at~ : Int, String)
    Unexpected(at~ : Int, String)
    Truncated(at~ : Int, String)
    Number(at~ : Int, String)
    LeadingZero(at~ : Int, String)
    EmptyIdentifier(at~ : Int, String)
    Identifier(at~ : Int, String)
    Unsupported(at~ : Int, String)
    Range(at~ : Int, String)
    } derive(Eq)

    Why a version or a range was refused, and where.

    at is the zero-based character index of the first offending character, or the input's length when it ended early; the message says what was expected.
    impl Show for Refused

    Refused::at

    fn Refused::at(self : Refused) -> Int

    Where the input went wrong.

    Refused::equal

    fn Refused::equal(Refused, Refused) -> Bool

    Refused::message

    fn Refused::message(self : Refused) -> String

    What was wrong, in words.

    Refused::not_equal

    fn Refused::not_equal(x : Refused, y : Refused) -> Bool

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_repr

    Refused::to_string

    fn Refused::to_string(self : Refused) -> String

    Comparator

    pub(all) struct Comparator {
    op : Op
    version : Version
    }

    One comparator: an operator and a full version.

    Build metadata is dropped from comparators (>=1.2.3+build is >=1.2.3), since it takes no part in precedence.
    impl Eq for Comparator
    impl Show for Comparator

    Comparator::equal

    fn Comparator::equal(self : Comparator, other : Comparator) -> Bool

    Comparator::not_equal

    fn Comparator::not_equal(x : Comparator, y : Comparator) -> Bool

    Comparator::output

    fn Comparator::output(self : Comparator, logger : &Logger) -> Unit

    Comparator::to_repr

    Comparator::to_string

    fn Comparator::to_string(self : Comparator) -> String

    ComparatorSet

    pub(all) struct ComparatorSet {
    comparators : Array[Comparator]
    }

    A conjunction (AND) of comparators. An empty list means "any version".
    impl Eq for ComparatorSet

    ComparatorSet::equal

    fn ComparatorSet::equal(self : ComparatorSet, other : ComparatorSet) -> Bool

    ComparatorSet::not_equal

    fn ComparatorSet::not_equal(x : ComparatorSet, y : ComparatorSet) -> Bool

    ComparatorSet::output

    fn ComparatorSet::output(self : ComparatorSet, logger : &Logger) -> Unit

    ComparatorSet::to_string

    fn ComparatorSet::to_string(self : ComparatorSet) -> String

    DefaultOp

    pub(all) enum DefaultOp {
    Exact
    Caret
    Tilde
    }

    What a bare version means when no operator is given.
    impl Eq for DefaultOp

    DefaultOp::equal

    fn DefaultOp::equal(self : DefaultOp, other : DefaultOp) -> Bool

    DefaultOp::not_equal

    fn DefaultOp::not_equal(x : DefaultOp, y : DefaultOp) -> Bool

    Identifier

    pub(all) enum Identifier {
    Num(
    BigInt
    )
    Alpha(String)
    }

    A version identifier.

    SemVer 2.0.0 allows two kinds of identifier: all digits, or a string drawn from [0-9A-Za-z-]. Numeric ones are held as arbitrary-precision integers, so a value such as 18446744073709551616 does not overflow.
    impl Eq for Identifier
    impl Hash for Identifier
    impl Show for Identifier

    Identifier::alpha

    fn Identifier::alpha(s : String) -> Identifier

    Identifier::as_bigint

    The integer value, or None for an alphanumeric identifier.

    Identifier::as_int

    fn Identifier::as_int(self : Identifier) -> Int?

    The value as an Int, or None if it is alphanumeric or out of range.

    Identifier::compare

    fn Identifier::compare(self : Identifier, other : Identifier) -> Int

    Identifier::equal

    fn Identifier::equal(self : Identifier, other : Identifier) -> Bool

    Identifier::hash

    fn Identifier::hash(self : Identifier) -> Int

    Identifier::hash_combine

    fn Identifier::hash_combine(self : Identifier, hasher : Hasher) -> Unit

    Identifier::is_numeric

    fn Identifier::is_numeric(self : Identifier) -> Bool

    Identifier::not_equal

    fn Identifier::not_equal(x : Identifier, y : Identifier) -> Bool

    Identifier::numeric

    fn Identifier::numeric(n : Int) -> Identifier

    Identifier::op_ge

    fn Identifier::op_ge(x : Identifier, y : Identifier) -> Bool

    Identifier::op_gt

    fn Identifier::op_gt(x : Identifier, y : Identifier) -> Bool

    Identifier::op_le

    fn Identifier::op_le(x : Identifier, y : Identifier) -> Bool

    Identifier::op_lt

    fn Identifier::op_lt(x : Identifier, y : Identifier) -> Bool

    Identifier::output

    fn Identifier::output(self : Identifier, logger : &Logger) -> Unit

    Identifier::to_repr

    Identifier::to_string

    fn Identifier::to_string(self : Identifier) -> String

    pub(all) enum Op {
    Eq
    Ne
    Gt
    Ge
    Lt
    Le
    }

    A comparison operator.
    impl Eq for Op
    impl Show for Op
    impl Debug for Op

    Op::equal

    fn Op::equal(self : Op, other : Op) -> Bool

    Op::not_equal

    fn Op::not_equal(x : Op, y : Op) -> Bool

    Op::output

    fn Op::output(self : Op, logger : &Logger) -> Unit

    Op::to_repr

    fn Op::to_repr(self : Op) ->
    Repr

    Op::to_string

    fn Op::to_string(self : Op) -> String

    PrereleasePolicy

    pub(all) enum PrereleasePolicy {
    SameTuple
    Always
    Never
    }

    How prereleases are matched against a range.

    The spec only orders prereleases against each other; it says nothing about how a range should treat them, so every ecosystem settled on its own convention.

    PrereleasePolicy::equal

    fn PrereleasePolicy::equal(self : PrereleasePolicy, other : PrereleasePolicy) -> Bool

    PrereleasePolicy::not_equal

    fn PrereleasePolicy::not_equal(x : PrereleasePolicy, y : PrereleasePolicy) -> Bool

    PrereleasePolicy::to_repr

    Range

    pub struct Range {
    sets : Array[ComparatorSet]
    policy : PrereleasePolicy
    }

    A version range: a disjunction (OR) of comparator sets.

    A Range is the parsed, normalised structure rather than the original string. ^1.2.3, for instance, becomes the set >=1.2.3, <2.0.0.
    impl Eq for Range
    impl Show for Range
    impl ToJson for Range

    Range::any

    fn Range::any() -> Range

    A range matching any stable version, the same as *.

    Range::equal

    fn Range::equal(self : Range, other : Range) -> Bool

    Range::is_any

    fn Range::is_any(self : Range) -> Bool

    Whether this is equivalent to "any version".

    Range::max_satisfying

    fn Range::max_satisfying(self : Range, versions : Array[Version]) -> Version?

    The highest satisfying version, by precedence, with build metadata breaking ties.

    Range::min_satisfying

    fn Range::min_satisfying(self : Range, versions : Array[Version]) -> Version?

    The lowest satisfying version.

    Range::not_equal

    fn Range::not_equal(x : Range, y : Range) -> Bool

    Range::output

    fn Range::output(self : Range, logger : &Logger) -> Unit

    Canonical range text: disjuncts joined with || .

    The output always re-parses under the same grammar (even strict reads *).

    Range::parse

    fn Range::parse(s : String, syntax? : Syntax) -> Range raise Refused

    Parse a version range.

    npm's grammar is the default; pass syntax= for Cargo, Ruby or strict.

    Range::policy

    fn Range::policy(self : Range) -> PrereleasePolicy

    The prerelease policy this range was parsed with.

    Range::satisfies

    fn Range::satisfies(self : Range, version : Version) -> Bool

    Does the version fall inside the range?

    The prerelease policy comes from the Syntax used to parse the range.

    Range::satisfies_with

    fn Range::satisfies_with(self : Range, version : Version, policy : PrereleasePolicy) -> Bool

    Does the version fall inside the range, with the policy overridden for this call?

    Range::satisfying

    fn Range::satisfying(self : Range, versions : Array[Version]) -> Array[Version]

    Every satisfying version, sorted ascending by precedence.

    Range::sets

    fn Range::sets(self : Range) -> ArrayView[ComparatorSet]

    The disjuncts, one per || branch.

    Range::to_json

    fn Range::to_json(self : Range) -> Json

    Range::to_repr

    Range::to_string

    fn Range::to_string(self : Range) -> String

    Canonical range text: disjuncts joined with || .

    The output always re-parses under the same grammar (even strict reads *).

    Release

    pub(all) enum Release {
    Major
    Minor
    Patch
    PreMajor
    PreMinor
    PrePatch
    Prerelease
    Stable
    }

    The kind of bump to apply.

    Several corners are easy to miss: the major bump of 1.0.0-pre is 1.0.0.
    impl Show for Release

    Release::output

    fn Release::output(self : Release, logger : &Logger) -> Unit

    Release::parse

    fn Release::parse(name : String) -> Release?

    Parse a bump name, for the CLI.

    Release::to_repr

    Release::to_string

    fn Release::to_string(self : Release) -> String

    Syntax

    pub(all) struct Syntax {
    allow_v_prefix : Bool
    allow_equals_prefix : Bool
    allow_build_metadata : Bool
    trim : Bool
    default_op : DefaultOp
    tilde_bump : TildeBump
    allow_caret : Bool
    allow_tilde : Bool
    allow_ne : Bool
    allow_or : Bool
    allow_hyphen_range : Bool
    allow_xrange : Bool
    allow_empty : Bool
    prerelease_policy : PrereleasePolicy
    }

    Range grammar settings.

    A version has one spelling in SemVer 2.0.0, but ranges are not in the spec, and different ecosystems settled on different rules. Their differences are settings here, so one parser can read several grammars.

    Switches that govern version literals:
    • allow_v_prefix: accept v1.2.3
    • allow_equals_prefix: accept =1.2.3 as a version literal
    • allow_build_metadata: accept a +build segment
    • trim: allow whitespace around a version literal

    Switches that govern range syntax:
    • default_op: what a bare version means (npm Exact, Cargo Caret)
    • tilde_bump: how ~ finds its upper bound (npm/Cargo Patch, Ruby LastSpecified)
    • allow_caret / allow_tilde: accept ^ / ~ (~> only under LastSpecified)
    • allow_ne: accept !=
    • allow_or: accept ||
    • allow_hyphen_range: accept 1.2.3 - 2.0.0
    • allow_xrange: accept the shorthand forms 1.x, 1.2.* and bare 1.2
    • allow_empty: whether an empty string means "any version"
    • prerelease_policy: how prereleases are matched
    impl Default for Syntax
    impl Eq for Syntax
    impl Show for Syntax

    Syntax::cargo

    fn Syntax::cargo() -> Syntax

    Cargo grammar: a bare version carries a caret (1.2.3 is ^1.2.3), with , or space for conjunctions and * / 1.* wildcards; no ||, no hyphen ranges, no v.

    Syntax::default

    fn Syntax::default() -> Syntax

    The default grammar is npm's.

    Syntax::equal

    fn Syntax::equal(self : Syntax, other : Syntax) -> Bool

    Syntax::from_name

    fn Syntax::from_name(name : String) -> Syntax?

    Look up a preset by name, for the CLI and config files.

    Syntax::name

    fn Syntax::name(self : Syntax) -> String?

    The preset name for this syntax, or None for a custom mix.

    Syntax::not_equal

    fn Syntax::not_equal(x : Syntax, y : Syntax) -> Bool

    Syntax::npm

    fn Syntax::npm() -> Syntax

    npm grammar: a bare version is exact, with ^, ~, ||, hyphen ranges, 1.x shorthand, a v prefix, and the empty string for "any".

    Syntax::output

    fn Syntax::output(self : Syntax, logger : &Logger) -> Unit

    Syntax::ruby

    fn Syntax::ruby() -> Syntax

    Ruby grammar: ~> is the pessimistic operator (drop the last segment, then bump), and != is allowed; no ^, ||, hyphen ranges or shorthand.

    Syntax::strict

    fn Syntax::strict() -> Syntax

    Strict SemVer 2.0.0 spelling: only = > >= < <=, joined by commas or spaces, with no ^, ~, ||, hyphen ranges, 1.x shorthand or v prefix.

    The one exception is *, which always means "any version": it is what Range::to_string prints for an empty range, so refusing it would break the round trip.

    Syntax::to_string

    fn Syntax::to_string(self : Syntax) -> String

    TildeBump

    pub(all) enum TildeBump {
    Patch
    LastSpecified
    }

    How ~ and ~> work out their upper bound.
    impl Eq for TildeBump

    TildeBump::equal

    fn TildeBump::equal(self : TildeBump, other : TildeBump) -> Bool

    TildeBump::not_equal

    fn TildeBump::not_equal(x : TildeBump, y : TildeBump) -> Bool

    Version

    A SemVer 2.0.0 version.

    The three numbers are arbitrary-precision integers, so 18446744073709551616.0.0 parses, prints and compares exactly, well past 64 bits.

    The fields are private and only Version::new / Version::parse can build one, so a version never carries a leading zero or a malformed identifier list.
    impl Compare for Version
    impl Eq for Version
    impl Hash for Version
    impl Show for Version
    impl ToJson for Version

    Version::build

    fn Version::build(self : Version) -> ArrayView[String]

    The build metadata identifiers: ["build", "5"] for 1.2.3+build.5.

    Version::compare

    fn Version::compare(self : Version, other : Version) -> Int

    Version::core

    fn Version::core(self : Version) -> Version

    Keep the three numbers only, dropping prerelease and build metadata.

    Version::core_string

    fn Version::core_string(self : Version) -> String

    The major.minor.patch form, without prerelease or build metadata.

    Version::equal

    fn Version::equal(self : Version, other : Version) -> Bool

    Full equality: the numbers, the prerelease and the build metadata all match.

    That differs from precedence: 1.0.0+a and 1.0.0+b are not equal, yet rank alike.

    Version::from_bigint

    Build a version from three arbitrary-precision integers.

    Version::hash

    fn Version::hash(self : Version) -> Int

    Version::hash_combine

    fn Version::hash_combine(self : Version, hasher : Hasher) -> Unit

    Version::inc

    fn Version::inc(self : Version, release : Release) -> Version

    Apply a bump.

    Version::is_prerelease

    fn Version::is_prerelease(self : Version) -> Bool

    Whether the version carries a prerelease.

    Version::is_stable

    fn Version::is_stable(self : Version) -> Bool

    Whether this is a stable release. Build metadata does not make it unstable.

    Version::major

    The major number.

    Version::minor

    The minor number.

    Version::new

    fn Version::new(major : Int, minor : Int, patch : Int) -> Version

    Build a version from three Ints, with no prerelease and no build metadata.

    Version::not_equal

    fn Version::not_equal(x : Version, y : Version) -> Bool

    Full equality: the numbers, the prerelease and the build metadata all match.

    That differs from precedence: 1.0.0+a and 1.0.0+b are not equal, yet rank alike.

    Version::op_ge

    fn Version::op_ge(x : Version, y : Version) -> Bool

    Version::op_gt

    fn Version::op_gt(x : Version, y : Version) -> Bool

    Version::op_le

    fn Version::op_le(x : Version, y : Version) -> Bool

    Version::op_lt

    fn Version::op_lt(x : Version, y : Version) -> Bool

    Version::output

    fn Version::output(self : Version, logger : &Logger) -> Unit

    The canonical form: major.minor.patch[-prerelease][+build].

    Version::parse

    fn Version::parse(s : String, syntax? : Syntax) -> Version raise Refused

    Parse a version.

    The npm grammar is the default, so v1.2.3, =1.2.3 and surrounding whitespace are all fine. Pass syntax=Syntax::strict() to hold to the spec exactly.

    Version::patch

    The patch number.

    Version::precedence_cmp

    fn Version::precedence_cmp(self : Version, other : Version) -> Int

    Compare precedence as laid out in SemVer 2.0.0 §11.

    Returns -1, 0 or 1. Build metadata takes no part: 1.0.0+a and 1.0.0+b share the same precedence.

    Version::prerelease

    fn Version::prerelease(self : Version) -> ArrayView[Identifier]

    The prerelease identifiers: ["alpha", 1] for 1.2.3-alpha.1.

    Version::same_core

    fn Version::same_core(self : Version, other : Version) -> Bool

    Whether the three numbers match, ignoring prerelease and build metadata.

    Version::to_json

    fn Version::to_json(self : Version) -> Json

    Version::to_repr

    Version::to_string

    fn Version::to_string(self : Version) -> String

    The canonical form: major.minor.patch[-prerelease][+build].

    satisfies

    fn satisfies(version : String, range : String, syntax? : Syntax) -> Bool raise Refused

    Convenience wrapper: parse two strings and check whether the version satisfies.

    Both strings are read with the same grammar, the version through Version::parse.