silk.semver.range.

Version ranges in the npm and Cargo syntaxes: parse a requirement and test versions against it.

When to use#

Use VersionRange.parseNpm for ranges from package.json, such as ^1.2.3 || >=2.1.0 <3, and VersionRange.parseCargo for requirements from Cargo.toml, such as >=1.2, <1.5. Then call VersionRange.matches for each version, or VersionRange.bestMatch to pick the highest one that matches.

Details#

A VersionRange borrows its text and does not allocate. Parsing checks the whole text, and each match reads the text again, so a match costs time in proportion to the length of the range.

The two syntaxes give different meanings to the same text:

TextnpmCargo
1.2.3exactly 1.2.3^1.2.3
^1.2.3>=1.2.3, <2.0.0>=1.2.3, <2.0.0
~1.2.3>=1.2.3, <1.3.0>=1.2.3, <1.3.0
1.2.x, 1.2.*>=1.2.0, <1.3.0>=1.2.0, <1.3.0
>=1 <2, >=1, <2space means ANDcomma means AND
`ab`either rangenot valid
1.0.0 - 2.0.0>=1.0.0, <=2.0.0not valid

In both syntaxes, a pre-release version such as 1.3.0-beta matches only if a comparator of the range names a pre-release of the same MAJOR.MINOR.PATCH. So ^1.2.3 does not match 1.3.0-beta, but >=1.3.0-alpha matches 1.3.0-beta. Use VersionRange.matchesIncludingPrerelease to drop that rule.

Gotchas#

The npm parser follows the strict mode of the semver package: loose forms such as 01.2.3 or 1.2.3beta are errors. Build metadata in a range is accepted only after a full version, and it does not change the result.

Examples#

Pick the newest compatible version#

silk
import silk.core.option { Option }
import silk.core.result { Result }
import silk.semver.range { VersionRange }
import silk.semver.version { Version, SemverError }

pub fn main() -> i32 {
  let range = match VersionRange.parseNpm("^1.2.0") {
    Result<VersionRange, SemverError>.Success { value } => value
    Result<VersionRange, SemverError>.Failure { error: _ } => {
      return 1
    }
  }
  let versions = [Version.make(1, 1, 9), Version.make(1, 4, 2), Version.make(2, 0, 0)]
  return match range.bestMatch(&versions) {
    Option<usize>.Some { value } => {
      if value != 1 {
        return 1
      }
      return 0
    }
    Option<usize>.None => 1
  }
}

See also#

The npm range grammar and the Cargo requirement syntax define the two syntaxes.

Import as VersionRange with import silk.semver.range { VersionRange }.

Public declarations: 2.

RangeSyntax#

silk
pub enum RangeSyntax

The syntax of a VersionRange: the npm range syntax or the Cargo requirement syntax.

Npm#

silk
Npm

The semver package syntax of npm, with ||, spaces for AND, hyphen ranges, and exact bare versions.

Cargo#

silk
Cargo

The Cargo syntax, with commas for AND and bare versions that mean ^.

VersionRange#

silk
pub struct VersionRange<'text>

A parsed version range that borrows its text.

Details#

Make one with VersionRange.parseNpm or VersionRange.parseCargo. The value is Copy, does not allocate, and cannot live longer than its text. Each match reads the text again, so it costs time in proportion to the length of the range.

A pre-release version matches only if a comparator of the range names a pre-release of the same MAJOR.MINOR.PATCH. VersionRange.matchesIncludingPrerelease drops that rule.

Implementation Copy for VersionRange<'text>#

silk
impl<'text> Copy for VersionRange<'text>

Implementation VersionRange<'text>#

silk
impl<'text> VersionRange<'text>

Associated function parseNpm#

silk
pub fn parseNpm(text: string<'text>) -> Result<VersionRange<'text>, SemverError>

Parses a range in the npm syntax of the semver package, such as ^1.2.3 || >=2.1.0 <3.

Details#

|| separates alternatives, and spaces separate comparators that must all match. Each comparator is one of these forms. M, m, and p are numbers, and any of them can be x, X, or *, or be left out, to match any value.

  • 1.2.3 or =1.2.3: exactly that version. 1.2 means >=1.2.0 <1.3.0, and * or empty text means any version.

  • >1.2.3, >=1.2.3, <1.2.3, <=1.2.3: comparisons. A partial version counts as its range, so <=1.2 means <1.3.0 and >1.2 means >=1.3.0.

  • ^1.2.3: changes that do not modify the leftmost nonzero number, >=1.2.3 <2.0.0. ^0.2.3 means >=0.2.3 <0.3.0, and ^0.0.3 means >=0.0.3 <0.0.4.

  • ~1.2.3 or ~>1.2.3: patch changes, >=1.2.3 <1.3.0. ~1 means >=1.0.0 <2.0.0.

  • 1.2.3 - 2.3.4: inclusive, >=1.2.3 <=2.3.4. A partial upper end counts as its range, so 1.2.3 - 2.3 means >=1.2.3 <2.4.0.

An operator can have spaces after it, and a version can have a v prefix. Upper bounds made from partial versions exclude the pre-releases of the bound, so ^1.2.3 does not match 2.0.0-rc.1.

Errors#

  • SemverError with the reason and byte offset of the first problem, such as UnexpectedCharacter for an unknown operator or a single |, LeadingZero for 01, and UnexpectedEnd for an operator with no version after it.

Gotchas#

A bare 1.2.3 matches only 1.2.3 in npm, but means ^1.2.3 in Cargo. Loose forms that the semver package accepts only with its loose option, such as 01.2.3, are errors.

Associated function parseCargo#

silk
pub fn parseCargo(text: string<'text>) -> Result<VersionRange<'text>, SemverError>

Parses a requirement in the Cargo syntax, such as >=1.2, <1.5 or 0.4.

Details#

Commas separate comparators that must all match. Each comparator is an optional operator and a version, where the minor and patch numbers can be left out:

  • 1.2.3 or ^1.2.3: changes that do not modify the leftmost nonzero number, >=1.2.3, <2.0.0. ^0.2.3 means >=0.2.3, <0.3.0, ^0.0.3 means =0.0.3, and ^0 means <1.0.0.

  • ~1.2.3: patch changes, >=1.2.3, <1.3.0. ~1 means >=1.0.0, <2.0.0.

  • =1.2.3, >1.2.3, >=1.2.3, <1.2.3, <=1.2.3: comparisons. A partial version counts as its range, so =1.2 means >=1.2.0, <1.3.0 and >1.2 means >=1.3.0.

  • 1.*, 1.2.*, or * alone: wildcards, also spelled with x or X.

Spaces can come before and after each comparator and after an operator.

Errors#

  • SemverError with the reason and byte offset of the first problem:

    • Empty for empty text or text of only spaces.

    • UnexpectedCharacter for a byte that is not valid there, such as a missing comma, a v prefix, ||, a hyphen range, or * together with other comparators.

    • UnexpectedEnd, LeadingZero, NumberTooLarge, or EmptyIdentifier for a bad version.

    • TooManyComparators for more than 32 comparators, which Cargo also rejects.

Gotchas#

A bare version means ^, so 1.2.3 matches 1.9.0. Write =1.2.3 for exactly one version.

Method text#

silk
pub fn text(self: &Self) -> string<'text>

Returns the text of the range, as it was parsed.

Method syntax#

silk
pub fn syntax(self: &Self) -> RangeSyntax

Returns the syntax that the range was parsed with.

Method matches#

silk
pub fn matches(self: &Self, version: &Version<'_>) -> bool

Returns true if version is in the range.

Details#

A pre-release version matches only if it is in the range and a comparator in the same alternative names a pre-release with the same MAJOR.MINOR.PATCH. So >=1.2.3-alpha matches 1.2.3-beta but not 1.2.4-beta, and ^1.2.3 matches no pre-release. Build metadata of version does not count.

Method matchesIncludingPrerelease#

silk
pub fn matchesIncludingPrerelease(self: &Self, version: &Version<'_>) -> bool

Returns true if version is in the range, with pre-releases treated as other versions.

Details#

For an npm range, this is the includePrerelease option of the semver package: the bounds of ^, ~, x, and hyphen ranges include their pre-releases, so ^1.2.3 matches 1.4.0-beta and 1.x matches 1.0.0-rc.1. For a Cargo range, only the rule that a pre-release needs a comparator with a pre-release of the same MAJOR.MINOR.PATCH is dropped. Build metadata of version does not count.

Method bestMatch#

silk
pub fn bestMatch(self: &Self, versions: &[Version<'_>]) -> Option<usize>

Returns the position of the highest version in versions that the range matches, or None.

Details#

Matching uses VersionRange.matches, so pre-releases match only by that rule. Versions compare by precedence and then build metadata, and the first of two equal versions wins.

Implementation Display for VersionRange<'text>#

silk
impl<'text> Display for VersionRange<'text>

Operation display#

silk
effect fn display(self: &Self, formatter: &mut Formatter) -> ()
    ! WriterError
    ? &mut Writer

Writes the text of the range, as it was parsed. Writer failures propagate unchanged.

Implementation Debug for VersionRange<'text>#

silk
impl<'text> Debug for VersionRange<'text>

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
    ! WriterError
    ? &mut Writer

Writes the text of the range, as it was parsed. Writer failures propagate unchanged.

Category: Data formats.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page