123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- package semver
- import (
- "fmt"
- "strings"
- "unicode"
- )
- type comparator func(Version, Version) bool
- var (
- compEQ comparator = func(v1 Version, v2 Version) bool {
- return v1.Compare(v2) == 0
- }
- compNE = func(v1 Version, v2 Version) bool {
- return v1.Compare(v2) != 0
- }
- compGT = func(v1 Version, v2 Version) bool {
- return v1.Compare(v2) == 1
- }
- compGE = func(v1 Version, v2 Version) bool {
- return v1.Compare(v2) >= 0
- }
- compLT = func(v1 Version, v2 Version) bool {
- return v1.Compare(v2) == -1
- }
- compLE = func(v1 Version, v2 Version) bool {
- return v1.Compare(v2) <= 0
- }
- )
- 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)
- })
- }
- // Range represents a range of versions.
- // A Range 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)
- })
- }
- // 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)
- })
- }
- // ParseRange parses a range and returns a Range.
- // If the range could not be parsed an error is returned.
- //
- // Valid ranges are:
- // - "<1.0.0"
- // - "<=1.0.0"
- // - ">1.0.0"
- // - ">=1.0.0"
- // - "1.0.0", "=1.0.0", "==1.0.0"
- // - "!1.0.0", "!=1.0.0"
- //
- // A Range can consist of multiple ranges separated by space:
- // Ranges can be linked by logical AND:
- // - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
- // - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
- //
- // Ranges can also be linked by logical OR:
- // - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
- //
- // AND has a higher precedence than OR. It's not possible to use brackets.
- //
- // 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) {
- parts := splitAndTrim(s)
- orParts, err := splitORParts(parts)
- if err != nil {
- return nil, err
- }
- var orFn Range
- for _, p := range orParts {
- var andFn Range
- for _, ap := range p {
- opStr, vStr, err := splitComparatorVersion(ap)
- 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)
- }
- rf := vr.rangeFunc()
- // Set function
- if andFn == nil {
- andFn = rf
- } else { // Combine with existing function
- andFn = andFn.AND(rf)
- }
- }
- if orFn == nil {
- orFn = andFn
- } else {
- orFn = orFn.OR(andFn)
- }
- }
- return orFn, nil
- }
- // splitORParts splits the already cleaned parts by '||'.
- // Checks for invalid positions of the operator and returns an
- // error if found.
- func splitORParts(parts []string) ([][]string, error) {
- var ORparts [][]string
- last := 0
- for i, p := range parts {
- if p == "||" {
- if i == 0 {
- return nil, fmt.Errorf("First element in range is '||'")
- }
- ORparts = append(ORparts, parts[last:i])
- last = i + 1
- }
- }
- if last == len(parts) {
- return nil, fmt.Errorf("Last element in range is '||'")
- }
- ORparts = append(ORparts, parts[last:])
- return ORparts, nil
- }
- // buildVersionRange takes a slice of 2: operator and version
- // and builds a versionRange, otherwise an error.
- func buildVersionRange(opStr, vStr string) (*versionRange, error) {
- c := parseComparator(opStr)
- if c == nil {
- return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
- }
- v, err := Parse(vStr)
- if err != nil {
- return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
- }
- return &versionRange{
- v: v,
- c: c,
- }, nil
- }
- // splitAndTrim splits a range string by spaces and cleans leading and trailing spaces
- func splitAndTrim(s string) (result []string) {
- last := 0
- for i := 0; i < len(s); i++ {
- if s[i] == ' ' {
- if last < i-1 {
- result = append(result, s[last:i])
- }
- last = i + 1
- }
- }
- if last < len(s)-1 {
- result = append(result, s[last:])
- }
- // parts := strings.Split(s, " ")
- // for _, x := range parts {
- // if s := strings.TrimSpace(x); len(s) != 0 {
- // result = append(result, s)
- // }
- // }
- return
- }
- // splitComparatorVersion splits the comparator from the version.
- // Spaces between the comparator and the version are not allowed.
- // Input must be free of leading or trailing spaces.
- func splitComparatorVersion(s string) (string, string, error) {
- i := strings.IndexFunc(s, unicode.IsDigit)
- if i == -1 {
- return "", "", fmt.Errorf("Could not get version from string: %q", s)
- }
- return strings.TrimSpace(s[0:i]), s[i:], 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
- }
|