New Upstream Release - golang-github-hashicorp-go-version
Ready changes
Summary
Merged new upstream version: 1.6.0 (was: 1.4.0).
Resulting package
Built on 2023-05-14T20:38 (took 5m12s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-hashicorp-go-version-dev
Lintian Result
Diff
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbae7f7..5f16dd1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+# 1.6.0 (June 28, 2022)
+
+FEATURES:
+
+- Add `Prerelease` function to `Constraint` to return true if the version includes a prerelease field ([#100](https://github.com/hashicorp/go-version/pull/100))
+
+# 1.5.0 (May 18, 2022)
+
+FEATURES:
+
+- Use `encoding` `TextMarshaler` & `TextUnmarshaler` instead of JSON equivalents ([#95](https://github.com/hashicorp/go-version/pull/95))
+- Add JSON handlers to allow parsing from/to JSON ([#93](https://github.com/hashicorp/go-version/pull/93))
+
+# 1.4.0 (January 5, 2022)
+
+FEATURES:
+
+ - Introduce `MustConstraints()` ([#87](https://github.com/hashicorp/go-version/pull/87))
+ - `Constraints`: Introduce `Equals()` and `sort.Interface` methods ([#88](https://github.com/hashicorp/go-version/pull/88))
+
# 1.3.0 (March 31, 2021)
Please note that CHANGELOG.md does not exist in the source code prior to this release.
diff --git a/README.md b/README.md
index 851a337..4d25050 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Versioning Library for Go
-[![Build Status](https://circleci.com/gh/hashicorp/go-version/tree/master.svg?style=svg)](https://circleci.com/gh/hashicorp/go-version/tree/master)
+[![Build Status](https://circleci.com/gh/hashicorp/go-version/tree/main.svg?style=svg)](https://circleci.com/gh/hashicorp/go-version/tree/main)
[![GoDoc](https://godoc.org/github.com/hashicorp/go-version?status.svg)](https://godoc.org/github.com/hashicorp/go-version)
go-version is a library for parsing versions and version constraints,
diff --git a/constraint.go b/constraint.go
index d055759..da5d1ac 100644
--- a/constraint.go
+++ b/constraint.go
@@ -4,6 +4,7 @@ import (
"fmt"
"reflect"
"regexp"
+ "sort"
"strings"
)
@@ -11,30 +12,40 @@ import (
// ">= 1.0".
type Constraint struct {
f constraintFunc
+ op operator
check *Version
original string
}
+func (c *Constraint) Equals(con *Constraint) bool {
+ return c.op == con.op && c.check.Equal(con.check)
+}
+
// Constraints is a slice of constraints. We make a custom type so that
// we can add methods to it.
type Constraints []*Constraint
type constraintFunc func(v, c *Version) bool
-var constraintOperators map[string]constraintFunc
+var constraintOperators map[string]constraintOperation
+
+type constraintOperation struct {
+ op operator
+ f constraintFunc
+}
var constraintRegexp *regexp.Regexp
func init() {
- constraintOperators = map[string]constraintFunc{
- "": constraintEqual,
- "=": constraintEqual,
- "!=": constraintNotEqual,
- ">": constraintGreaterThan,
- "<": constraintLessThan,
- ">=": constraintGreaterThanEqual,
- "<=": constraintLessThanEqual,
- "~>": constraintPessimistic,
+ constraintOperators = map[string]constraintOperation{
+ "": {op: equal, f: constraintEqual},
+ "=": {op: equal, f: constraintEqual},
+ "!=": {op: notEqual, f: constraintNotEqual},
+ ">": {op: greaterThan, f: constraintGreaterThan},
+ "<": {op: lessThan, f: constraintLessThan},
+ ">=": {op: greaterThanEqual, f: constraintGreaterThanEqual},
+ "<=": {op: lessThanEqual, f: constraintLessThanEqual},
+ "~>": {op: pessimistic, f: constraintPessimistic},
}
ops := make([]string, 0, len(constraintOperators))
@@ -66,6 +77,16 @@ func NewConstraint(v string) (Constraints, error) {
return Constraints(result), nil
}
+// MustConstraints is a helper that wraps a call to a function
+// returning (Constraints, error) and panics if error is non-nil.
+func MustConstraints(c Constraints, err error) Constraints {
+ if err != nil {
+ panic(err)
+ }
+
+ return c
+}
+
// Check tests if a version satisfies all the constraints.
func (cs Constraints) Check(v *Version) bool {
for _, c := range cs {
@@ -77,6 +98,56 @@ func (cs Constraints) Check(v *Version) bool {
return true
}
+// Equals compares Constraints with other Constraints
+// for equality. This may not represent logical equivalence
+// of compared constraints.
+// e.g. even though '>0.1,>0.2' is logically equivalent
+// to '>0.2' it is *NOT* treated as equal.
+//
+// Missing operator is treated as equal to '=', whitespaces
+// are ignored and constraints are sorted before comaparison.
+func (cs Constraints) Equals(c Constraints) bool {
+ if len(cs) != len(c) {
+ return false
+ }
+
+ // make copies to retain order of the original slices
+ left := make(Constraints, len(cs))
+ copy(left, cs)
+ sort.Stable(left)
+ right := make(Constraints, len(c))
+ copy(right, c)
+ sort.Stable(right)
+
+ // compare sorted slices
+ for i, con := range left {
+ if !con.Equals(right[i]) {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (cs Constraints) Len() int {
+ return len(cs)
+}
+
+func (cs Constraints) Less(i, j int) bool {
+ if cs[i].op < cs[j].op {
+ return true
+ }
+ if cs[i].op > cs[j].op {
+ return false
+ }
+
+ return cs[i].check.LessThan(cs[j].check)
+}
+
+func (cs Constraints) Swap(i, j int) {
+ cs[i], cs[j] = cs[j], cs[i]
+}
+
// Returns the string format of the constraints
func (cs Constraints) String() string {
csStr := make([]string, len(cs))
@@ -92,6 +163,12 @@ func (c *Constraint) Check(v *Version) bool {
return c.f(v, c.check)
}
+// Prerelease returns true if the version underlying this constraint
+// contains a prerelease field.
+func (c *Constraint) Prerelease() bool {
+ return len(c.check.Prerelease()) > 0
+}
+
func (c *Constraint) String() string {
return c.original
}
@@ -107,8 +184,11 @@ func parseSingle(v string) (*Constraint, error) {
return nil, err
}
+ cop := constraintOperators[matches[1]]
+
return &Constraint{
- f: constraintOperators[matches[1]],
+ f: cop.f,
+ op: cop.op,
check: check,
original: v,
}, nil
@@ -138,6 +218,18 @@ func prereleaseCheck(v, c *Version) bool {
// Constraint functions
//-------------------------------------------------------------------
+type operator rune
+
+const (
+ equal operator = '='
+ notEqual operator = '≠'
+ greaterThan operator = '>'
+ lessThan operator = '<'
+ greaterThanEqual operator = '≥'
+ lessThanEqual operator = '≤'
+ pessimistic operator = '~'
+)
+
func constraintEqual(v, c *Version) bool {
return v.Equal(c)
}
diff --git a/constraint_test.go b/constraint_test.go
index 9c5bee3..5338c8e 100644
--- a/constraint_test.go
+++ b/constraint_test.go
@@ -1,6 +1,9 @@
package version
import (
+ "fmt"
+ "reflect"
+ "sort"
"testing"
)
@@ -97,6 +100,132 @@ func TestConstraintCheck(t *testing.T) {
}
}
+func TestConstraintPrerelease(t *testing.T) {
+ cases := []struct {
+ constraint string
+ prerelease bool
+ }{
+ {"= 1.0", false},
+ {"= 1.0-beta", true},
+ {"~> 2.1.0", false},
+ {"~> 2.1.0-dev", true},
+ {"> 2.0", false},
+ {">= 2.1.0-a", true},
+ }
+
+ for _, tc := range cases {
+ c, err := parseSingle(tc.constraint)
+ if err != nil {
+ t.Fatalf("err: %s", err)
+ }
+
+ actual := c.Prerelease()
+ expected := tc.prerelease
+ if actual != expected {
+ t.Fatalf("Constraint: %s\nExpected: %#v",
+ tc.constraint, expected)
+ }
+ }
+}
+
+func TestConstraintEqual(t *testing.T) {
+ cases := []struct {
+ leftConstraint string
+ rightConstraint string
+ expectedEqual bool
+ }{
+ {
+ "0.0.1",
+ "0.0.1",
+ true,
+ },
+ { // whitespaces
+ " 0.0.1 ",
+ "0.0.1",
+ true,
+ },
+ { // equal op implied
+ "=0.0.1 ",
+ "0.0.1",
+ true,
+ },
+ { // version difference
+ "=0.0.1",
+ "=0.0.2",
+ false,
+ },
+ { // operator difference
+ ">0.0.1",
+ "=0.0.1",
+ false,
+ },
+ { // different order
+ ">0.1.0, <=1.0.0",
+ "<=1.0.0, >0.1.0",
+ true,
+ },
+ }
+
+ for _, tc := range cases {
+ leftCon, err := NewConstraint(tc.leftConstraint)
+ if err != nil {
+ t.Fatalf("err: %s", err)
+ }
+ rightCon, err := NewConstraint(tc.rightConstraint)
+ if err != nil {
+ t.Fatalf("err: %s", err)
+ }
+
+ actual := leftCon.Equals(rightCon)
+ if actual != tc.expectedEqual {
+ t.Fatalf("Constraints: %s vs %s\nExpected: %t\nActual: %t",
+ tc.leftConstraint, tc.rightConstraint, tc.expectedEqual, actual)
+ }
+ }
+}
+
+func TestConstraint_sort(t *testing.T) {
+ cases := []struct {
+ constraint string
+ expectedConstraints string
+ }{
+ {
+ ">= 0.1.0,< 1.12",
+ "< 1.12,>= 0.1.0",
+ },
+ {
+ "< 1.12,>= 0.1.0",
+ "< 1.12,>= 0.1.0",
+ },
+ {
+ "< 1.12,>= 0.1.0,0.2.0",
+ "< 1.12,0.2.0,>= 0.1.0",
+ },
+ {
+ ">1.0,>0.1.0,>0.3.0,>0.2.0",
+ ">0.1.0,>0.2.0,>0.3.0,>1.0",
+ },
+ }
+
+ for i, tc := range cases {
+ t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+ c, err := NewConstraint(tc.constraint)
+ if err != nil {
+ t.Fatalf("err: %s", err)
+ }
+
+ sort.Sort(c)
+
+ actual := c.String()
+
+ if !reflect.DeepEqual(actual, tc.expectedConstraints) {
+ t.Fatalf("unexpected order\nexpected: %#v\nactual: %#v",
+ tc.expectedConstraints, actual)
+ }
+ })
+ }
+}
+
func TestConstraintsString(t *testing.T) {
cases := []struct {
constraint string
diff --git a/debian/changelog b/debian/changelog
index 2eefa3a..710441f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-hashicorp-go-version (1.6.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Sun, 14 May 2023 20:33:39 -0000
+
golang-github-hashicorp-go-version (1.3.0-1) unstable; urgency=medium
* Team upload.
diff --git a/version.go b/version.go
index 8068834..e87df69 100644
--- a/version.go
+++ b/version.go
@@ -64,7 +64,6 @@ func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
}
segmentsStr := strings.Split(matches[1], ".")
segments := make([]int64, len(segmentsStr))
- si := 0
for i, str := range segmentsStr {
val, err := strconv.ParseInt(str, 10, 64)
if err != nil {
@@ -72,8 +71,7 @@ func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
"Error parsing version: %s", err)
}
- segments[i] = int64(val)
- si++
+ segments[i] = val
}
// Even though we could support more than three segments, if we
@@ -92,7 +90,7 @@ func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
metadata: matches[10],
pre: pre,
segments: segments,
- si: si,
+ si: len(segmentsStr),
original: v,
}, nil
}
@@ -390,3 +388,20 @@ func (v *Version) String() string {
func (v *Version) Original() string {
return v.original
}
+
+// UnmarshalText implements encoding.TextUnmarshaler interface.
+func (v *Version) UnmarshalText(b []byte) error {
+ temp, err := NewVersion(string(b))
+ if err != nil {
+ return err
+ }
+
+ *v = *temp
+
+ return nil
+}
+
+// MarshalText implements encoding.TextMarshaler interface.
+func (v *Version) MarshalText() ([]byte, error) {
+ return []byte(v.String()), nil
+}
diff --git a/version_test.go b/version_test.go
index 9fa34f6..08cbf01 100644
--- a/version_test.go
+++ b/version_test.go
@@ -1,6 +1,8 @@
package version
import (
+ "encoding/json"
+ "fmt"
"reflect"
"testing"
)
@@ -21,13 +23,13 @@ func TestNewVersion(t *testing.T) {
{"1.2-beta.5", false},
{"\n1.2", true},
{"1.2.0-x.Y.0+metadata", false},
- {"1.2.0-x.Y.0+metadata-width-hypen", false},
- {"1.2.3-rc1-with-hypen", false},
+ {"1.2.0-x.Y.0+metadata-width-hyphen", false},
+ {"1.2.3-rc1-with-hyphen", false},
{"1.2.3.4", false},
{"1.2.0.4-x.Y.0+metadata", false},
- {"1.2.0.4-x.Y.0+metadata-width-hypen", false},
+ {"1.2.0.4-x.Y.0+metadata-width-hyphen", false},
{"1.2.0-X-1.2.0+metadata~dist", false},
- {"1.2.3.4-rc1-with-hypen", false},
+ {"1.2.3.4-rc1-with-hyphen", false},
{"1.2.3.4", false},
{"v1.2.3", false},
{"foo1.2.3", true},
@@ -62,13 +64,13 @@ func TestNewSemver(t *testing.T) {
{"1.2-beta.5", false},
{"\n1.2", true},
{"1.2.0-x.Y.0+metadata", false},
- {"1.2.0-x.Y.0+metadata-width-hypen", false},
- {"1.2.3-rc1-with-hypen", false},
+ {"1.2.0-x.Y.0+metadata-width-hyphen", false},
+ {"1.2.3-rc1-with-hyphen", false},
{"1.2.3.4", false},
{"1.2.0.4-x.Y.0+metadata", false},
- {"1.2.0.4-x.Y.0+metadata-width-hypen", false},
+ {"1.2.0.4-x.Y.0+metadata-width-hyphen", false},
{"1.2.0-X-1.2.0+metadata~dist", false},
- {"1.2.3.4-rc1-with-hypen", false},
+ {"1.2.3.4-rc1-with-hyphen", false},
{"1.2.3.4", false},
{"v1.2.3", false},
{"foo1.2.3", true},
@@ -393,6 +395,75 @@ func TestVersionSegments64(t *testing.T) {
}
}
+func TestJsonMarshal(t *testing.T) {
+ cases := []struct {
+ version string
+ err bool
+ }{
+ {"1.2.3", false},
+ {"1.2.0-x.Y.0+metadata", false},
+ {"1.2.0-x.Y.0+metadata-width-hyphen", false},
+ {"1.2.3-rc1-with-hyphen", false},
+ {"1.2.3.4", false},
+ {"1.2.0.4-x.Y.0+metadata", false},
+ {"1.2.0.4-x.Y.0+metadata-width-hyphen", false},
+ {"1.2.0-X-1.2.0+metadata~dist", false},
+ {"1.2.3.4-rc1-with-hyphen", false},
+ {"1.2.3.4", false},
+ }
+
+ for _, tc := range cases {
+ v, err1 := NewVersion(tc.version)
+ if err1 != nil {
+ t.Fatalf("error for version %q: %s", tc.version, err1)
+ }
+
+ parsed, err2 := json.Marshal(v)
+ if err2 != nil {
+ t.Fatalf("error marshaling version %q: %s", tc.version, err2)
+ }
+ result := string(parsed)
+ expected := fmt.Sprintf("%q", tc.version)
+ if result != expected && !tc.err {
+ t.Fatalf("Error marshaling unexpected marshaled content: result=%q expected=%q", result, expected)
+ }
+ }
+}
+
+func TestJsonUnmarshal(t *testing.T) {
+ cases := []struct {
+ version string
+ err bool
+ }{
+ {"1.2.3", false},
+ {"1.2.0-x.Y.0+metadata", false},
+ {"1.2.0-x.Y.0+metadata-width-hyphen", false},
+ {"1.2.3-rc1-with-hyphen", false},
+ {"1.2.3.4", false},
+ {"1.2.0.4-x.Y.0+metadata", false},
+ {"1.2.0.4-x.Y.0+metadata-width-hyphen", false},
+ {"1.2.0-X-1.2.0+metadata~dist", false},
+ {"1.2.3.4-rc1-with-hyphen", false},
+ {"1.2.3.4", false},
+ }
+
+ for _, tc := range cases {
+ expected, err1 := NewVersion(tc.version)
+ if err1 != nil {
+ t.Fatalf("err: %s", err1)
+ }
+
+ actual := &Version{}
+ err2 := json.Unmarshal([]byte(fmt.Sprintf("%q", tc.version)), actual)
+ if err2 != nil {
+ t.Fatalf("error unmarshaling version: %s", err2)
+ }
+ if !reflect.DeepEqual(actual, expected) {
+ t.Fatalf("error unmarshaling, unexpected object content: actual=%q expected=%q", actual, expected)
+ }
+ }
+}
+
func TestVersionString(t *testing.T) {
cases := [][]string{
{"1.2.3", "1.2.3"},
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files