Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT]: added Ranger as alternative to Range #71

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 163 additions & 95 deletions v4/range.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package semver

import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
Expand Down Expand Up @@ -29,60 +31,156 @@ func wildcardTypefromInt(i int) wildcardType {
}
}

type comparator func(Version, Version) bool
//----------------------------------------------------------------------------------------------------------//
//
//----------------------------------------------------------------------------------------------------------//

var (
compEQ comparator = func(v1 Version, v2 Version) bool {
return v1.Compare(v2) == 0
type comparator uint8

const (
compInvalid comparator = iota
compEQ
compNE
compGT
compGE
compLT
compLE
)

func parseComparator(s string) comparator {
switch s {
case "==", "", "=":
return compEQ
case ">":
return compGT
case ">=":
return compGE
case "<":
return compLT
case "<=":
return compLE
case "!", "!=":
return compNE
}
compNE = func(v1 Version, v2 Version) bool {

return compInvalid
}

func (c comparator) compare(v1 Version, v2 Version) bool {
switch c {
case compEQ:
return v1.Compare(v2) == 0
case compNE:
return v1.Compare(v2) != 0
}
compGT = func(v1 Version, v2 Version) bool {
case compGT:
return v1.Compare(v2) == 1
}
compGE = func(v1 Version, v2 Version) bool {
case compGE:
return v1.Compare(v2) >= 0
}
compLT = func(v1 Version, v2 Version) bool {
case compLT:
return v1.Compare(v2) == -1
}
compLE = func(v1 Version, v2 Version) bool {
case compLE:
return v1.Compare(v2) <= 0
default:
panic("invalid comparator")
}
)
}

func (c comparator) String() string {
switch c {
case compEQ:
return ""
case compNE:
return "!"
case compGT:
return ">"
case compGE:
return ">="
case compLT:
return "<"
case compLE:
return "<="
default:
panic("invalid comparator")
}
}

//----------------------------------------------------------------------------------------------------------//
//
//----------------------------------------------------------------------------------------------------------//

type versionRange struct {
v Version
c comparator
}

// rangeFunc creates a Range from the given versionRange.
func (vr *versionRange) rangeFunc() Range {
return Range(func(v Version) bool {
return vr.c(v, vr.v)
})
func (vr *versionRange) Range(v Version) bool {
return vr.c.compare(v, vr.v)
}

func (vr *versionRange) String() string {
return vr.c.String() + vr.v.String()
}

// Range represents a range of versions.
// A Range can be used to check if a Version satisfies it:
// Ranger is slightly different from Range, that it can be converted back to string.
// A Ranger can be used to check if a Version satisfies it:
//
// range, err := semver.ParseRange(">1.0.0 <2.0.0")
// range(semver.MustParse("1.1.1") // returns true
type Range func(Version) bool

// OR combines the existing Range with another Range using logical OR.
func (rf Range) OR(f Range) Range {
return Range(func(v Version) bool {
return rf(v) || f(v)
})
// r, err := semver.ParseRanger(">1.0.0 <2.0.0")
// r.Range(semver.MustParse("1.1.1") // returns true
// r.String() // returns ">1.0.0 <2.0.0" back
type Ranger interface {
Range(b Version) bool
fmt.Stringer
}

type andRange []Ranger

func (r andRange) Range(v Version) bool {
for _, i := range r {
if !i.Range(v) {
return false
}
}
return true
}

// AND combines the existing Range with another Range using logical AND.
func (rf Range) AND(f Range) Range {
return Range(func(v Version) bool {
return rf(v) && f(v)
})
func (r andRange) String() string {
strs := make([]string, len(r))
for i, item := range r {
strs[i] = item.String()
}

return strings.Join(strs, " ")
}

type orRange []Ranger

func (r orRange) Range(v Version) bool {
for _, i := range r {
if i.Range(v) {
return true
}
}
return false
}

func (r orRange) String() string {
strs := make([]string, len(r))
for i, item := range r {
strs[i] = item.String()
}

return strings.Join(strs, " || ")
}

// MustParseRange is like ParseRange but panics if the range cannot be parsed.
func MustParseRange(s string) Ranger {
r, err := ParseRange(s)
if err != nil {
panic(`semver: ParseRange(` + s + `): ` + err.Error())
}
return r
}

// ParseRange parses a range and returns a Range.
Expand All @@ -109,7 +207,7 @@ func (rf Range) AND(f Range) Range {
// Ranges can be combined by both AND and OR
//
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
func ParseRange(s string) (Range, error) {
func ParseRange(s string) (Ranger, error) {
parts := splitAndTrim(s)
orParts, err := splitORParts(parts)
if err != nil {
Expand All @@ -119,35 +217,38 @@ func ParseRange(s string) (Range, error) {
if err != nil {
return nil, err
}
var orFn Range
for _, p := range expandedParts {
var andFn Range
for _, ap := range p {
opStr, vStr, err := splitComparatorVersion(ap)
var andRanges []Ranger
for _, parts := range expandedParts {
var ranges []Ranger
for _, andParts := range parts {
opStr, vStr, err := splitComparatorVersion(andParts)
if err != nil {
return nil, err
}
vr, err := buildVersionRange(opStr, vStr)
if err != nil {
return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
return nil, fmt.Errorf("Could not parse Range %q: %s", andParts, err)
}
rf := vr.rangeFunc()

// Set function
if andFn == nil {
andFn = rf
} else { // Combine with existing function
andFn = andFn.AND(rf)
}
ranges = append(ranges, vr)
}
if orFn == nil {
orFn = andFn
} else {
orFn = orFn.OR(andFn)
switch len(ranges) {
case 0:
return nil, errors.New("empty range")
case 1:
andRanges = append(andRanges, ranges[0])
default:
andRanges = append(andRanges, andRange(ranges))
}

}
return orFn, nil
switch len(andRanges) {
case 0:
return nil, errors.New("empty range")
case 1:
return andRanges[0], nil
default:
return orRange(andRanges), nil
}
}

// splitORParts splits the already cleaned parts by '||'.
Expand Down Expand Up @@ -176,7 +277,7 @@ func splitORParts(parts []string) ([][]string, error) {
// and builds a versionRange, otherwise an error.
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
c := parseComparator(opStr)
if c == nil {
if c == compInvalid {
return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
}
v, err := Parse(vStr)
Expand All @@ -191,14 +292,8 @@ func buildVersionRange(opStr, vStr string) (*versionRange, error) {

}

// inArray checks if a byte is contained in an array of bytes
func inArray(s byte, list []byte) bool {
for _, el := range list {
if el == s {
return true
}
}
return false
func containsByte(b []byte, c byte) bool {
return bytes.IndexByte(b, c) >= 0
}

// splitAndTrim splits a range string by spaces and cleans whitespaces
Expand All @@ -207,7 +302,7 @@ func splitAndTrim(s string) (result []string) {
var lastChar byte
excludeFromSplit := []byte{'>', '<', '='}
for i := 0; i < len(s); i++ {
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
if s[i] == ' ' && !containsByte(excludeFromSplit, lastChar) {
if last < i-1 {
result = append(result, s[last:i])
}
Expand Down Expand Up @@ -381,36 +476,9 @@ func expandWildcardVersion(parts [][]string) ([][]string, error) {
return expandedParts, nil
}

func parseComparator(s string) comparator {
switch s {
case "==":
fallthrough
case "":
fallthrough
case "=":
return compEQ
case ">":
return compGT
case ">=":
return compGE
case "<":
return compLT
case "<=":
return compLE
case "!":
fallthrough
case "!=":
return compNE
}

return nil
}

// MustParseRange is like ParseRange but panics if the range cannot be parsed.
func MustParseRange(s string) Range {
r, err := ParseRange(s)
if err != nil {
panic(`semver: ParseRange(` + s + `): ` + err.Error())
}
return r
type wildcardSemver struct {
Major int
Minor int
Patch int
PR []PRVersion
}
Loading