Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added 'compatible-with' (caret ^) operator #8

Merged
merged 4 commits into from
May 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ This library allows the use of an even more relaxed semver specification using t
- If the parsed string is a valid semver (following the rules above), then the `RelaxedVersion` will behave exactly as a normal `Version` object
- if the parsed string is **not** a valid semver, then the string is kept as-is inside the `RelaxedVersion` object as a custom version string
- when comparing two `RelaxedVersion` the rule is simple: if both are valid semver, the semver rules applies; if both are custom version string they are compared as alphanumeric strings; if one is valid semver and the other is a custom version string the valid semver is always greater
- two `RelaxedVersion` are compatible (by the `CompatibleWith` operation) only if
- they are equal
- they are both valid semver and they are compatible as per semver specification

The `RelaxedVersion` object is basically made to allow systems that do not use semver to soft transition to semantic versioning, because it allows an intermediate period where the invalid version is still tolerated.

Expand All @@ -45,6 +48,7 @@ The following operators are supported:
| `>=` | greater than or equal to |
| `<` | less than |
| `<=` | less than or equal to |
| `^` | compatible-with |
| `!` | NOT |
| `&&` | AND |
| `\|\|` | OR |
Expand All @@ -55,23 +59,30 @@ The following operators are supported:
Given the following releases of a dependency:

- `0.1.0`
cmaglie marked this conversation as resolved.
Show resolved Hide resolved
- `0.1.1`
- `0.2.0`
- `1.0.0`
- `2.0.0`
- `2.0.5`
- `2.0.6`
- `2.1.0`

constraints would resolve as follows:

| Constraint | Resolution |
| -------------------------------- | ---------- |
| `=1.0.0` | `1.0.0` |
| `>1.0.0` | `2.1.0` |
| `>=1.0.0` | `2.1.0` |
| `<2.0.0` | `1.0.0` |
| `<=2.0.0` | `2.0.0` |
| `!=1.0.0` | `2.1.0` |
| `>1.0.0 && <2.1.0` | `2.0.0` |
| `<1.0.0 \|\| >2.0.0` | `2.1.0` |
| `(>0.1.0 && <2.0.0) \|\| >2.1.0` | `1.0.0` |
- `3.0.0`

constraints conditions would match as follows:

| The following condition... | will match with versions... |
| -------------------------------- | ---------------------------------------------------- |
| `=1.0.0` | `1.0.0` |
| `>1.0.0` | `2.0.0`, `2.0.5`, `2.0.6`, `2.1.0`, `3.0.0` |
| `>=1.0.0` | `1.0.0`, `2.0.0`, `2.0.5`, `2.0.6`, `2.1.0`, `3.0.0` |
| `<2.0.0` | `0.1.0`, `0.1.1`, `0.2.0`, `1.0.0` |
| `<=2.0.0` | `0.1.0`, `0.1.1`, `0.2.0`, `1.0.0`, `2.0.0` |
| `!=1.0.0` | `0.1.0`, `2.0.0`, `2.0.5`, `2.0.6`, `2.1.0`, `3.0.0` |
| `>1.0.0 && <2.1.0` | `2.0.0`, `2.0.5`, `2.0.6` |
| `<1.0.0 \|\| >2.0.0` | `0.1.0`, `2.0.5`, `2.0.6`, `2.1.0`, `3.0.0` |
| `(>0.1.0 && <2.0.0) \|\| >2.0.5` | `1.0.0`, `2.0.6`, `2.1.0`, `3.0.0` |
| `^2.0.5` | `2.0.5`, `2.0.6`, `2.1.0` |
cmaglie marked this conversation as resolved.
Show resolved Hide resolved
| `^0.1.0` | `0.1.0`, `0.1.1` |

## Json parsable

Expand Down
20 changes: 20 additions & 0 deletions constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ func ParseConstraint(in string) (Constraint, error) {
return nil, err
}
return &Equals{v}, nil
case '^':
v, err := version()
if err != nil {
return nil, err
}
return &CompatibleWith{v}, nil
case '>':
if peek() == '=' {
next()
Expand Down Expand Up @@ -273,6 +279,20 @@ func (gte *GreaterThanOrEqual) String() string {
return ">=" + gte.Version.String()
}

// CompatibleWith is the "compatible with" (^) constraint
type CompatibleWith struct {
Version *Version
}

// Match returns true if v satisfies the condition
func (cw *CompatibleWith) Match(v *Version) bool {
return cw.Version.CompatibleWith(v)
}

func (cw *CompatibleWith) String() string {
return "^" + cw.Version.String()
}

// Or will match if ANY of the Operands Constraint will match
type Or struct {
Operands []Constraint
Expand Down
19 changes: 19 additions & 0 deletions constraints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ func TestConstraints(t *testing.T) {
require.True(t, notOr.Match(v("2.0.0")))
require.False(t, notOr.Match(v("2.1.0")))
require.Equal(t, "!(>2.0.0 || <=1.0.0)", notOr.String())

comp := &CompatibleWith{v("1.3.4-rc.3")}
require.False(t, comp.Match(v("1.2.3")))
require.False(t, comp.Match(v("1.3.2")))
require.False(t, comp.Match(v("1.2.3")))
require.False(t, comp.Match(v("1.3.4-rc.1")))
require.True(t, comp.Match(v("1.3.4-rc.5")))
require.True(t, comp.Match(v("1.3.4")))
require.True(t, comp.Match(v("1.3.6")))
require.True(t, comp.Match(v("1.4.0")))
require.True(t, comp.Match(v("1.4.5")))
require.True(t, comp.Match(v("1.4.5-rc.2")))
require.False(t, comp.Match(v("2.0.0")))
}

func TestConstraintsParser(t *testing.T) {
Expand All @@ -92,10 +105,15 @@ func TestConstraintsParser(t *testing.T) {
{">1.3.0", ">1.3.0"},
{"<=1.3.0", "<=1.3.0"},
{"<1.3.0", "<1.3.0"},
{"^1.3.0", "^1.3.0"},
{" ^1.3.0", "^1.3.0"},
{"^1.3.0 ", "^1.3.0"},
{" ^1.3.0 ", "^1.3.0"},
{"(=1.4.0)", "=1.4.0"},
{"!(=1.4.0)", "!(=1.4.0)"},
{"!(((=1.4.0)))", "!(=1.4.0)"},
{"=1.2.4 && =1.3.0", "(=1.2.4 && =1.3.0)"},
{"=1.2.4 && ^1.3.0", "(=1.2.4 && ^1.3.0)"},
{"=1.2.4 && =1.3.0 && =1.2.0", "(=1.2.4 && =1.3.0 && =1.2.0)"},
{"=1.2.4 && =1.3.0 || =1.2.0", "((=1.2.4 && =1.3.0) || =1.2.0)"},
{"=1.2.4 || =1.3.0 && =1.2.0", "(=1.2.4 || (=1.3.0 && =1.2.0))"},
Expand Down Expand Up @@ -124,6 +142,7 @@ func TestConstraintsParser(t *testing.T) {
">>1.0.0",
">1.0.0 =2.0.0",
">1.0.0 &",
"^1.1.1.1",
"!1.0.0",
">1.0.0 && 2.0.0",
">1.0.0 | =2.0.0",
Expand Down
8 changes: 8 additions & 0 deletions relaxed_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,11 @@ func (v *RelaxedVersion) GreaterThan(u *RelaxedVersion) bool {
func (v *RelaxedVersion) GreaterThanOrEqual(u *RelaxedVersion) bool {
return v.CompareTo(u) >= 0
}

// CompatibleWith returns true if the RelaxedVersion is compatible with the RelaxedVersion passed as paramater
func (v *RelaxedVersion) CompatibleWith(u *RelaxedVersion) bool {
if v.version != nil && u.version != nil {
return v.version.CompatibleWith(u.version)
}
return v.Equal(u)
}
33 changes: 33 additions & 0 deletions relaxed_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,39 @@ func TestRelaxedVersionComparator(t *testing.T) {
)
}

func TestRelaxedCompatibleWith(t *testing.T) {
inv := ParseRelaxed("invalid-semver")
inv2 := ParseRelaxed("invalid-semver-2")
v145 := ParseRelaxed("1.4.5")
v152 := ParseRelaxed("1.5.2")
v213 := ParseRelaxed("2.1.3")
require.True(t, inv.CompatibleWith(inv))
require.False(t, inv.CompatibleWith(inv2))
require.False(t, inv.CompatibleWith(v145))
require.False(t, inv.CompatibleWith(v152))
require.False(t, inv.CompatibleWith(v213))
require.False(t, inv2.CompatibleWith(inv))
require.True(t, inv2.CompatibleWith(inv2))
require.False(t, inv2.CompatibleWith(v145))
require.False(t, inv2.CompatibleWith(v152))
require.False(t, inv2.CompatibleWith(v213))
require.False(t, v145.CompatibleWith(inv))
require.False(t, v145.CompatibleWith(inv2))
require.True(t, v145.CompatibleWith(v145))
require.True(t, v145.CompatibleWith(v152))
require.False(t, v145.CompatibleWith(v213))
require.False(t, v152.CompatibleWith(inv))
require.False(t, v152.CompatibleWith(inv2))
require.False(t, v152.CompatibleWith(v145))
require.True(t, v152.CompatibleWith(v152))
require.False(t, v152.CompatibleWith(v213))
require.False(t, v213.CompatibleWith(inv))
require.False(t, v213.CompatibleWith(inv2))
require.False(t, v213.CompatibleWith(v145))
require.False(t, v213.CompatibleWith(v152))
require.True(t, v213.CompatibleWith(v213))
}

func TestNilRelaxedVersionString(t *testing.T) {
var nilVersion *RelaxedVersion
require.Equal(t, "", nilVersion.String())
Expand Down
14 changes: 14 additions & 0 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,17 @@ func (v *Version) GreaterThan(u *Version) bool {
func (v *Version) GreaterThanOrEqual(u *Version) bool {
return v.CompareTo(u) >= 0
}

// CompatibleWith returns true if the Version is compatible with the version passed as paramater
func (v *Version) CompatibleWith(u *Version) bool {
if !u.GreaterThanOrEqual(v) {
return false
}
if v.major[0] != '0' {
return compareNumber(u.major, v.major) == 0
} else if v.minor[0] != '0' {
return compareNumber(u.major, v.major) == 0 && compareNumber(u.minor, v.minor) == 0
} else {
return compareNumber(u.major, v.major) == 0 && compareNumber(u.minor, v.minor) == 0 && compareNumber(u.patch, v.patch) == 0
}
}
Loading