123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- package semver
- import (
- "fmt"
- "strconv"
- "strings"
- "unicode"
- )
- type wildcardType int
- const (
- noneWildcard wildcardType = iota
- majorWildcard wildcardType = 1
- minorWildcard wildcardType = 2
- patchWildcard wildcardType = 3
- )
- func wildcardTypefromInt(i int) wildcardType {
- switch i {
- case 1:
- return majorWildcard
- case 2:
- return minorWildcard
- case 3:
- return patchWildcard
- default:
- return noneWildcard
- }
- }
- 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
- }
- expandedParts, err := expandWildcardVersion(orParts)
- 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)
- 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
- }
- // 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
- }
- // splitAndTrim splits a range string by spaces and cleans whitespaces
- func splitAndTrim(s string) (result []string) {
- last := 0
- var lastChar byte
- excludeFromSplit := []byte{'>', '<', '='}
- for i := 0; i < len(s); i++ {
- if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
- if last < i-1 {
- result = append(result, s[last:i])
- }
- last = i + 1
- } else if s[i] != ' ' {
- lastChar = s[i]
- }
- }
- if last < len(s)-1 {
- result = append(result, s[last:])
- }
- for i, v := range result {
- result[i] = strings.Replace(v, " ", "", -1)
- }
- // 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.
- // 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
- }
- // getWildcardType will return the type of wildcard that the
- // passed version contains
- func getWildcardType(vStr string) wildcardType {
- parts := strings.Split(vStr, ".")
- nparts := len(parts)
- wildcard := parts[nparts-1]
- possibleWildcardType := wildcardTypefromInt(nparts)
- if wildcard == "x" {
- return possibleWildcardType
- }
- return noneWildcard
- }
- // createVersionFromWildcard will convert a wildcard version
- // into a regular version, replacing 'x's with '0's, handling
- // special cases like '1.x.x' and '1.x'
- func createVersionFromWildcard(vStr string) string {
- // handle 1.x.x
- vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
- vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
- parts := strings.Split(vStr2, ".")
- // handle 1.x
- if len(parts) == 2 {
- return vStr2 + ".0"
- }
- return vStr2
- }
- // incrementMajorVersion will increment the major version
- // of the passed version
- func incrementMajorVersion(vStr string) (string, error) {
- parts := strings.Split(vStr, ".")
- i, err := strconv.Atoi(parts[0])
- if err != nil {
- return "", err
- }
- parts[0] = strconv.Itoa(i + 1)
- return strings.Join(parts, "."), nil
- }
- // incrementMajorVersion will increment the minor version
- // of the passed version
- func incrementMinorVersion(vStr string) (string, error) {
- parts := strings.Split(vStr, ".")
- i, err := strconv.Atoi(parts[1])
- if err != nil {
- return "", err
- }
- parts[1] = strconv.Itoa(i + 1)
- return strings.Join(parts, "."), nil
- }
- // expandWildcardVersion will expand wildcards inside versions
- // following these rules:
- //
- // * when dealing with patch wildcards:
- // >= 1.2.x will become >= 1.2.0
- // <= 1.2.x will become < 1.3.0
- // > 1.2.x will become >= 1.3.0
- // < 1.2.x will become < 1.2.0
- // != 1.2.x will become < 1.2.0 >= 1.3.0
- //
- // * when dealing with minor wildcards:
- // >= 1.x will become >= 1.0.0
- // <= 1.x will become < 2.0.0
- // > 1.x will become >= 2.0.0
- // < 1.0 will become < 1.0.0
- // != 1.x will become < 1.0.0 >= 2.0.0
- //
- // * when dealing with wildcards without
- // version operator:
- // 1.2.x will become >= 1.2.0 < 1.3.0
- // 1.x will become >= 1.0.0 < 2.0.0
- func expandWildcardVersion(parts [][]string) ([][]string, error) {
- var expandedParts [][]string
- for _, p := range parts {
- var newParts []string
- for _, ap := range p {
- if strings.Index(ap, "x") != -1 {
- opStr, vStr, err := splitComparatorVersion(ap)
- if err != nil {
- return nil, err
- }
- versionWildcardType := getWildcardType(vStr)
- flatVersion := createVersionFromWildcard(vStr)
- var resultOperator string
- var shouldIncrementVersion bool
- switch opStr {
- case ">":
- resultOperator = ">="
- shouldIncrementVersion = true
- case ">=":
- resultOperator = ">="
- case "<":
- resultOperator = "<"
- case "<=":
- resultOperator = "<"
- shouldIncrementVersion = true
- case "", "=", "==":
- newParts = append(newParts, ">="+flatVersion)
- resultOperator = "<"
- shouldIncrementVersion = true
- case "!=", "!":
- newParts = append(newParts, "<"+flatVersion)
- resultOperator = ">="
- shouldIncrementVersion = true
- }
- var resultVersion string
- if shouldIncrementVersion {
- switch versionWildcardType {
- case patchWildcard:
- resultVersion, _ = incrementMinorVersion(flatVersion)
- case minorWildcard:
- resultVersion, _ = incrementMajorVersion(flatVersion)
- }
- } else {
- resultVersion = flatVersion
- }
- ap = resultOperator + resultVersion
- }
- newParts = append(newParts, ap)
- }
- expandedParts = append(expandedParts, newParts)
- }
- 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
- }
|