Constrain versions with ranges
A range is a set of versions. This guide shows how to build ranges three ways — from a native range string, from caret/tilde shorthands, and explicitly — and how the prerelease-in-range rule affects membership.
Ranges are Ranges!V values; you test membership with satisfies. The examples use SemVer; the scheme catalogue notes each scheme's native-range grammar.
Parse the ecosystem's native range grammar
parseNativeRange reads the scheme's own syntax. For SemVer that is the node-semver grammar: comparators (>=, <, …), AND-by-space, unions with ||, hyphen ranges, and wildcards:
auto r = SemVer.parseNativeRange(">=1.2.0 <2.0.0").value;
writeln("1.5.0 in >=1.2.0 <2.0.0: ", v("1.5.0").satisfies(r)); // true
writeln("2.0.0 in >=1.2.0 <2.0.0: ", v("2.0.0").satisfies(r)); // false(v here is a tiny helper, auto v(string s) => SemVer.parse(s).value;, used to keep the examples short.)
Caret and tilde
caret and tilde desugar the npm shorthands directly from a version, without going through the string grammar. They are gated on the SemVer triple at compile time, so they are only available on SemVer-shaped schemes:
import sparkles.versions.operations : caret, tilde;
auto c = caret(v("1.2.3")); // ^1.2.3 → [1.2.3, 2.0.0) (compatible within major)
auto t = tilde(v("1.2.3")); // ~1.2.3 → [1.2.3, 1.3.0) (compatible within minor)
writeln("1.9.9 in ^1.2.3: ", v("1.9.9").satisfies(c)); // true
writeln("1.3.0 in ~1.2.3: ", v("1.3.0").satisfies(t)); // false — 1.3.0 is the exclusive upper boundThe prerelease-in-range rule
This is the rule that surprises people. A prerelease only satisfies a range when some comparator in that range names a prerelease of the same major.minor.patch triple. A prerelease is not admitted just because it falls numerically inside the interval:
auto stable = SemVer.parseNativeRange(">=1.2.0").value;
writeln("1.3.0-beta.1 in >=1.2.0: ", v("1.3.0-beta.1").satisfies(stable));
// → false: 1.3.0-beta.1 is numerically ≥ 1.2.0, but no comparator names a 1.3.0 prerelease
auto withPre = SemVer.parseNativeRange(">=1.2.0-alpha").value;
writeln("1.2.0-beta in >=1.2.0-alpha: ", v("1.2.0-beta").satisfies(withPre));
// → true: the comparator names a prerelease of the same 1.2.0 tripleThis matches node-semver. For why it works this way, see Prerelease in ranges. The rule is statically inert for schemes that do not model prereleases (e.g. Tiny, Generic), where satisfies is just membership.
Complete example
#!/usr/bin/env dub
/+ dub.sdl:
name "constrain_with_ranges"
dependency "sparkles:versions" version="*"
+/
import std.stdio : writeln;
import sparkles.versions.schemes.semver : SemVer;
import sparkles.versions.operations : satisfies, caret, tilde;
void main()
{
auto v(string s) => SemVer.parse(s).value;
// Native npm range grammar.
auto r = SemVer.parseNativeRange(">=1.2.0 <2.0.0").value;
writeln("1.5.0 in >=1.2.0 <2.0.0: ", v("1.5.0").satisfies(r));
writeln("2.0.0 in >=1.2.0 <2.0.0: ", v("2.0.0").satisfies(r));
// Caret and tilde desugarings, built directly from a version.
auto c = caret(v("1.2.3")); // [1.2.3, 2.0.0)
auto t = tilde(v("1.2.3")); // [1.2.3, 1.3.0)
writeln("1.9.9 in ^1.2.3: ", v("1.9.9").satisfies(c));
writeln("1.3.0 in ~1.2.3: ", v("1.3.0").satisfies(t));
// Prerelease-in-range rule.
auto stable = SemVer.parseNativeRange(">=1.2.0").value;
writeln("1.3.0-beta.1 in >=1.2.0: ", v("1.3.0-beta.1").satisfies(stable));
auto withPre = SemVer.parseNativeRange(">=1.2.0-alpha").value;
writeln("1.2.0-beta in >=1.2.0-alpha: ", v("1.2.0-beta").satisfies(withPre));
}1.5.0 in >=1.2.0 <2.0.0: true
2.0.0 in >=1.2.0 <2.0.0: false
1.9.9 in ^1.2.3: true
1.3.0 in ~1.2.3: false
1.3.0-beta.1 in >=1.2.0: false
1.2.0-beta in >=1.2.0-alpha: trueNotes
- Ranges are sets.
Ranges!Vsupportsintersection,union_,complement,contains, and the derivedisDisjoint/subsetOf— the full set algebra. See the concepts reference. - One range type for every scheme. There is no
NpmRangeorPypiRange; a scheme's range is justRanges!ThatVersion. What differs is the native grammar each scheme parses. - Emit as VERS. A range's
toStringproduces VERS constraint syntax; see VERS and pURL interop.