...

Source file src/github.com/hashicorp/go-version/constraint.go

Documentation: github.com/hashicorp/go-version

     1  package version
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"regexp"
     7  	"sort"
     8  	"strings"
     9  )
    10  
    11  // Constraint represents a single constraint for a version, such as
    12  // ">= 1.0".
    13  type Constraint struct {
    14  	f        constraintFunc
    15  	op       operator
    16  	check    *Version
    17  	original string
    18  }
    19  
    20  func (c *Constraint) Equals(con *Constraint) bool {
    21  	return c.op == con.op && c.check.Equal(con.check)
    22  }
    23  
    24  // Constraints is a slice of constraints. We make a custom type so that
    25  // we can add methods to it.
    26  type Constraints []*Constraint
    27  
    28  type constraintFunc func(v, c *Version) bool
    29  
    30  var constraintOperators map[string]constraintOperation
    31  
    32  type constraintOperation struct {
    33  	op operator
    34  	f  constraintFunc
    35  }
    36  
    37  var constraintRegexp *regexp.Regexp
    38  
    39  func init() {
    40  	constraintOperators = map[string]constraintOperation{
    41  		"":   {op: equal, f: constraintEqual},
    42  		"=":  {op: equal, f: constraintEqual},
    43  		"!=": {op: notEqual, f: constraintNotEqual},
    44  		">":  {op: greaterThan, f: constraintGreaterThan},
    45  		"<":  {op: lessThan, f: constraintLessThan},
    46  		">=": {op: greaterThanEqual, f: constraintGreaterThanEqual},
    47  		"<=": {op: lessThanEqual, f: constraintLessThanEqual},
    48  		"~>": {op: pessimistic, f: constraintPessimistic},
    49  	}
    50  
    51  	ops := make([]string, 0, len(constraintOperators))
    52  	for k := range constraintOperators {
    53  		ops = append(ops, regexp.QuoteMeta(k))
    54  	}
    55  
    56  	constraintRegexp = regexp.MustCompile(fmt.Sprintf(
    57  		`^\s*(%s)\s*(%s)\s*$`,
    58  		strings.Join(ops, "|"),
    59  		VersionRegexpRaw))
    60  }
    61  
    62  // NewConstraint will parse one or more constraints from the given
    63  // constraint string. The string must be a comma-separated list of
    64  // constraints.
    65  func NewConstraint(v string) (Constraints, error) {
    66  	vs := strings.Split(v, ",")
    67  	result := make([]*Constraint, len(vs))
    68  	for i, single := range vs {
    69  		c, err := parseSingle(single)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  
    74  		result[i] = c
    75  	}
    76  
    77  	return Constraints(result), nil
    78  }
    79  
    80  // MustConstraints is a helper that wraps a call to a function
    81  // returning (Constraints, error) and panics if error is non-nil.
    82  func MustConstraints(c Constraints, err error) Constraints {
    83  	if err != nil {
    84  		panic(err)
    85  	}
    86  
    87  	return c
    88  }
    89  
    90  // Check tests if a version satisfies all the constraints.
    91  func (cs Constraints) Check(v *Version) bool {
    92  	for _, c := range cs {
    93  		if !c.Check(v) {
    94  			return false
    95  		}
    96  	}
    97  
    98  	return true
    99  }
   100  
   101  // Equals compares Constraints with other Constraints
   102  // for equality. This may not represent logical equivalence
   103  // of compared constraints.
   104  // e.g. even though '>0.1,>0.2' is logically equivalent
   105  // to '>0.2' it is *NOT* treated as equal.
   106  //
   107  // Missing operator is treated as equal to '=', whitespaces
   108  // are ignored and constraints are sorted before comaparison.
   109  func (cs Constraints) Equals(c Constraints) bool {
   110  	if len(cs) != len(c) {
   111  		return false
   112  	}
   113  
   114  	// make copies to retain order of the original slices
   115  	left := make(Constraints, len(cs))
   116  	copy(left, cs)
   117  	sort.Stable(left)
   118  	right := make(Constraints, len(c))
   119  	copy(right, c)
   120  	sort.Stable(right)
   121  
   122  	// compare sorted slices
   123  	for i, con := range left {
   124  		if !con.Equals(right[i]) {
   125  			return false
   126  		}
   127  	}
   128  
   129  	return true
   130  }
   131  
   132  func (cs Constraints) Len() int {
   133  	return len(cs)
   134  }
   135  
   136  func (cs Constraints) Less(i, j int) bool {
   137  	if cs[i].op < cs[j].op {
   138  		return true
   139  	}
   140  	if cs[i].op > cs[j].op {
   141  		return false
   142  	}
   143  
   144  	return cs[i].check.LessThan(cs[j].check)
   145  }
   146  
   147  func (cs Constraints) Swap(i, j int) {
   148  	cs[i], cs[j] = cs[j], cs[i]
   149  }
   150  
   151  // Returns the string format of the constraints
   152  func (cs Constraints) String() string {
   153  	csStr := make([]string, len(cs))
   154  	for i, c := range cs {
   155  		csStr[i] = c.String()
   156  	}
   157  
   158  	return strings.Join(csStr, ",")
   159  }
   160  
   161  // Check tests if a constraint is validated by the given version.
   162  func (c *Constraint) Check(v *Version) bool {
   163  	return c.f(v, c.check)
   164  }
   165  
   166  // Prerelease returns true if the version underlying this constraint
   167  // contains a prerelease field.
   168  func (c *Constraint) Prerelease() bool {
   169  	return len(c.check.Prerelease()) > 0
   170  }
   171  
   172  func (c *Constraint) String() string {
   173  	return c.original
   174  }
   175  
   176  func parseSingle(v string) (*Constraint, error) {
   177  	matches := constraintRegexp.FindStringSubmatch(v)
   178  	if matches == nil {
   179  		return nil, fmt.Errorf("Malformed constraint: %s", v)
   180  	}
   181  
   182  	check, err := NewVersion(matches[2])
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	cop := constraintOperators[matches[1]]
   188  
   189  	return &Constraint{
   190  		f:        cop.f,
   191  		op:       cop.op,
   192  		check:    check,
   193  		original: v,
   194  	}, nil
   195  }
   196  
   197  func prereleaseCheck(v, c *Version) bool {
   198  	switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; {
   199  	case cPre && vPre:
   200  		// A constraint with a pre-release can only match a pre-release version
   201  		// with the same base segments.
   202  		return reflect.DeepEqual(c.Segments64(), v.Segments64())
   203  
   204  	case !cPre && vPre:
   205  		// A constraint without a pre-release can only match a version without a
   206  		// pre-release.
   207  		return false
   208  
   209  	case cPre && !vPre:
   210  		// OK, except with the pessimistic operator
   211  	case !cPre && !vPre:
   212  		// OK
   213  	}
   214  	return true
   215  }
   216  
   217  //-------------------------------------------------------------------
   218  // Constraint functions
   219  //-------------------------------------------------------------------
   220  
   221  type operator rune
   222  
   223  const (
   224  	equal            operator = '='
   225  	notEqual         operator = '≠'
   226  	greaterThan      operator = '>'
   227  	lessThan         operator = '<'
   228  	greaterThanEqual operator = '≥'
   229  	lessThanEqual    operator = '≤'
   230  	pessimistic      operator = '~'
   231  )
   232  
   233  func constraintEqual(v, c *Version) bool {
   234  	return v.Equal(c)
   235  }
   236  
   237  func constraintNotEqual(v, c *Version) bool {
   238  	return !v.Equal(c)
   239  }
   240  
   241  func constraintGreaterThan(v, c *Version) bool {
   242  	return prereleaseCheck(v, c) && v.Compare(c) == 1
   243  }
   244  
   245  func constraintLessThan(v, c *Version) bool {
   246  	return prereleaseCheck(v, c) && v.Compare(c) == -1
   247  }
   248  
   249  func constraintGreaterThanEqual(v, c *Version) bool {
   250  	return prereleaseCheck(v, c) && v.Compare(c) >= 0
   251  }
   252  
   253  func constraintLessThanEqual(v, c *Version) bool {
   254  	return prereleaseCheck(v, c) && v.Compare(c) <= 0
   255  }
   256  
   257  func constraintPessimistic(v, c *Version) bool {
   258  	// Using a pessimistic constraint with a pre-release, restricts versions to pre-releases
   259  	if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") {
   260  		return false
   261  	}
   262  
   263  	// If the version being checked is naturally less than the constraint, then there
   264  	// is no way for the version to be valid against the constraint
   265  	if v.LessThan(c) {
   266  		return false
   267  	}
   268  	// We'll use this more than once, so grab the length now so it's a little cleaner
   269  	// to write the later checks
   270  	cs := len(c.segments)
   271  
   272  	// If the version being checked has less specificity than the constraint, then there
   273  	// is no way for the version to be valid against the constraint
   274  	if cs > len(v.segments) {
   275  		return false
   276  	}
   277  
   278  	// Check the segments in the constraint against those in the version. If the version
   279  	// being checked, at any point, does not have the same values in each index of the
   280  	// constraints segments, then it cannot be valid against the constraint.
   281  	for i := 0; i < c.si-1; i++ {
   282  		if v.segments[i] != c.segments[i] {
   283  			return false
   284  		}
   285  	}
   286  
   287  	// Check the last part of the segment in the constraint. If the version segment at
   288  	// this index is less than the constraints segment at this index, then it cannot
   289  	// be valid against the constraint
   290  	if c.segments[cs-1] > v.segments[cs-1] {
   291  		return false
   292  	}
   293  
   294  	// If nothing has rejected the version by now, it's valid
   295  	return true
   296  }
   297  

View as plain text