1// semver is a Semver Constraints package copied from 2// github.com/hashicorp/go-version @ 2046c9d0f0b03c779670f5186a2a4b2c85493a71 3// 4// Unlike Constraints in go-version, Semver constraints use Semver 2.0 ordering 5// rules and only accept properly formatted Semver versions. 6package semver 7 8import ( 9 "fmt" 10 "regexp" 11 "strings" 12 13 "github.com/hashicorp/go-version" 14) 15 16// Constraint represents a single constraint for a version, such as ">= 17// 1.0". 18type Constraint struct { 19 f constraintFunc 20 check *version.Version 21 original string 22} 23 24// Constraints is a slice of constraints. We make a custom type so that 25// we can add methods to it. 26type Constraints []*Constraint 27 28type constraintFunc func(v, c *version.Version) bool 29 30var constraintOperators map[string]constraintFunc 31 32var constraintRegexp *regexp.Regexp 33 34func init() { 35 constraintOperators = map[string]constraintFunc{ 36 "": constraintEqual, 37 "=": constraintEqual, 38 "!=": constraintNotEqual, 39 ">": constraintGreaterThan, 40 "<": constraintLessThan, 41 ">=": constraintGreaterThanEqual, 42 "<=": constraintLessThanEqual, 43 } 44 45 ops := make([]string, 0, len(constraintOperators)) 46 for k := range constraintOperators { 47 ops = append(ops, regexp.QuoteMeta(k)) 48 } 49 50 constraintRegexp = regexp.MustCompile(fmt.Sprintf( 51 `^\s*(%s)\s*(%s)\s*$`, 52 strings.Join(ops, "|"), 53 version.SemverRegexpRaw)) 54} 55 56// NewConstraint will parse one or more constraints from the given 57// constraint string. The string must be a comma-separated list of constraints. 58func NewConstraint(v string) (Constraints, error) { 59 vs := strings.Split(v, ",") 60 result := make([]*Constraint, len(vs)) 61 for i, single := range vs { 62 c, err := parseSingle(single) 63 if err != nil { 64 return nil, err 65 } 66 67 result[i] = c 68 } 69 70 return Constraints(result), nil 71} 72 73// Check tests if a version satisfies all the constraints. 74func (cs Constraints) Check(v *version.Version) bool { 75 for _, c := range cs { 76 if !c.Check(v) { 77 return false 78 } 79 } 80 81 return true 82} 83 84// Returns the string format of the constraints 85func (cs Constraints) String() string { 86 csStr := make([]string, len(cs)) 87 for i, c := range cs { 88 csStr[i] = c.String() 89 } 90 91 return strings.Join(csStr, ",") 92} 93 94// Check tests if a constraint is validated by the given version. 95func (c *Constraint) Check(v *version.Version) bool { 96 return c.f(v, c.check) 97} 98 99func (c *Constraint) String() string { 100 return c.original 101} 102 103func parseSingle(v string) (*Constraint, error) { 104 matches := constraintRegexp.FindStringSubmatch(v) 105 if matches == nil { 106 return nil, fmt.Errorf("Malformed constraint: %s", v) 107 } 108 109 check, err := version.NewSemver(matches[2]) 110 if err != nil { 111 return nil, err 112 } 113 114 return &Constraint{ 115 f: constraintOperators[matches[1]], 116 check: check, 117 original: v, 118 }, nil 119} 120 121//------------------------------------------------------------------- 122// Constraint functions 123//------------------------------------------------------------------- 124 125func constraintEqual(v, c *version.Version) bool { 126 return v.Equal(c) 127} 128 129func constraintNotEqual(v, c *version.Version) bool { 130 return !v.Equal(c) 131} 132 133func constraintGreaterThan(v, c *version.Version) bool { 134 return v.Compare(c) == 1 135} 136 137func constraintLessThan(v, c *version.Version) bool { 138 return v.Compare(c) == -1 139} 140 141func constraintGreaterThanEqual(v, c *version.Version) bool { 142 return v.Compare(c) >= 0 143} 144 145func constraintLessThanEqual(v, c *version.Version) bool { 146 return v.Compare(c) <= 0 147} 148