123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- package version
- import (
- "fmt"
- "reflect"
- "regexp"
- "strings"
- )
- // Constraint represents a single constraint for a version, such as
- // ">= 1.0".
- type Constraint struct {
- f constraintFunc
- check *Version
- original string
- }
- // Constraints is a slice of constraints. We make a custom type so that
- // we can add methods to it.
- type Constraints []*Constraint
- type constraintFunc func(v, c *Version) bool
- var constraintOperators map[string]constraintFunc
- var constraintRegexp *regexp.Regexp
- func init() {
- constraintOperators = map[string]constraintFunc{
- "": constraintEqual,
- "=": constraintEqual,
- "!=": constraintNotEqual,
- ">": constraintGreaterThan,
- "<": constraintLessThan,
- ">=": constraintGreaterThanEqual,
- "<=": constraintLessThanEqual,
- "~>": constraintPessimistic,
- }
- ops := make([]string, 0, len(constraintOperators))
- for k := range constraintOperators {
- ops = append(ops, regexp.QuoteMeta(k))
- }
- constraintRegexp = regexp.MustCompile(fmt.Sprintf(
- `^\s*(%s)\s*(%s)\s*$`,
- strings.Join(ops, "|"),
- VersionRegexpRaw))
- }
- // NewConstraint will parse one or more constraints from the given
- // constraint string. The string must be a comma-separated list of
- // constraints.
- func NewConstraint(v string) (Constraints, error) {
- vs := strings.Split(v, ",")
- result := make([]*Constraint, len(vs))
- for i, single := range vs {
- c, err := parseSingle(single)
- if err != nil {
- return nil, err
- }
- result[i] = c
- }
- return Constraints(result), nil
- }
- // Check tests if a version satisfies all the constraints.
- func (cs Constraints) Check(v *Version) bool {
- for _, c := range cs {
- if !c.Check(v) {
- return false
- }
- }
- return true
- }
- // Returns the string format of the constraints
- func (cs Constraints) String() string {
- csStr := make([]string, len(cs))
- for i, c := range cs {
- csStr[i] = c.String()
- }
- return strings.Join(csStr, ",")
- }
- // Check tests if a constraint is validated by the given version.
- func (c *Constraint) Check(v *Version) bool {
- return c.f(v, c.check)
- }
- func (c *Constraint) String() string {
- return c.original
- }
- func parseSingle(v string) (*Constraint, error) {
- matches := constraintRegexp.FindStringSubmatch(v)
- if matches == nil {
- return nil, fmt.Errorf("Malformed constraint: %s", v)
- }
- check, err := NewVersion(matches[2])
- if err != nil {
- return nil, err
- }
- return &Constraint{
- f: constraintOperators[matches[1]],
- check: check,
- original: v,
- }, nil
- }
- func prereleaseCheck(v, c *Version) bool {
- switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; {
- case cPre && vPre:
- // A constraint with a pre-release can only match a pre-release version
- // with the same base segments.
- return reflect.DeepEqual(c.Segments64(), v.Segments64())
- case !cPre && vPre:
- // A constraint without a pre-release can only match a version without a
- // pre-release.
- return false
- case cPre && !vPre:
- // OK, except with the pessimistic operator
- case !cPre && !vPre:
- // OK
- }
- return true
- }
- //-------------------------------------------------------------------
- // Constraint functions
- //-------------------------------------------------------------------
- func constraintEqual(v, c *Version) bool {
- return v.Equal(c)
- }
- func constraintNotEqual(v, c *Version) bool {
- return !v.Equal(c)
- }
- func constraintGreaterThan(v, c *Version) bool {
- return prereleaseCheck(v, c) && v.Compare(c) == 1
- }
- func constraintLessThan(v, c *Version) bool {
- return prereleaseCheck(v, c) && v.Compare(c) == -1
- }
- func constraintGreaterThanEqual(v, c *Version) bool {
- return prereleaseCheck(v, c) && v.Compare(c) >= 0
- }
- func constraintLessThanEqual(v, c *Version) bool {
- return prereleaseCheck(v, c) && v.Compare(c) <= 0
- }
- func constraintPessimistic(v, c *Version) bool {
- // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases
- if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") {
- return false
- }
- // If the version being checked is naturally less than the constraint, then there
- // is no way for the version to be valid against the constraint
- if v.LessThan(c) {
- return false
- }
- // We'll use this more than once, so grab the length now so it's a little cleaner
- // to write the later checks
- cs := len(c.segments)
- // If the version being checked has less specificity than the constraint, then there
- // is no way for the version to be valid against the constraint
- if cs > len(v.segments) {
- return false
- }
- // Check the segments in the constraint against those in the version. If the version
- // being checked, at any point, does not have the same values in each index of the
- // constraints segments, then it cannot be valid against the constraint.
- for i := 0; i < c.si-1; i++ {
- if v.segments[i] != c.segments[i] {
- return false
- }
- }
- // Check the last part of the segment in the constraint. If the version segment at
- // this index is less than the constraints segment at this index, then it cannot
- // be valid against the constraint
- if c.segments[cs-1] > v.segments[cs-1] {
- return false
- }
- // If nothing has rejected the version by now, it's valid
- return true
- }
|