...

Source file src/github.com/blang/semver/range.go

Documentation: github.com/blang/semver

     1  package semver
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"unicode"
     8  )
     9  
    10  type wildcardType int
    11  
    12  const (
    13  	noneWildcard  wildcardType = iota
    14  	majorWildcard wildcardType = 1
    15  	minorWildcard wildcardType = 2
    16  	patchWildcard wildcardType = 3
    17  )
    18  
    19  func wildcardTypefromInt(i int) wildcardType {
    20  	switch i {
    21  	case 1:
    22  		return majorWildcard
    23  	case 2:
    24  		return minorWildcard
    25  	case 3:
    26  		return patchWildcard
    27  	default:
    28  		return noneWildcard
    29  	}
    30  }
    31  
    32  type comparator func(Version, Version) bool
    33  
    34  var (
    35  	compEQ comparator = func(v1 Version, v2 Version) bool {
    36  		return v1.Compare(v2) == 0
    37  	}
    38  	compNE = func(v1 Version, v2 Version) bool {
    39  		return v1.Compare(v2) != 0
    40  	}
    41  	compGT = func(v1 Version, v2 Version) bool {
    42  		return v1.Compare(v2) == 1
    43  	}
    44  	compGE = func(v1 Version, v2 Version) bool {
    45  		return v1.Compare(v2) >= 0
    46  	}
    47  	compLT = func(v1 Version, v2 Version) bool {
    48  		return v1.Compare(v2) == -1
    49  	}
    50  	compLE = func(v1 Version, v2 Version) bool {
    51  		return v1.Compare(v2) <= 0
    52  	}
    53  )
    54  
    55  type versionRange struct {
    56  	v Version
    57  	c comparator
    58  }
    59  
    60  // rangeFunc creates a Range from the given versionRange.
    61  func (vr *versionRange) rangeFunc() Range {
    62  	return Range(func(v Version) bool {
    63  		return vr.c(v, vr.v)
    64  	})
    65  }
    66  
    67  // Range represents a range of versions.
    68  // A Range can be used to check if a Version satisfies it:
    69  //
    70  //     range, err := semver.ParseRange(">1.0.0 <2.0.0")
    71  //     range(semver.MustParse("1.1.1") // returns true
    72  type Range func(Version) bool
    73  
    74  // OR combines the existing Range with another Range using logical OR.
    75  func (rf Range) OR(f Range) Range {
    76  	return Range(func(v Version) bool {
    77  		return rf(v) || f(v)
    78  	})
    79  }
    80  
    81  // AND combines the existing Range with another Range using logical AND.
    82  func (rf Range) AND(f Range) Range {
    83  	return Range(func(v Version) bool {
    84  		return rf(v) && f(v)
    85  	})
    86  }
    87  
    88  // ParseRange parses a range and returns a Range.
    89  // If the range could not be parsed an error is returned.
    90  //
    91  // Valid ranges are:
    92  //   - "<1.0.0"
    93  //   - "<=1.0.0"
    94  //   - ">1.0.0"
    95  //   - ">=1.0.0"
    96  //   - "1.0.0", "=1.0.0", "==1.0.0"
    97  //   - "!1.0.0", "!=1.0.0"
    98  //
    99  // A Range can consist of multiple ranges separated by space:
   100  // Ranges can be linked by logical AND:
   101  //   - ">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"
   102  //   - ">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
   103  //
   104  // Ranges can also be linked by logical OR:
   105  //   - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
   106  //
   107  // AND has a higher precedence than OR. It's not possible to use brackets.
   108  //
   109  // Ranges can be combined by both AND and OR
   110  //
   111  //  - `>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`
   112  func ParseRange(s string) (Range, error) {
   113  	parts := splitAndTrim(s)
   114  	orParts, err := splitORParts(parts)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	expandedParts, err := expandWildcardVersion(orParts)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	var orFn Range
   123  	for _, p := range expandedParts {
   124  		var andFn Range
   125  		for _, ap := range p {
   126  			opStr, vStr, err := splitComparatorVersion(ap)
   127  			if err != nil {
   128  				return nil, err
   129  			}
   130  			vr, err := buildVersionRange(opStr, vStr)
   131  			if err != nil {
   132  				return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
   133  			}
   134  			rf := vr.rangeFunc()
   135  
   136  			// Set function
   137  			if andFn == nil {
   138  				andFn = rf
   139  			} else { // Combine with existing function
   140  				andFn = andFn.AND(rf)
   141  			}
   142  		}
   143  		if orFn == nil {
   144  			orFn = andFn
   145  		} else {
   146  			orFn = orFn.OR(andFn)
   147  		}
   148  
   149  	}
   150  	return orFn, nil
   151  }
   152  
   153  // splitORParts splits the already cleaned parts by '||'.
   154  // Checks for invalid positions of the operator and returns an
   155  // error if found.
   156  func splitORParts(parts []string) ([][]string, error) {
   157  	var ORparts [][]string
   158  	last := 0
   159  	for i, p := range parts {
   160  		if p == "||" {
   161  			if i == 0 {
   162  				return nil, fmt.Errorf("First element in range is '||'")
   163  			}
   164  			ORparts = append(ORparts, parts[last:i])
   165  			last = i + 1
   166  		}
   167  	}
   168  	if last == len(parts) {
   169  		return nil, fmt.Errorf("Last element in range is '||'")
   170  	}
   171  	ORparts = append(ORparts, parts[last:])
   172  	return ORparts, nil
   173  }
   174  
   175  // buildVersionRange takes a slice of 2: operator and version
   176  // and builds a versionRange, otherwise an error.
   177  func buildVersionRange(opStr, vStr string) (*versionRange, error) {
   178  	c := parseComparator(opStr)
   179  	if c == nil {
   180  		return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
   181  	}
   182  	v, err := Parse(vStr)
   183  	if err != nil {
   184  		return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
   185  	}
   186  
   187  	return &versionRange{
   188  		v: v,
   189  		c: c,
   190  	}, nil
   191  
   192  }
   193  
   194  // inArray checks if a byte is contained in an array of bytes
   195  func inArray(s byte, list []byte) bool {
   196  	for _, el := range list {
   197  		if el == s {
   198  			return true
   199  		}
   200  	}
   201  	return false
   202  }
   203  
   204  // splitAndTrim splits a range string by spaces and cleans whitespaces
   205  func splitAndTrim(s string) (result []string) {
   206  	last := 0
   207  	var lastChar byte
   208  	excludeFromSplit := []byte{'>', '<', '='}
   209  	for i := 0; i < len(s); i++ {
   210  		if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
   211  			if last < i-1 {
   212  				result = append(result, s[last:i])
   213  			}
   214  			last = i + 1
   215  		} else if s[i] != ' ' {
   216  			lastChar = s[i]
   217  		}
   218  	}
   219  	if last < len(s)-1 {
   220  		result = append(result, s[last:])
   221  	}
   222  
   223  	for i, v := range result {
   224  		result[i] = strings.Replace(v, " ", "", -1)
   225  	}
   226  
   227  	// parts := strings.Split(s, " ")
   228  	// for _, x := range parts {
   229  	// 	if s := strings.TrimSpace(x); len(s) != 0 {
   230  	// 		result = append(result, s)
   231  	// 	}
   232  	// }
   233  	return
   234  }
   235  
   236  // splitComparatorVersion splits the comparator from the version.
   237  // Input must be free of leading or trailing spaces.
   238  func splitComparatorVersion(s string) (string, string, error) {
   239  	i := strings.IndexFunc(s, unicode.IsDigit)
   240  	if i == -1 {
   241  		return "", "", fmt.Errorf("Could not get version from string: %q", s)
   242  	}
   243  	return strings.TrimSpace(s[0:i]), s[i:], nil
   244  }
   245  
   246  // getWildcardType will return the type of wildcard that the
   247  // passed version contains
   248  func getWildcardType(vStr string) wildcardType {
   249  	parts := strings.Split(vStr, ".")
   250  	nparts := len(parts)
   251  	wildcard := parts[nparts-1]
   252  
   253  	possibleWildcardType := wildcardTypefromInt(nparts)
   254  	if wildcard == "x" {
   255  		return possibleWildcardType
   256  	}
   257  
   258  	return noneWildcard
   259  }
   260  
   261  // createVersionFromWildcard will convert a wildcard version
   262  // into a regular version, replacing 'x's with '0's, handling
   263  // special cases like '1.x.x' and '1.x'
   264  func createVersionFromWildcard(vStr string) string {
   265  	// handle 1.x.x
   266  	vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
   267  	vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
   268  	parts := strings.Split(vStr2, ".")
   269  
   270  	// handle 1.x
   271  	if len(parts) == 2 {
   272  		return vStr2 + ".0"
   273  	}
   274  
   275  	return vStr2
   276  }
   277  
   278  // incrementMajorVersion will increment the major version
   279  // of the passed version
   280  func incrementMajorVersion(vStr string) (string, error) {
   281  	parts := strings.Split(vStr, ".")
   282  	i, err := strconv.Atoi(parts[0])
   283  	if err != nil {
   284  		return "", err
   285  	}
   286  	parts[0] = strconv.Itoa(i + 1)
   287  
   288  	return strings.Join(parts, "."), nil
   289  }
   290  
   291  // incrementMajorVersion will increment the minor version
   292  // of the passed version
   293  func incrementMinorVersion(vStr string) (string, error) {
   294  	parts := strings.Split(vStr, ".")
   295  	i, err := strconv.Atoi(parts[1])
   296  	if err != nil {
   297  		return "", err
   298  	}
   299  	parts[1] = strconv.Itoa(i + 1)
   300  
   301  	return strings.Join(parts, "."), nil
   302  }
   303  
   304  // expandWildcardVersion will expand wildcards inside versions
   305  // following these rules:
   306  //
   307  // * when dealing with patch wildcards:
   308  // >= 1.2.x    will become    >= 1.2.0
   309  // <= 1.2.x    will become    <  1.3.0
   310  // >  1.2.x    will become    >= 1.3.0
   311  // <  1.2.x    will become    <  1.2.0
   312  // != 1.2.x    will become    <  1.2.0 >= 1.3.0
   313  //
   314  // * when dealing with minor wildcards:
   315  // >= 1.x      will become    >= 1.0.0
   316  // <= 1.x      will become    <  2.0.0
   317  // >  1.x      will become    >= 2.0.0
   318  // <  1.0      will become    <  1.0.0
   319  // != 1.x      will become    <  1.0.0 >= 2.0.0
   320  //
   321  // * when dealing with wildcards without
   322  // version operator:
   323  // 1.2.x       will become    >= 1.2.0 < 1.3.0
   324  // 1.x         will become    >= 1.0.0 < 2.0.0
   325  func expandWildcardVersion(parts [][]string) ([][]string, error) {
   326  	var expandedParts [][]string
   327  	for _, p := range parts {
   328  		var newParts []string
   329  		for _, ap := range p {
   330  			if strings.Index(ap, "x") != -1 {
   331  				opStr, vStr, err := splitComparatorVersion(ap)
   332  				if err != nil {
   333  					return nil, err
   334  				}
   335  
   336  				versionWildcardType := getWildcardType(vStr)
   337  				flatVersion := createVersionFromWildcard(vStr)
   338  
   339  				var resultOperator string
   340  				var shouldIncrementVersion bool
   341  				switch opStr {
   342  				case ">":
   343  					resultOperator = ">="
   344  					shouldIncrementVersion = true
   345  				case ">=":
   346  					resultOperator = ">="
   347  				case "<":
   348  					resultOperator = "<"
   349  				case "<=":
   350  					resultOperator = "<"
   351  					shouldIncrementVersion = true
   352  				case "", "=", "==":
   353  					newParts = append(newParts, ">="+flatVersion)
   354  					resultOperator = "<"
   355  					shouldIncrementVersion = true
   356  				case "!=", "!":
   357  					newParts = append(newParts, "<"+flatVersion)
   358  					resultOperator = ">="
   359  					shouldIncrementVersion = true
   360  				}
   361  
   362  				var resultVersion string
   363  				if shouldIncrementVersion {
   364  					switch versionWildcardType {
   365  					case patchWildcard:
   366  						resultVersion, _ = incrementMinorVersion(flatVersion)
   367  					case minorWildcard:
   368  						resultVersion, _ = incrementMajorVersion(flatVersion)
   369  					}
   370  				} else {
   371  					resultVersion = flatVersion
   372  				}
   373  
   374  				ap = resultOperator + resultVersion
   375  			}
   376  			newParts = append(newParts, ap)
   377  		}
   378  		expandedParts = append(expandedParts, newParts)
   379  	}
   380  
   381  	return expandedParts, nil
   382  }
   383  
   384  func parseComparator(s string) comparator {
   385  	switch s {
   386  	case "==":
   387  		fallthrough
   388  	case "":
   389  		fallthrough
   390  	case "=":
   391  		return compEQ
   392  	case ">":
   393  		return compGT
   394  	case ">=":
   395  		return compGE
   396  	case "<":
   397  		return compLT
   398  	case "<=":
   399  		return compLE
   400  	case "!":
   401  		fallthrough
   402  	case "!=":
   403  		return compNE
   404  	}
   405  
   406  	return nil
   407  }
   408  
   409  // MustParseRange is like ParseRange but panics if the range cannot be parsed.
   410  func MustParseRange(s string) Range {
   411  	r, err := ParseRange(s)
   412  	if err != nil {
   413  		panic(`semver: ParseRange(` + s + `): ` + err.Error())
   414  	}
   415  	return r
   416  }
   417  

View as plain text