A Go implementation of the VERS specification for version range parsing and comparison across package ecosystems.
go get github.com/git-pkgs/vers
The VERS URI format provides a universal way to express version ranges:
package main import ( "fmt" "github.com/git-pkgs/vers" ) func main() { // Parse a VERS URI r, _ := vers.Parse("vers:npm/>=1.0.0|<2.0.0") fmt.Println(r.Contains("1.5.0")) // true fmt.Println(r.Contains("2.0.0")) // false fmt.Println(r.Contains("0.9.0")) // false }
Each package ecosystem has its own version constraint syntax. This library parses them all:
// npm: caret, tilde, x-ranges, hyphen ranges r, _ := vers.ParseNative("^1.2.3", "npm") r, _ = vers.ParseNative("~1.2.3", "npm") r, _ = vers.ParseNative("1.0.0 - 2.0.0", "npm") r, _ = vers.ParseNative(">=1.0.0 <2.0.0", "npm") // Composer: caret, tilde, wildcard, hyphen, AND and OR ranges r, _ = vers.ParseNative("^1.2.3", "composer") r, _ = vers.ParseNative("~1.2", "composer") r, _ = vers.ParseNative(">=1.0 <1.1 || >=1.2", "composer") // Ruby gems: pessimistic operator r, _ = vers.ParseNative("~> 1.2", "gem") r, _ = vers.ParseNative(">= 1.0, < 2.0", "gem") // Python: compatible release, exclusions r, _ = vers.ParseNative("~=1.4.2", "pypi") r, _ = vers.ParseNative(">=1.0.0,<2.0.0,!=1.5.0", "pypi") r, _ = vers.ParseNative("==2.32.4", "pypi") // Pub: caret and traditional intersection syntax r, _ = vers.ParseNative("^1.2.3", "pub") r, _ = vers.ParseNative(">=1.2.3 <2.0.0", "pub") // Maven/NuGet: bracket notation r, _ = vers.ParseNative("[1.0,2.0)", "maven") r, _ = vers.ParseNative("[1.0,)", "maven") // Cargo: same syntax as npm r, _ = vers.ParseNative("^1.2.3", "cargo") // Go: comma-separated constraints r, _ = vers.ParseNative(">=1.0.0,<2.0.0", "go") // Debian: >> and << operators r, _ = vers.ParseNative(">> 1.0", "deb") // RPM r, _ = vers.ParseNative(">= 1.0", "rpm") // Conan, OpenSSL, and Nginx r, _ = vers.ParseNative("^1.2", "conan") r, _ = vers.ParseNative("1.1.1w, 3.0.0", "openssl") r, _ = vers.ParseNative("0.8.40+", "nginx")
// Using VERS URI (empty scheme) ok, _ := vers.Satisfies("1.5.0", "vers:npm/>=1.0.0|<2.0.0", "") fmt.Println(ok) // true // Using native syntax ok, _ = vers.Satisfies("1.5.0", "^1.0.0", "npm") fmt.Println(ok) // true
vers.Compare("1.2.3", "1.2.4") // -1 (a < b) vers.Compare("2.0.0", "1.9.9") // 1 (a > b) vers.Compare("1.0.0", "1.0.0") // 0 (equal) // Prerelease versions sort before stable vers.Compare("1.0.0", "1.0.0-alpha") // 1 (stable > prerelease) // Use ecosystem-specific ordering where versions are not SemVer vers.CompareWithScheme("1.0~rc1", "1.0", "deb") // -1 vers.CompareWithScheme("1.0.beta1", "1.0", "gem") // -1
vers.Valid("1.2.3") // true vers.Valid("invalid") // false v, _ := vers.Normalize("1") // "1.0.0" v, _ = vers.Normalize("1.2") // "1.2.0" v, _ = vers.Normalize("1.2.3") // "1.2.3" vers.ValidWithScheme("1:2.3.4-1", "deb") // true v, _ = vers.NormalizeWithScheme("01!02.0RC1", "pypi") // "1!2.0rc1"
MinimumVersion returns an inclusive lower bound only when that exact version
is part of the range. TagCandidates trims surrounding whitespace, preserves
the remaining spelling, adds its scheme-normalized form, and tries the common
optional v prefix.
r, _ := vers.ParseNative("^1.2", "npm") baseline, ok := r.MinimumVersion() // "1.2", true tags, _ := vers.TagCandidates(baseline, "npm") // 1.2, v1.2, 1.2.0, v1.2.0
// Exact version r := vers.Exact("1.2.3") // Greater/less than r = vers.GreaterThan("1.0.0", true) // >=1.0.0 r = vers.GreaterThan("1.0.0", false) // >1.0.0 r = vers.LessThan("2.0.0", false) // <2.0.0 // Unbounded (matches all) r = vers.Unbounded() // Combine ranges r1, _ := vers.ParseNative(">=1.0.0", "npm") r2, _ := vers.ParseNative("<2.0.0", "npm") intersection := r1.Intersect(r2) // >=1.0.0 AND <2.0.0 union := r1.Union(r2) // >=1.0.0 OR <2.0.0 // Add exclusions r = r.Exclude("1.5.0")
r, _ := vers.ParseNative("==2.32.4", "pypi") version, exact := r.ExactVersion() // "2.32.4", true r, _ = vers.ParseNative("^2.32.4", "npm") _, exact = r.ExactVersion() // false
r, _ := vers.ParseNative("^1.2.3", "npm") uri := vers.ToVersString(r, "npm") // vers:npm/>=1.2.3|<2.0.0 // Unbounded range r = vers.Unbounded() uri = vers.ToVersString(r, "npm") // vers:npm/*
| Ecosystem | Scheme | Example Syntax |
|---|---|---|
| npm | npm |
^1.2.3, ~1.2.3, >=1.0.0 <2.0.0, 1.x, 1.0.0 - 2.0.0 |
| Composer | composer |
^1.2.3, ~1.2, 1.2.*, >=1.0 <2.0, ` |
| RubyGems | gem, rubygems |
~> 1.2, >= 1.0, < 2.0 |
| PyPI | pypi |
~=1.4.2, >=1.0.0,<2.0.0, !=1.5.0 |
| Pub | pub |
^1.2.3, >=1.2.3 <2.0.0, any |
| Maven | maven |
[1.0,2.0), (1.0,2.0], [1.0,), [1.0] |
| NuGet | nuget |
Same as Maven |
| Cargo | cargo |
Same as npm |
| Go | go, golang |
>=1.0.0,<2.0.0 |
| Hex | hex, elixir |
~> 1.2, >= 1.0 and < 2.0 |
| Debian | deb, debian |
>> 1.0, << 2.0, >= 1.0 |
| RPM | rpm |
>= 1.0, <= 2.0 |
| Conan | conan |
^1.2, ~1.2, >1 <2, ` |
| OpenSSL | openssl |
1.1.1w, 1.1.1w, 3.0.0 |
| Nginx | nginx |
0.8.40+, 0.7.52-0.8.39 |
Version comparison additionally supports semver, apk/alpine, alpm,
gentoo, intdot, lexicographic, and datetime.
go test ./...go test -v ./...go test -v -run TestParseNpmRangego test -cover ./...go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outgo test -bench=. -benchmemRun specific benchmarks:
go test -bench=BenchmarkContains -benchmemMIT.