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