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

More details

Full run details