Skip to content

Commit

Permalink
add inequality specifiers (#441)
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferC committed Jul 3, 2018
1 parent 98fa5fa commit d444092
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 8 deletions.
12 changes: 12 additions & 0 deletions stdlib/Pkg/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ Similar to other package managers, the Julia package manager respects [semantic
As an example, a version specifier is given as e.g. `1.2.3` is therefore assumed to be compatible with the versions `[1.2.3 - 2.0.0)` where `)` is a non-inclusive upper bound.
More specifically, a version specifier is either given as a **caret specifier**, e.g. `^1.2.3` or a **tilde specifier** `~1.2.3`.
Caret specifiers are the default and hence `1.2.3 == ^1.2.3`. The difference between a caret and tilde is described in the next section.
The intersection of multiple version specifiers can be formed by comma separating indiviual version specifiers.

##### Caret specifiers

Expand Down Expand Up @@ -740,6 +741,17 @@ This gives the following example.
~1 = [1.0.0, 2.0.0)
```

#### Inequality specifiers

Inequalities can also be used to specify version ranges:

```
>= 1.2.3 = [1.2.3, ∞)
≥ 1.2.3 = [1.2.3, ∞)
= 1.2.3 = [1.2.3, 1.2.3]
< 1.2.3 = [0.0.0, 1.2.2]
```


## Precompiling a project

Expand Down
62 changes: 54 additions & 8 deletions stdlib/Pkg/src/versions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -247,29 +247,35 @@ Base.show(io::IO, s::VersionSpec) = print(io, "VersionSpec(\"", s, "\")")
# Semver notation #
###################

const ver_reg = r"^([~^]?)?[v]?([0-9]+?)(?:\.([0-9]+?))?(?:\.([0-9]+?))?$"

function semver_spec(s::String)
s = replace(s, " " => "")
ranges = VersionRange[]
for ver in split(s, ',')
push!(ranges, semver_interval(strip(ver)))
range = nothing
found_match = false
for (ver_reg, f) in ver_regs
if occursin(ver_reg, ver)
range = f(match(ver_reg, ver))
found_match = true
break
end
end
found_match || error("invalid version specifier: $s")
push!(ranges, range)
end
return VersionSpec(ranges)
end

function semver_interval(s::AbstractString)
if !occursin(ver_reg, s)
error("invalid version specifier: $s")
end
m = match(ver_reg, s)
function semver_interval(m::RegexMatch)
@assert length(m.captures) == 4
n_significant = count(x -> x !== nothing, m.captures) - 1
typ, _major, _minor, _patch = m.captures
major = parse(Int, _major)
minor = (n_significant < 2) ? 0 : parse(Int, _minor)
patch = (n_significant < 3) ? 0 : parse(Int, _patch)
if n_significant == 3 && major == 0 && minor == 0 && patch == 0
error("invalid version: $s")
error("invalid version: \"0.0.0\"")
end
# Default type is :caret
vertyp = (typ == "" || typ == "^") ? :caret : :tilde
Expand All @@ -296,3 +302,43 @@ function semver_interval(s::AbstractString)
end
end
end

const _inf = Pkg.Types.VersionBound("*")
function inequality_interval(m::RegexMatch)
@assert length(m.captures) == 4
typ, _major, _minor, _patch = m.captures
n_significant = count(x -> x !== nothing, m.captures) - 1
major = parse(Int, _major)
minor = (n_significant < 2) ? 0 : parse(Int, _minor)
patch = (n_significant < 3) ? 0 : parse(Int, _patch)
if n_significant == 3 && major == 0 && minor == 0 && patch == 0
error("invalid version: $s")
end
v = VersionBound(major, minor, patch)
if typ == "<"
nil = VersionBound(0, 0, 0)
if v[3] == 0
if v[2] == 0
v1 = VersionBound(v[1]-1)
else
v1 = VersionBound(v[1], v[2]-1)
end
else
v1 = VersionBound(v[1], v[2], v[3]-1)
end
return VersionRange(nil, v1)
elseif typ == "="
return VersionRange(v)
elseif typ == ">=" || typ == ""
return VersionRange(v, _inf)
else
error("invalid prefix $typ")
end
end

const version = "v?([0-9]+?)(?:\\.([0-9]+?))?(?:\\.([0-9]+?))?"
const ver_regs =
[
Regex("^([~^]?)?$version\$") => semver_interval, # 0.5 ^0.4 ~0.3.2
Regex("^((?:≥)|(?:>=)|(?:=)|(?:<)|(?:=))v?$version\$") => inequality_interval,# < 0.2 >= 0.5,2
]
31 changes: 31 additions & 0 deletions stdlib/Pkg/test/pkg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,37 @@ import Pkg.Types: semver_spec, VersionSpec
@test v"0.0.0" in semver_spec("0.0")
@test v"0.0.99" in semver_spec("0.0")
@test !(v"0.1.0" in semver_spec("0.0"))

@test semver_spec("<1.2.3") == VersionSpec("0.0.0 - 1.2.2")
@test semver_spec("<1.2") == VersionSpec("0.0.0 - 1.1")
@test semver_spec("<1") == VersionSpec("0.0.0 - 0")
@test semver_spec("<2") == VersionSpec("0.0.0 - 1")
@test semver_spec("<0.2.3") == VersionSpec("0.0.0 - 0.2.2")
@test semver_spec("<2.0.3") == VersionSpec("0.0.0 - 2.0.2")
@test v"0.2.3" in semver_spec("<0.2.4")
@test !(v"0.2.4" in semver_spec("<0.2.4"))

@test semver_spec("=1.2.3") == VersionSpec("1.2.3")
@test semver_spec("=1.2") == VersionSpec("1.2.0")
@test semver_spec(" =1") == VersionSpec("1.0.0")
@test v"1.2.3" in semver_spec("=1.2.3")
@test !(v"1.2.4" in semver_spec("=1.2.3"))
@test !(v"1.2.2" in semver_spec("=1.2.3"))

@test semver_spec("≥1.3.0") == semver_spec(">=1.3.0")


@test semver_spec(">= 1.2.3") == VersionSpec("1.2.3-*")
@test semver_spec(">=1.2 ") == VersionSpec("1.2.0-*")
@test semver_spec(" >= 1") == VersionSpec("1.0.0-*")
@test v"1.0.0" in semver_spec(">=1")
@test v"0.0.1" in semver_spec(">=0")
@test v"1.2.3" in semver_spec(">=1.2.3")
@test !(v"1.2.2" in semver_spec(">=1.2.3"))

@test_throws ErrorException semver_spec("^^0.2.3")
@test_throws ErrorException semver_spec("^^0.2.3.4")
@test_throws ErrorException semver_spec("0.0.0")
end

# TODO: Should rewrite these tests not to rely on internals like field names
Expand Down

0 comments on commit d444092

Please sign in to comment.