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:
| Text | npm | Cargo | ||
|---|---|---|---|---|
1.2.3 | exactly 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, <2 | space means AND | comma means AND | ||
| `a | b` | either range | not valid | |
1.0.0 - 2.0.0 | >=1.0.0, <=2.0.0 | not 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#
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#
pub enum RangeSyntaxThe syntax of a VersionRange: the npm range syntax or the Cargo requirement syntax.
Npm#
NpmThe semver package syntax of npm, with ||, spaces for AND, hyphen ranges, and exact bare
versions.
Cargo#
CargoThe Cargo syntax, with commas for AND and bare versions that mean ^.
VersionRange#
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>#
impl<'text> Copy for VersionRange<'text>Implementation VersionRange<'text>#
impl<'text> VersionRange<'text>Associated function parseNpm#
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.3or=1.2.3: exactly that version.1.2means>=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.2means<1.3.0and>1.2means>=1.3.0.^1.2.3: changes that do not modify the leftmost nonzero number,>=1.2.3 <2.0.0.^0.2.3means>=0.2.3 <0.3.0, and^0.0.3means>=0.0.3 <0.0.4.~1.2.3or~>1.2.3: patch changes,>=1.2.3 <1.3.0.~1means>=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, so1.2.3 - 2.3means>=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#
SemverErrorwith the reason and byte offset of the first problem, such asUnexpectedCharacterfor an unknown operator or a single|,LeadingZerofor01, andUnexpectedEndfor 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#
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.3or^1.2.3: changes that do not modify the leftmost nonzero number,>=1.2.3, <2.0.0.^0.2.3means>=0.2.3, <0.3.0,^0.0.3means=0.0.3, and^0means<1.0.0.~1.2.3: patch changes,>=1.2.3, <1.3.0.~1means>=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.2means>=1.2.0, <1.3.0and>1.2means>=1.3.0.1.*,1.2.*, or*alone: wildcards, also spelled withxorX.
Spaces can come before and after each comparator and after an operator.
Errors#
SemverErrorwith the reason and byte offset of the first problem:Emptyfor empty text or text of only spaces.UnexpectedCharacterfor a byte that is not valid there, such as a missing comma, avprefix,||, a hyphen range, or*together with other comparators.UnexpectedEnd,LeadingZero,NumberTooLarge, orEmptyIdentifierfor a bad version.TooManyComparatorsfor 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#
pub fn text(self: &Self) -> string<'text>Returns the text of the range, as it was parsed.
Method syntax#
pub fn syntax(self: &Self) -> RangeSyntaxReturns the syntax that the range was parsed with.
Method matches#
pub fn matches(self: &Self, version: &Version<'_>) -> boolReturns 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#
pub fn matchesIncludingPrerelease(self: &Self, version: &Version<'_>) -> boolReturns 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#
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>#
impl<'text> Display for VersionRange<'text>Operation display#
effect fn display(self: &Self, formatter: &mut Formatter) -> ()
! WriterError
? &mut WriterWrites the text of the range, as it was parsed. Writer failures propagate unchanged.
Implementation Debug for VersionRange<'text>#
impl<'text> Debug for VersionRange<'text>Operation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
! WriterError
? &mut WriterWrites 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.