1package version 2 3import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "strings" 8) 9 10// Constraint represents a single constraint for a version, such as 11// ">= 1.0". 12type Constraint struct { 13 f constraintFunc 14 check *Version 15 original string 16} 17 18// Constraints is a slice of constraints. We make a custom type so that 19// we can add methods to it. 20type Constraints []*Constraint 21 22type constraintFunc func(v, c *Version) bool 23 24var constraintOperators map[string]constraintFunc 25 26var constraintRegexp *regexp.Regexp 27 28func init() { 29 constraintOperators = map[string]constraintFunc{ 30 "": constraintEqual, 31 "=": constraintEqual, 32 "!=": constraintNotEqual, 33 ">": constraintGreaterThan, 34 "<": constraintLessThan, 35 ">=": constraintGreaterThanEqual, 36 "<=": constraintLessThanEqual, 37 "~>": constraintPessimistic, 38 } 39 40 ops := make([]string, 0, len(constraintOperators)) 41 for k := range constraintOperators { 42 ops = append(ops, regexp.QuoteMeta(k)) 43 } 44 45 constraintRegexp = regexp.MustCompile(fmt.Sprintf( 46 `^\s*(%s)\s*(%s)\s*$`, 47 strings.Join(ops, "|"), 48 VersionRegexpRaw)) 49} 50 51// NewConstraint will parse one or more constraints from the given 52// constraint string. The string must be a comma-separated list of 53// constraints. 54func NewConstraint(v string) (Constraints, error) { 55 vs := strings.Split(v, ",") 56 result := make([]*Constraint, len(vs)) 57 for i, single := range vs { 58 c, err := parseSingle(single) 59 if err != nil { 60 return nil, err 61 } 62 63 result[i] = c 64 } 65 66 return Constraints(result), nil 67} 68 69// Check tests if a version satisfies all the constraints. 70func (cs Constraints) Check(v *Version) bool { 71 for _, c := range cs { 72 if !c.Check(v) { 73 return false 74 } 75 } 76 77 return true 78} 79 80// Returns the string format of the constraints 81func (cs Constraints) String() string { 82 csStr := make([]string, len(cs)) 83 for i, c := range cs { 84 csStr[i] = c.String() 85 } 86 87 return strings.Join(csStr, ",") 88} 89 90// Check tests if a constraint is validated by the given version. 91func (c *Constraint) Check(v *Version) bool { 92 return c.f(v, c.check) 93} 94 95func (c *Constraint) String() string { 96 return c.original 97} 98 99func parseSingle(v string) (*Constraint, error) { 100 matches := constraintRegexp.FindStringSubmatch(v) 101 if matches == nil { 102 return nil, fmt.Errorf("Malformed constraint: %s", v) 103 } 104 105 check, err := NewVersion(matches[2]) 106 if err != nil { 107 return nil, err 108 } 109 110 return &Constraint{ 111 f: constraintOperators[matches[1]], 112 check: check, 113 original: v, 114 }, nil 115} 116 117func prereleaseCheck(v, c *Version) bool { 118 switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; { 119 case cPre && vPre: 120 // A constraint with a pre-release can only match a pre-release version 121 // with the same base segments. 122 return reflect.DeepEqual(c.Segments64(), v.Segments64()) 123 124 case !cPre && vPre: 125 // A constraint without a pre-release can only match a version without a 126 // pre-release. 127 return false 128 129 case cPre && !vPre: 130 // OK, except with the pessimistic operator 131 case !cPre && !vPre: 132 // OK 133 } 134 return true 135} 136 137//------------------------------------------------------------------- 138// Constraint functions 139//------------------------------------------------------------------- 140 141func constraintEqual(v, c *Version) bool { 142 return v.Equal(c) 143} 144 145func constraintNotEqual(v, c *Version) bool { 146 return !v.Equal(c) 147} 148 149func constraintGreaterThan(v, c *Version) bool { 150 return prereleaseCheck(v, c) && v.Compare(c) == 1 151} 152 153func constraintLessThan(v, c *Version) bool { 154 return prereleaseCheck(v, c) && v.Compare(c) == -1 155} 156 157func constraintGreaterThanEqual(v, c *Version) bool { 158 return prereleaseCheck(v, c) && v.Compare(c) >= 0 159} 160 161func constraintLessThanEqual(v, c *Version) bool { 162 return prereleaseCheck(v, c) && v.Compare(c) <= 0 163} 164 165func constraintPessimistic(v, c *Version) bool { 166 // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases 167 if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") { 168 return false 169 } 170 171 // If the version being checked is naturally less than the constraint, then there 172 // is no way for the version to be valid against the constraint 173 if v.LessThan(c) { 174 return false 175 } 176 // We'll use this more than once, so grab the length now so it's a little cleaner 177 // to write the later checks 178 cs := len(c.segments) 179 180 // If the version being checked has less specificity than the constraint, then there 181 // is no way for the version to be valid against the constraint 182 if cs > len(v.segments) { 183 return false 184 } 185 186 // Check the segments in the constraint against those in the version. If the version 187 // being checked, at any point, does not have the same values in each index of the 188 // constraints segments, then it cannot be valid against the constraint. 189 for i := 0; i < c.si-1; i++ { 190 if v.segments[i] != c.segments[i] { 191 return false 192 } 193 } 194 195 // Check the last part of the segment in the constraint. If the version segment at 196 // this index is less than the constraints segment at this index, then it cannot 197 // be valid against the constraint 198 if c.segments[cs-1] > v.segments[cs-1] { 199 return false 200 } 201 202 // If nothing has rejected the version by now, it's valid 203 return true 204} 205