terraform/vendor/github.com/apparentlymart/go-versions/versions/constraints/spec.go

250 lines
7.1 KiB
Go
Raw Normal View History

package constraints
import (
"bytes"
"fmt"
"strconv"
)
// Spec is an interface type that UnionSpec, IntersectionSpec, SelectionSpec,
// and VersionSpec all belong to.
//
// It's provided to allow generic code to be written that accepts and operates
// on all specs, but such code must still handle each type separately using
// e.g. a type switch. This is a closed type that will not have any new
// implementations added in future.
type Spec interface {
isSpec()
}
// UnionSpec represents an "or" operation on nested version constraints.
//
// This is not directly representable in all of our supported constraint
// syntaxes.
type UnionSpec []IntersectionSpec
func (s UnionSpec) isSpec() {}
// IntersectionSpec represents an "and" operation on nested version constraints.
type IntersectionSpec []SelectionSpec
func (s IntersectionSpec) isSpec() {}
// SelectionSpec represents applying a single operator to a particular
// "boundary" version.
type SelectionSpec struct {
Boundary VersionSpec
Operator SelectionOp
}
func (s SelectionSpec) isSpec() {}
// VersionSpec represents the boundary within a SelectionSpec.
type VersionSpec struct {
Major NumConstraint
Minor NumConstraint
Patch NumConstraint
Prerelease string
Metadata string
}
func (s VersionSpec) isSpec() {}
// IsExact returns bool if all of the version numbers in the receiver are
// fully-constrained. This is the same as s.ConstraintDepth() == ConstrainedPatch
func (s VersionSpec) IsExact() bool {
return s.ConstraintDepth() == ConstrainedPatch
}
// ConstraintDepth returns the constraint depth of the receiver, which is
// the most specifc version number segment that is exactly constrained.
//
// The constraints must be consistent, which means that if a given segment
// is unconstrained then all of the deeper segments must also be unconstrained.
// If not, this method will panic. Version specs produced by the parsers in
// this package are guaranteed to be consistent.
func (s VersionSpec) ConstraintDepth() ConstraintDepth {
if s == (VersionSpec{}) {
// zero value is a degenerate case meaning completely unconstrained
return Unconstrained
}
switch {
case s.Major.Unconstrained:
if !(s.Minor.Unconstrained && s.Patch.Unconstrained && s.Prerelease == "" && s.Metadata == "") {
panic("inconsistent constraint depth")
}
return Unconstrained
case s.Minor.Unconstrained:
if !(s.Patch.Unconstrained && s.Prerelease == "" && s.Metadata == "") {
panic("inconsistent constraint depth")
}
return ConstrainedMajor
case s.Patch.Unconstrained:
if s.Prerelease != "" || s.Metadata != "" {
panic(fmt.Errorf("inconsistent constraint depth: wildcard major, minor and patch followed by prerelease %q and metadata %q", s.Prerelease, s.Metadata))
}
return ConstrainedMinor
default:
return ConstrainedPatch
}
}
// ConstraintBounds returns two exact VersionSpecs that represent the upper
// and lower bounds of the possibly-inexact receiver. If the receiver
// is already exact then the two bounds are identical and have operator
// OpEqual. If they are different then the lower bound is OpGreaterThanOrEqual
// and the upper bound is OpLessThan.
//
// As a special case, if the version spec is entirely unconstrained the
// two bounds will be identical and the zero value of SelectionSpec. For
// consistency, this result is also returned if the receiver is already
// the zero value of VersionSpec, since a zero spec represents a lack of
// constraint.
//
// The constraints must be consistent as defined by ConstraintDepth, or this
// method will panic.
func (s VersionSpec) ConstraintBounds() (SelectionSpec, SelectionSpec) {
switch s.ConstraintDepth() {
case Unconstrained:
return SelectionSpec{}, SelectionSpec{}
case ConstrainedMajor:
lowerBound := s.ConstrainToZero()
lowerBound.Metadata = ""
upperBound := lowerBound
upperBound.Major.Num++
upperBound.Minor.Num = 0
upperBound.Patch.Num = 0
upperBound.Prerelease = ""
upperBound.Metadata = ""
return SelectionSpec{
Operator: OpGreaterThanOrEqual,
Boundary: lowerBound,
}, SelectionSpec{
Operator: OpLessThan,
Boundary: upperBound,
}
case ConstrainedMinor:
lowerBound := s.ConstrainToZero()
lowerBound.Metadata = ""
upperBound := lowerBound
upperBound.Minor.Num++
upperBound.Patch.Num = 0
upperBound.Metadata = ""
return SelectionSpec{
Operator: OpGreaterThanOrEqual,
Boundary: lowerBound,
}, SelectionSpec{
Operator: OpLessThan,
Boundary: upperBound,
}
default:
eq := SelectionSpec{
Operator: OpEqual,
Boundary: s,
}
return eq, eq
}
}
// ConstrainToZero returns a copy of the receiver with all of its
// unconstrained numeric segments constrained to zero.
func (s VersionSpec) ConstrainToZero() VersionSpec {
switch s.ConstraintDepth() {
case Unconstrained:
s.Major = NumConstraint{Num: 0}
s.Minor = NumConstraint{Num: 0}
s.Patch = NumConstraint{Num: 0}
s.Prerelease = ""
s.Metadata = ""
case ConstrainedMajor:
s.Minor = NumConstraint{Num: 0}
s.Patch = NumConstraint{Num: 0}
s.Prerelease = ""
s.Metadata = ""
case ConstrainedMinor:
s.Patch = NumConstraint{Num: 0}
s.Prerelease = ""
s.Metadata = ""
}
return s
}
// ConstrainToUpperBound returns a copy of the receiver with all of its
// unconstrained numeric segments constrained to zero and its last
// constrained segment increased by one.
//
// This operation is not meaningful for an entirely unconstrained VersionSpec,
// so will return the zero value of the type in that case.
func (s VersionSpec) ConstrainToUpperBound() VersionSpec {
switch s.ConstraintDepth() {
case Unconstrained:
return VersionSpec{}
case ConstrainedMajor:
s.Major.Num++
s.Minor = NumConstraint{Num: 0}
s.Patch = NumConstraint{Num: 0}
s.Prerelease = ""
s.Metadata = ""
case ConstrainedMinor:
s.Minor.Num++
s.Patch = NumConstraint{Num: 0}
s.Prerelease = ""
s.Metadata = ""
}
return s
}
func (s VersionSpec) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%s.%s.%s", s.Major, s.Minor, s.Patch)
if s.Prerelease != "" {
fmt.Fprintf(&buf, "-%s", s.Prerelease)
}
if s.Metadata != "" {
fmt.Fprintf(&buf, "+%s", s.Metadata)
}
return buf.String()
}
type SelectionOp rune
//go:generate stringer -type SelectionOp
const (
OpUnconstrained SelectionOp = 0
OpGreaterThan SelectionOp = '>'
OpLessThan SelectionOp = '<'
OpGreaterThanOrEqual SelectionOp = '≥'
OpGreaterThanOrEqualPatchOnly SelectionOp = '~'
OpGreaterThanOrEqualMinorOnly SelectionOp = '^'
OpLessThanOrEqual SelectionOp = '≤'
OpEqual SelectionOp = '='
OpNotEqual SelectionOp = '≠'
OpMatch SelectionOp = '*'
)
type NumConstraint struct {
Num uint64
Unconstrained bool
}
func (c NumConstraint) String() string {
if c.Unconstrained {
return "*"
} else {
return strconv.FormatUint(c.Num, 10)
}
}
type ConstraintDepth int
//go:generate stringer -type ConstraintDepth
const (
Unconstrained ConstraintDepth = 0
ConstrainedMajor ConstraintDepth = 1
ConstrainedMinor ConstraintDepth = 2
ConstrainedPatch ConstraintDepth = 3
)