Repository: Masterminds/semver
Pull request #117 (2019-09-09 20:22:07)

Print constraints to strings


Patch:
diff --git a/constraints.go b/constraints.go
index 2fe3816..40099db 100644
--- a/constraints.go
+++ b/constraints.go
@@ -1,6 +1,7 @@
 package semver
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"regexp"
@@ -96,7 +97,7 @@ func (cs Constraints) Validate(v *Version) (bool, []error) {
 			} else {
 
 				if !c.check(v) {
-					em := fmt.Errorf(c.msg, v, c.orig)
+					em := fmt.Errorf(constraintMsg[c.origfunc], v, c.orig)
 					e = append(e, em)
 					joy = false
 				}
@@ -111,6 +112,27 @@ func (cs Constraints) Validate(v *Version) (bool, []error) {
 	return false, e
 }
 
+func (cs Constraints) String() string {
+	buf := make([]string, len(cs.constraints))
+	var tmp bytes.Buffer
+
+	for k, v := range cs.constraints {
+		tmp.Reset()
+		vlen := len(v)
+		for kk, c := range v {
+			tmp.WriteString(c.string())
+
+			// Space separate the AND conditions
+			if vlen > 1 && kk < vlen-1 {
+				tmp.WriteString(" ")
+			}
+		}
+		buf[k] = tmp.String()
+	}
+
+	return strings.Join(buf, " || ")
+}
+
 var constraintOps map[string]cfunc
 var constraintMsg map[string]string
 var constraintRegex *regexp.Regexp
@@ -184,12 +206,6 @@ func init() {
 
 // An individual constraint
 type constraint struct {
-	// The callback function for the restraint. It performs the logic for
-	// the constraint.
-	function cfunc
-
-	msg string
-
 	// The version used in the constraint check. For example, if a constraint
 	// is '<= 2.0.0' the con a version instance representing 2.0.0.
 	con *Version
@@ -197,6 +213,9 @@ type constraint struct {
 	// The original parsed version (e.g., 4.x from != 4.x)
 	orig string
 
+	// The original operator for the constraint
+	origfunc string
+
 	// When an x is used as part of the version (e.g., 1.x)
 	minorDirty bool
 	dirty      bool
@@ -205,7 +224,12 @@ type constraint struct {
 
 // Check if a version meets the constraint
 func (c *constraint) check(v *Version) bool {
-	return c.function(v, c)
+	return constraintOps[c.origfunc](v, c)
+}
+
+// String prints an individual constraint into a string
+func (c *constraint) string() string {
+	return c.origfunc + c.orig
 }
 
 type cfunc func(v *Version, c *constraint) bool
@@ -217,8 +241,12 @@ func parseConstraint(c string) (*constraint, error) {
 			return nil, fmt.Errorf("improper constraint: %s", c)
 		}
 
+		cs := &constraint{
+			orig:     m[2],
+			origfunc: m[1],
+		}
+
 		ver := m[2]
-		orig := ver
 		minorDirty := false
 		patchDirty := false
 		dirty := false
@@ -243,15 +271,11 @@ func parseConstraint(c string) (*constraint, error) {
 			return nil, errors.New("constraint Parser Error")
 		}
 
-		cs := &constraint{
-			function:   constraintOps[m[1]],
-			msg:        constraintMsg[m[1]],
-			con:        con,
-			orig:       orig,
-			minorDirty: minorDirty,
-			patchDirty: patchDirty,
-			dirty:      dirty,
-		}
+		cs.con = con
+		cs.minorDirty = minorDirty
+		cs.patchDirty = patchDirty
+		cs.dirty = dirty
+
 		return cs, nil
 	}
 
@@ -266,10 +290,9 @@ func parseConstraint(c string) (*constraint, error) {
 	}
 
 	cs := &constraint{
-		function:   constraintOps[""],
-		msg:        constraintMsg[""],
 		con:        con,
 		orig:       c,
+		origfunc:   "",
 		minorDirty: false,
 		patchDirty: false,
 		dirty:      true,


Test patch:
diff --git a/constraints_test.go b/constraints_test.go
index ddad921..41506e2 100644
--- a/constraints_test.go
+++ b/constraints_test.go
@@ -22,6 +22,7 @@ func TestParseConstraint(t *testing.T) {
 		{"=1.5", constraintTildeOrEqual, "1.5.0", false},
 		{"> 1.3", constraintGreaterThan, "1.3.0", false},
 		{"< 1.4.1", constraintLessThan, "1.4.1", false},
+		{"< 40.50.10", constraintLessThan, "40.50.10", false},
 	}
 
 	for _, tc := range tests {
@@ -43,7 +44,7 @@ func TestParseConstraint(t *testing.T) {
 		}
 
 		f1 := reflect.ValueOf(tc.f)
-		f2 := reflect.ValueOf(c.function)
+		f2 := reflect.ValueOf(constraintOps[c.origfunc])
 		if f1 != f2 {
 			t.Errorf("Wrong constraint found for %s", tc.in)
 		}
@@ -203,6 +204,7 @@ func TestNewConstraint(t *testing.T) {
 		err   bool
 	}{
 		{">= 1.1", 1, 1, false},
+		{">40.50.60, < 50.70", 1, 2, false},
 		{"2.0", 1, 1, false},
 		{"v2.3.5-20161202202307-sha.e8fc5e5", 1, 1, false},
 		{">= bar", 0, 0, true},
@@ -613,3 +615,29 @@ func TestConstraintsValidate(t *testing.T) {
 		}
 	}
 }
+
+func TestConstraintString(t *testing.T) {
+	tests := []struct {
+		constraint string
+		st         string
+	}{
+		{"*", "*"},
+		{">=1.2.3", ">=1.2.3"},
+		{">= 1.2.3", ">=1.2.3"},
+		{"2.x,   >=1.2.3 || >4.5.6, < 5.7", "2.x >=1.2.3 || >4.5.6 <5.7"},
+		{"2.x,   >=1.2.3 || >4.5.6, < 5.7 || >40.50.60, < 50.70", "2.x >=1.2.3 || >4.5.6 <5.7 || >40.50.60 <50.70"},
+		{"1.2", "1.2"},
+	}
+
+	for _, tc := range tests {
+		c, err := NewConstraint(tc.constraint)
+		if err != nil {
+			t.Errorf("cannot create constraint for %q, err: %s", tc.constraint, err)
+			continue
+		}
+
+		if c.String() != tc.st {
+			t.Errorf("expected constraint from %q to be a string as %q but got %q", tc.constraint, tc.st, c.String())
+		}
+	}
+}