10 // Constraint represents a single constraint for a version, such as
12 type Constraint struct {
18 // Constraints is a slice of constraints. We make a custom type so that
19 // we can add methods to it.
20 type Constraints []*Constraint
22 type constraintFunc func(v, c *Version) bool
24 var constraintOperators map[string]constraintFunc
26 var constraintRegexp *regexp.Regexp
29 constraintOperators = map[string]constraintFunc{
32 "!=": constraintNotEqual,
33 ">": constraintGreaterThan,
34 "<": constraintLessThan,
35 ">=": constraintGreaterThanEqual,
36 "<=": constraintLessThanEqual,
37 "~>": constraintPessimistic,
40 ops := make([]string, 0, len(constraintOperators))
41 for k := range constraintOperators {
42 ops = append(ops, regexp.QuoteMeta(k))
45 constraintRegexp = regexp.MustCompile(fmt.Sprintf(
46 `^\s*(%s)\s*(%s)\s*$`,
47 strings.Join(ops, "|"),
51 // NewConstraint will parse one or more constraints from the given
52 // constraint string. The string must be a comma-separated list of
54 func 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)
66 return Constraints(result), nil
69 // Check tests if a version satisfies all the constraints.
70 func (cs Constraints) Check(v *Version) bool {
71 for _, c := range cs {
80 // Returns the string format of the constraints
81 func (cs Constraints) String() string {
82 csStr := make([]string, len(cs))
83 for i, c := range cs {
87 return strings.Join(csStr, ",")
90 // Check tests if a constraint is validated by the given version.
91 func (c *Constraint) Check(v *Version) bool {
92 return c.f(v, c.check)
95 func (c *Constraint) String() string {
99 func parseSingle(v string) (*Constraint, error) {
100 matches := constraintRegexp.FindStringSubmatch(v)
102 return nil, fmt.Errorf("Malformed constraint: %s", v)
105 check, err := NewVersion(matches[2])
111 f: constraintOperators[matches[1]],
117 func prereleaseCheck(v, c *Version) bool {
118 switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; {
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())
125 // A constraint without a pre-release can only match a version without a
130 // OK, except with the pessimistic operator
137 //-------------------------------------------------------------------
138 // Constraint functions
139 //-------------------------------------------------------------------
141 func constraintEqual(v, c *Version) bool {
145 func constraintNotEqual(v, c *Version) bool {
149 func constraintGreaterThan(v, c *Version) bool {
150 return prereleaseCheck(v, c) && v.Compare(c) == 1
153 func constraintLessThan(v, c *Version) bool {
154 return prereleaseCheck(v, c) && v.Compare(c) == -1
157 func constraintGreaterThanEqual(v, c *Version) bool {
158 return prereleaseCheck(v, c) && v.Compare(c) >= 0
161 func constraintLessThanEqual(v, c *Version) bool {
162 return prereleaseCheck(v, c) && v.Compare(c) <= 0
165 func 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() == "") {
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
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)
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) {
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] {
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] {
202 // If nothing has rejected the version by now, it's valid