github.com/quay/claircore@v1.5.28/pkg/pep440/range.go (about) 1 package pep440 2 3 import ( 4 "fmt" 5 "strings" 6 "unicode" 7 ) 8 9 //go:generate -command stringer go run golang.org/x/tools/cmd/stringer 10 //go:generate stringer -linecomment -type op 11 type op int 12 13 const ( 14 _ op = iota 15 16 opMatch // == 17 opExclusion // != 18 opLTE // <= 19 opGTE // >= 20 opLT // < 21 opGT // > 22 ) 23 24 type criterion struct { 25 V Version 26 Op op 27 } 28 29 func (c *criterion) Match(v *Version) bool { 30 cmp := v.Compare(&c.V) 31 switch c.Op { 32 case opMatch: 33 return cmp == 0 34 case opExclusion: 35 return cmp != 0 36 case opLTE: 37 return cmp != +1 38 case opGTE: 39 return cmp != -1 40 case opLT: 41 return cmp == -1 42 case opGT: 43 return cmp == +1 44 default: 45 panic("programmer error") 46 } 47 } 48 49 // Range is a set of criteria corresponding to a range of versions. 50 type Range []criterion 51 52 func (r Range) String() string { 53 b := strings.Builder{} 54 for i, c := range r { 55 if i != 0 { 56 b.WriteString(", ") 57 } 58 b.WriteString(c.Op.String()) 59 b.WriteString(c.V.String()) 60 } 61 return b.String() 62 } 63 64 // Match reports whether the passed-in Version matches the Range. 65 func (r Range) Match(v *Version) bool { 66 for _, c := range r { 67 if !c.Match(v) { 68 return false 69 } 70 } 71 return true 72 } 73 74 // AND returns a Range that is the logical AND of the two Ranges. 75 func (r Range) AND(n Range) Range { 76 return append(r, n...) 77 } 78 79 // ParseRange takes a version specifer as described in PEP-440 and turns it into 80 // a Range, with the following exceptions: 81 // 82 // Wildcards are not implemented. 83 // 84 // Arbtrary matching (===) is not implemented. 85 func ParseRange(r string) (Range, error) { 86 const op = `~=!<>` 87 r = strings.Map(stripSpace, r) 88 89 var ret []criterion 90 for _, r := range strings.Split(r, ",") { 91 i := strings.LastIndexAny(r, op) + 1 92 o := r[:i] 93 v, err := Parse(r[i:]) 94 if err != nil { 95 return nil, err 96 } 97 switch o { 98 case "==": 99 ret = append(ret, criterion{Op: opMatch, V: v}) 100 case "!=": 101 ret = append(ret, criterion{Op: opExclusion, V: v}) 102 case "<=": 103 ret = append(ret, criterion{Op: opLTE, V: v}) 104 case ">=": 105 ret = append(ret, criterion{Op: opGTE, V: v}) 106 case "<": 107 ret = append(ret, criterion{Op: opLT, V: v}) 108 case ">": 109 ret = append(ret, criterion{Op: opGT, V: v}) 110 case "~=": 111 uv := Version{} 112 l := len(v.Release) - 1 113 uv.Release = make([]int, l) 114 copy(uv.Release, v.Release) 115 uv.Release[l-1]++ 116 uv.Epoch = v.Epoch 117 ret = append(ret, 118 criterion{Op: opGTE, V: v}, 119 criterion{Op: opLT, V: uv}, 120 ) 121 122 default: 123 return nil, fmt.Errorf("unknown range operator: %q", o) 124 } 125 } 126 return Range(ret), nil 127 } 128 129 func stripSpace(r rune) rune { 130 if unicode.IsSpace(r) { 131 return -1 132 } 133 return r 134 }