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  }