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