GithubHelp home page GithubHelp logo

masterminds / semver Goto Github PK

View Code? Open in Web Editor NEW
1.2K 18.0 147.0 1.44 MB

Work with Semantic Versions in Go

License: MIT License

Go 99.07% Makefile 0.93%
semantic-versions semver golang go constraints comparison tilde caret

semver's Introduction

SemVer

The semver package provides the ability to work with Semantic Versions in Go. Specifically it provides the ability to:

  • Parse semantic versions
  • Sort semantic versions
  • Check if a semantic version fits within a set of constraints
  • Optionally work with a v prefix

Stability: Active GoDoc Go Report Card

If you are looking for a command line tool for version comparisons please see vert which uses this library.

Package Versions

Note, import github.com/Masterminds/semver/v3 to use the latest version.

There are three major versions fo the semver package.

  • 3.x.x is the stable and active version. This version is focused on constraint compatibility for range handling in other tools from other languages. It has a similar API to the v1 releases. The development of this version is on the master branch. The documentation for this version is below.
  • 2.x was developed primarily for dep. There are no tagged releases and the development was performed by @sdboyer. There are API breaking changes from v1. This version lives on the 2.x branch.
  • 1.x.x is the original release. It is no longer maintained. You should use the v3 release instead. You can read the documentation for the 1.x.x release here.

Parsing Semantic Versions

There are two functions that can parse semantic versions. The StrictNewVersion function only parses valid version 2 semantic versions as outlined in the specification. The NewVersion function attempts to coerce a version into a semantic version and parse it. For example, if there is a leading v or a version listed without all 3 parts (e.g. v1.2) it will attempt to coerce it into a valid semantic version (e.g., 1.2.0). In both cases a Version object is returned that can be sorted, compared, and used in constraints.

When parsing a version an error is returned if there is an issue parsing the version. For example,

v, err := semver.NewVersion("1.2.3-beta.1+build345")

The version object has methods to get the parts of the version, compare it to other versions, convert the version back into a string, and get the original string. Getting the original string is useful if the semantic version was coerced into a valid form.

Sorting Semantic Versions

A set of versions can be sorted using the sort package from the standard library. For example,

raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
vs := make([]*semver.Version, len(raw))
for i, r := range raw {
    v, err := semver.NewVersion(r)
    if err != nil {
        t.Errorf("Error parsing version: %s", err)
    }

    vs[i] = v
}

sort.Sort(semver.Collection(vs))

Checking Version Constraints

There are two methods for comparing versions. One uses comparison methods on Version instances and the other uses Constraints. There are some important differences to notes between these two methods of comparison.

  1. When two versions are compared using functions such as Compare, LessThan, and others it will follow the specification and always include pre-releases within the comparison. It will provide an answer that is valid with the comparison section of the spec at https://semver.org/#spec-item-11
  2. When constraint checking is used for checks or validation it will follow a different set of rules that are common for ranges with tools like npm/js and Rust/Cargo. This includes considering pre-releases to be invalid if the ranges does not include one. If you want to have it include pre-releases a simple solution is to include -0 in your range.
  3. Constraint ranges can have some complex rules including the shorthand use of ~ and ^. For more details on those see the options below.

There are differences between the two methods or checking versions because the comparison methods on Version follow the specification while comparison ranges are not part of the specification. Different packages and tools have taken it upon themselves to come up with range rules. This has resulted in differences. For example, npm/js and Cargo/Rust follow similar patterns while PHP has a different pattern for ^. The comparison features in this package follow the npm/js and Cargo/Rust lead because applications using it have followed similar patters with their versions.

Checking a version against version constraints is one of the most featureful parts of the package.

c, err := semver.NewConstraint(">= 1.2.3")
if err != nil {
    // Handle constraint not being parsable.
}

v, err := semver.NewVersion("1.3")
if err != nil {
    // Handle version not being parsable.
}
// Check if the version meets the constraints. The variable a will be true.
a := c.Check(v)

Basic Comparisons

There are two elements to the comparisons. First, a comparison string is a list of space or comma separated AND comparisons. These are then separated by || (OR) comparisons. For example, ">= 1.2 < 3.0.0 || >= 4.2.3" is looking for a comparison that's greater than or equal to 1.2 and less than 3.0.0 or is greater than or equal to 4.2.3.

The basic comparisons are:

  • =: equal (aliased to no operator)
  • !=: not equal
  • >: greater than
  • <: less than
  • >=: greater than or equal to
  • <=: less than or equal to

Working With Prerelease Versions

Pre-releases, for those not familiar with them, are used for software releases prior to stable or generally available releases. Examples of pre-releases include development, alpha, beta, and release candidate releases. A pre-release may be a version such as 1.2.3-beta.1 while the stable release would be 1.2.3. In the order of precedence, pre-releases come before their associated releases. In this example 1.2.3-beta.1 < 1.2.3.

According to the Semantic Version specification, pre-releases may not be API compliant with their release counterpart. It says,

A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.

SemVer's comparisons using constraints without a pre-release comparator will skip pre-release versions. For example, >=1.2.3 will skip pre-releases when looking at a list of releases while >=1.2.3-0 will evaluate and find pre-releases.

The reason for the 0 as a pre-release version in the example comparison is because pre-releases can only contain ASCII alphanumerics and hyphens (along with . separators), per the spec. Sorting happens in ASCII sort order, again per the spec. The lowest character is a 0 in ASCII sort order (see an ASCII Table)

Understanding ASCII sort ordering is important because A-Z comes before a-z. That means >=1.2.3-BETA will return 1.2.3-alpha. What you might expect from case sensitivity doesn't apply here. This is due to ASCII sort ordering which is what the spec specifies.

Hyphen Range Comparisons

There are multiple methods to handle ranges and the first is hyphens ranges. These look like:

  • 1.2 - 1.4.5 which is equivalent to >= 1.2 <= 1.4.5
  • 2.3.4 - 4.5 which is equivalent to >= 2.3.4 <= 4.5

Note that 1.2-1.4.5 without whitespace is parsed completely differently; it's parsed as a single constraint 1.2.0 with prerelease 1.4.5.

Wildcards In Comparisons

The x, X, and * characters can be used as a wildcard character. This works for all comparison operators. When used on the = operator it falls back to the patch level comparison (see tilde below). For example,

  • 1.2.x is equivalent to >= 1.2.0, < 1.3.0
  • >= 1.2.x is equivalent to >= 1.2.0
  • <= 2.x is equivalent to < 3
  • * is equivalent to >= 0.0.0

Tilde Range Comparisons (Patch)

The tilde (~) comparison operator is for patch level ranges when a minor version is specified and major level changes when the minor number is missing. For example,

  • ~1.2.3 is equivalent to >= 1.2.3, < 1.3.0
  • ~1 is equivalent to >= 1, < 2
  • ~2.3 is equivalent to >= 2.3, < 2.4
  • ~1.2.x is equivalent to >= 1.2.0, < 1.3.0
  • ~1.x is equivalent to >= 1, < 2

Caret Range Comparisons (Major)

The caret (^) comparison operator is for major level changes once a stable (1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts as the API stability level. This is useful when comparisons of API versions as a major change is API breaking. For example,

  • ^1.2.3 is equivalent to >= 1.2.3, < 2.0.0
  • ^1.2.x is equivalent to >= 1.2.0, < 2.0.0
  • ^2.3 is equivalent to >= 2.3, < 3
  • ^2.x is equivalent to >= 2.0.0, < 3
  • ^0.2.3 is equivalent to >=0.2.3 <0.3.0
  • ^0.2 is equivalent to >=0.2.0 <0.3.0
  • ^0.0.3 is equivalent to >=0.0.3 <0.0.4
  • ^0.0 is equivalent to >=0.0.0 <0.1.0
  • ^0 is equivalent to >=0.0.0 <1.0.0

Validation

In addition to testing a version against a constraint, a version can be validated against a constraint. When validation fails a slice of errors containing why a version didn't meet the constraint is returned. For example,

c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
if err != nil {
    // Handle constraint not being parseable.
}

v, err := semver.NewVersion("1.3")
if err != nil {
    // Handle version not being parseable.
}

// Validate a version against a constraint.
a, msgs := c.Validate(v)
// a is false
for _, m := range msgs {
    fmt.Println(m)

    // Loops over the errors which would read
    // "1.3 is greater than 1.2.3"
    // "1.3 is less than 1.4"
}

Contribute

If you find an issue or want to contribute please file an issue or create a pull request.

Security

Security is an important consideration for this project. The project currently uses the following tools to help discover security issues:

If you believe you have found a security vulnerability you can privately disclose it through the GitHub security page.

semver's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

semver's Issues

Caret by default

We need a way of simulating carat-by-default behavior. That is, if you call semver.NewConstraint("1.0.0"), it will interpret that as "^1.0.0". To get only and exactly that version, you'd have to call semver.NewConstraint("=1.0.0").

When I started looking at this, it had more far-reaching implications than I initially thought :(

Pre-release versions handling not consistent

In the documentation about pre-release version:

In the order of precidence, pre-releases come before their associated releases. In this example 1.2.3-beta.1 < 1.2.3.

But code prove this claim to be not respected and behavior not consistent:

v, _ := semver.NewVersion("1.2.3-beta.1")

c1, _ := semver.NewConstraint("> 1.2.3")
c2, _ := semver.NewConstraint("< 1.2.3")

if ok, reasons := c1.Validate(v); !ok {
	for _, reason := range reasons {
		fmt.Println(reason)
	}
}

if ok, reasons := c2.Validate(v); !ok {
	for _, reason := range reasons {
		fmt.Println(reason)
	}
}

Will output:

1.2.3-beta.1 is less than or equal to 1.2.3
1.2.3-beta.1 is greater than or equal to 1.2.3

is something weird with 0.x.x constraints ?

Hi,

While i m working on the cli, i found out a weird behavior about constraints when using a pattern starting with a major = 0

Please see:

$ go run main.go -filter 1.x -sort 2.6.5 "1.1.1" 1.1.2 0.0.5
1.1.1
1.1.2
[mh-cbon@pc15-home cli] $ go run main.go -filter 1.x.x -sort 2.6.5 "1.1.1" 1.1.2 0.0.5
1.1.1
1.1.2
[mh-cbon@pc15-home cli] $ go run main.go -filter 0.x.x -sort 2.6.5 "1.1.1" 1.1.2 0.0.5
0.0.5
1.1.1
1.1.2
2.6.5
[mh-cbon@pc15-home cli] $ go run main.go -filter 0.x -sort 2.6.5 "1.1.1" 1.1.2 0.0.5
0.0.5
1.1.1
1.1.2
2.6.5
[mh-cbon@pc15-home cli] $ go run main.go -filter 2.x -sort 2.6.5 "1.1.1" 1.1.2 0.0.5
2.6.5

How to support the time version, like as r2016.01.01

How to support the time version, like as r2016.01.01, or like as r2011-03-15 or like as go.weekly.2011-12-14

$ git ls-remote http://gopkg.in/mgo.v2
3f83fa5005286a7fe593b055f0d7771a7dce4655 HEAD
3f83fa5005286a7fe593b055f0d7771a7dce4655 refs/heads/master
c91e44ec198183371f67220d4e3ee563ee5cc10f refs/pull/97/head
d3ff508e020127390aad8e6abf3311345fe8da78 refs/pull/97/merge
e5d775ad02fa2dfc96d4a4cc91d2938d49bdd4c3 refs/pull/99/head
d8f5b176c5cb051ec61b5f7106b35526a7375dce refs/tags/go.2011-08-17
d8f5b176c5cb051ec61b5f7106b35526a7375dce refs/tags/go.r59
dcd742c64912694716517bfb7141283d02d76384 refs/tags/go.r60
3f4829794e873359609f558cc21445145da54796 refs/tags/go.weekly.2011-12-14
60e3fb2629f6f12661c0737b219b1bd881cbc3d2 refs/tags/go.weekly.2012-01-20
447d0e2e62baa0b44fa26c105b176672a88de215 refs/tags/r2011-03-15
e4bd393b94ffd3a26813cfe74569af4d57e7c5dc refs/tags/r2011-03-17
6c68d933922c946a26f9796c3dd29e7e96021fb4 refs/tags/r2011-03-20
1fd865ee346ed909725b1a5138a033b6dfc2e705 refs/tags/r2011-03-24
447d0e2e62baa0b44fa26c105b176672a88de215 refs/tags/r2011.03.15
e4bd393b94ffd3a26813cfe74569af4d57e7c5dc refs/tags/r2011.03.17
6c68d933922c946a26f9796c3dd29e7e96021fb4 refs/tags/r2011.03.20
1fd865ee346ed909725b1a5138a033b6dfc2e705 refs/tags/r2011.03.24
2c4c03eedadcffcc4f1884ac7c343cd8727b1fc1 refs/tags/r2011.04.09
1fa0d41f2deb3ec0fd73dde2b6b93919da5f8080 refs/tags/r2011.04.13
8856b721b1b3c7f66e1f86384b5c6478a93887a7 refs/tags/r2011.04.19
5284467e72033c936abaf3624dc9da532ed83e59 refs/tags/r2011.04.24
45b249cb231488859b5fa2a7de52ac1d07c60c2b refs/tags/r2011.04.28
50b7d712aee1b3a874929a07e22d02e416c190f7 refs/tags/r2011.05.12
bac454ccffeec8485e770a6e3d9095ac41f6789c refs/tags/r2011.05.24
819c5251834521bd0ab304aee127ffc3dcd63636 refs/tags/r2011.06.24
955f9645b41aad079aba67c8d22a0284507dad82 refs/tags/r2011.07.15
4ac916566d5b7e41c27f5f4a20f6c2f8bc2dcf28 refs/tags/r2011.07.28
49ea3870e84e003045e5ad0721f3665ad43cda1d refs/tags/r2011.08.02
d8f5b176c5cb051ec61b5f7106b35526a7375dce refs/tags/r2011.08.21
9a87d973f95fa19487f8293730e73792a3d63778 refs/tags/r2011.09.15
aeecf000f12161500c20b44c253cab9c01449497 refs/tags/r2011.10.29
dcd742c64912694716517bfb7141283d02d76384 refs/tags/r2011.12.18
a4033b400eedcf2961c43d05a766b872274b659b refs/tags/r2012.01.22
41512ec3abf54c189f495f7783a91f24411cf457 refs/tags/r2012.02.02
f13bea7d47b0e33e977c5266274381ff544417e4 refs/tags/r2012.03.04
25d859ee5cf9d5a4f331b41f3f5f55a60981b1de refs/tags/r2012.04.08
1227e67c223d2cc895a26fdad17d81b925c08eab refs/tags/r2012.05.21
44dcc642bb0878687fdafede04b052e24b92e9f6 refs/tags/r2012.06.22
6891649c6058bd9d9673fa3a26f228b14c084c81 refs/tags/r2012.08.07
4927bc5b144fe41be53806725ca8e2a0deabfea5 refs/tags/r2012.08.22
b48af10f9077becfc8a16858f81010779a1d8db0 refs/tags/r2012.08.23
ecc5141140853a61cfc3b42c52a10088f77a77e6 refs/tags/r2012.09.05
9fe1bd153017197d70c1b67bfcd8a778eed9fd96 refs/tags/r2012.10.04
3cc10a22ab9009f75d734e99d0ee5a3c2c0148b5 refs/tags/r2012.10.28
39723d9d045fb83fa241ee5cdc474472dd4b1783 refs/tags/r2013.01.20
ad9402baa229c5a791d63a45197e1a60f290865f refs/tags/r2013.04.05
0db23adb3ff3f570149d7ac9c42e75e05bc854d8 refs/tags/r2013.04.11
1ed4068c77793fa29cff7809424f8863ac8c6541 refs/tags/r2013.05.19
d1c9089508537c1d8841c91cc31282433df462ee refs/tags/r2013.07.21
15571ea624ced126e8e56ead0b9f772b3d514d1a refs/tags/r2013.09.04
bb07d904984913eb3bec4e72586624c6f2e027e1 refs/tags/r2013.11.18
121e4ef93103097b8f2940cd8a6c43527503b800 refs/tags/r2014.01.25
ad99486d2d1583175c6e6579fe12792d1f76ebba refs/tags/r2014.03.12
3116f72d0fe0216b75201cb3a1ae61c82c2a4d63 refs/tags/r2014.07.21
231ce7e0549b5ce60d35ed805bff505cab7098e4 refs/tags/r2014.10.12
c6a7dce14133ccac2dcac3793f1d6e2ef048503a refs/tags/r2015.01.24
01ee097136da162d1dd3c9b44fbdf3abf4fd6552 refs/tags/r2015.05.29
3f8090ae6a04fa37c3f8299c62ed27b192835e73 refs/tags/r2015.06.03
f402e3a216db333ae6b3ba68b9152a34a0bc6984 refs/tags/r2015.10.05
e30de8ac9ae3b30df7065f766c71f88bba7d4e49 refs/tags/r2015.12.06
d90005c5262a3463800497ea5a89aed5fe22c886 refs/tags/r2016.02.04
b6121c6199b7beecde08e6853f256a6475fe8031 refs/tags/r2016.08.01

Stop using pointer receiver on *Version

That we use a pointer receiver for Version's methods has frustrated me for a while for two basic reasons:

  • It meant doing a lot of nil-checking all over the API, rather than just being able to call methods that would respond normally. The areEq() func is just one place that I sorta shimmed around this:

    func areEq(v1, v2 *Version) bool {
      if v1 == nil && v2 == nil {
          return true
      }
    
      if v1 != nil && v2 != nil {
          return v1.Equal(v2)
      }
      return false
    }
  • It also means that all the version objects we create (and therefore also, all the constraint objects we create) allocate and escape to the heap. (see output of go test -gcflags -m)

Both of these things annoyed me, but what really pushed me over the edge was working on rangeConstraint: I realized that I was effectively using a nil min or max as either 'zero' or 'infinity', respectively. Conflating two meanings into the same nil space is generally a bad idea, and had the effect of making it very hard to write any helper methods for computation on versions from ranges: I couldn't know if a nil meant a max or a min (or an unintentionally empty version).

So, this issue is to drop the pointer receiver. Doing so will mean a bit of refactoring to tighten things back up, but it'll be worth it.

We need to do this before a 2.0.0 release; it'll be a breaking API change, so we need to lump it in with everything from #7.

Regression in v3 or unexpected breaking behavior for NewConstraint(str)

I experienced a downstream issue in Helm v3 that worked well in Helm v2. This issue relates to a change of masterminds/semver v2 to v3.

The downstream issue is described here briefly, but it is really an upstream issue of masterminds/semver, and is reproducible in this Go Playground.

In short, the 001 variant of the following versions are now causing an issue for the NewConstraint() function.

	var version = ""
	version = "0.9.0-alpha.100" // OK
	version = "0.9.0-alpha.001" // ERROR
	var err error
	
	_, err = semver.NewConstraint(version)
	if err != nil {
		fmt.Println(err)
	} else {
        	fmt.Println("Everything is OK!")
	}

Add support for SQL

Would love to use this as a struct DB field with gorm. I created a PR that adds this support in the Version type.

#131

Asterisk and pre-release versions

I would assume that * constraint just accepts any version that is >=0.0.0, but it does not accept eg. 3.0.0-rc2.

Is this a bug or is it on purpose? (Can't recall at the moment how package managers behave in this scenario, but some of them also define a separate stability flag which modifies the behaviour of constraint checking, so in some cases the above example would work)

Ranges and pre-release bug

Consider the case of a range > 0 and the versions 0.0.1-beta and 0.

This range will return false for both. But, isn't 0.0.1-beta greater that 0? If a range like > 0-a is used than a version like 0 doesn't work. It's one of those edge cases when #21/#23 was fixed.

@technosophos this came about when I was trying to update helm for the latest release to deal with the pre-release issue.

Considering two options:

  1. Handling this special edge case in the code.
  2. Adding a flag to the constraints struct for UsingPrerelease to force it into place without putting a prerelease on the constraint.

@technosophos @sdboyer thoughts?

NewConstraint returns nil

Hello, semver.NewConstraint returns err for >= 1.3.1 < 2

c, err := semver.NewConstraint(">= 1.3.1 < 2")

Remove header line from license

The license appears to be MIT, but the first line The Masterminds confuses the github UI and the licensee gem, so that it isn't obvious until manual investigation that this is MIT-licensed. Please remove the first line so that tooling isn't confused. Thanks.

Invalid constraint comparison for <11

I would expect the following to both fail the constraint check here, but < 11 is being interpreted differently

c := "< 11.0"
d := "< 11"

withDot, _ := semver.NewConstraint(c)
noDot, _ := semver.NewConstraint(d)

version, _ := semver.NewVersion("11.1.0")

fmt.Println(withDot.Check(version)) // false
fmt.Println(noDot.Check(version))   // true

Namely < 11 is hitting this branch and being marked as dirty where as < 11.0 does not

GreaterThan function does not account for numerics within pre-release

If I'm reading semver.org correctly,

1.0.0-beta.2 < 1.0.0-beta.11  and 2.1.0 < 2.1.1.

This looks ok.

 v1,_ = semver.NewVersion("7.42.0");
 v2,_ = semver.NewVersion("7.43.0");
 fmt.Println(v1.GreaterThan(v2));

returns

false

However...

var v1,_= semver.NewVersion("7.43.0-SNAPSHOT.99");
var v2,_ = semver.NewVersion("7.43.0-SNAPSHOT.103");
fmt.Println(v1.GreaterThan(v2));

returns

true

ValidPrerelease should end in $

I was reading the code and came across:

const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`

in version.go. I don't know go, but it seems to me to mis-implement the metadata extension, in that this regex matches '0==='
i.e.

	matched, err := regexp.MatchString(`^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`, "0===")

gives true, nil.

uint64s for 2.0?

I'm kinda loathe to open this. Honestly, it sorta annoys me even just writing this. But right now, Version uses int64 to represent the major, minor, and patch components of a version, which means negative numbers are allowed. The spec's quite clear on this - these number must be nonnegative.

I get that using uint64 can make some things more annoying, but it seems maybe worth it to switch over to using a type that matches the spec exactly, rather than leaving open the possibility of errors that we have to handle?

* constraint failing

When using * as a constraint it is failing with versions when it should return true for all versions.

Convert ranges to tilde or carat form, if possible

Right now, I'm staring at this diff:

 - package: github.com/Masterminds/vcs
-  version: ^1.7.0
+  version: '>=1.7.0, <2.0.0'
 - package: github.com/codegangsta/cli
-  version: ~1.14.0
+  version: '>=1.14.0, <1.15.0'

We need to store the input to the constraint, and then return that back out on a String() call, as with Versions.

The problem there, of course, is when you start combining constraints - it's just no longer possible to respect the original exact user input. So, as soon as a programmatic combination occurs, we should probably just drop the original input, and recalculate it as needed on a String() call. When doing that, I think we should also then prefer to express ranges with carats and tildes, if it is correct to do so

constraintCaret broken

Commit 6e10208 broke constraintCaret. If the major version is greater than constraint major version and if some combination of minor or patch versions are equal, the constraint falsely passes. The following test case added to TestConstraintCheck reproduces the behavior.

		{"^1.1.0", "2.1.0", false},
		{"^1.2.0", "2.2.1", false},
--- FAIL: TestConstraintCheck (0.00s)
    constraints_test.go:197: Constraint "^1.0.0" failing with "2.1.0"
    constraints_test.go:197: Constraint "^1.2.0" failing with "2.2.1"

Major Release Coming

The semver package has been around more than 3 and a half years. That is a long time for a v1. This issue documents the current plan.

Features:

The features we would like to see incorporated are:

  • Range support that mirrors what NPM and possibly others are doing. For instance, no need for a comma between multiple parts of a range
  • Go module support
  • Better error handling

Known Issues:

  • Handling the existing v2 branch that was not released

Process:

  • A release-v1 branch will be created to continue on with the existing v1 work. This will enable longer term support to deal with bugs.
  • New work will for on master and that includes breaking changes.

Be able to print out Constraints to string

Right now, it's not possible for error messages etc. to print out the constraint itself. It would be good to have inverse function to parseConstraint and be able to get back the constraint as printable string.

Miscomparison of versions with "negative" pre-release identifiers

Consider 1.0.0-beta.-3, 1.0.0-beta.-2, and 1.0.0-beta.4. Each of these is a valid SemVer version string. Per SemVer,

Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by comparing each dot separated identifier from left to right until a difference is found as follows: identifiers consisting of only digits are compared numerically and identifiers with letters or hyphens are compared lexically in ASCII sort order. Numeric identifiers always have lower precedence than non-numeric identifiers.

This means that 1.0.0-beta.4 should be treated as having a numeric second pre-release identifier, while 1.0.0-beta.-3 and 1.0.0-beta.-2 should be treated as having an ASCII alphanumeric second pre-release identifier. Therefore, the correct ordering of these versions should be:

1.0.0-beta.4 < 1.0.0-beta.-2 < 1.0.0-beta.-3

However, because the library parses the pre-release version identifiers -2 and -3 as numbers, it instead produces the reverse, incorrect ordering:

1.0.0-beta.-3 < 1.0.0-beta.-2 < 1.0.0-beta.4

Constraints marshaller

disclaimer: I'm new to golang, might be doing something wrong or missing something in front of me

Could Constraints include a default json/yaml marshaller similar to #44 so that Constraints could be used directly when unmarshalling into a struct without writing a custom one?

Feel free to close whenever. Thanks for your time ๐Ÿ‘

"3.0" should not match "<= 2.x"

As discussed in Masterminds/glide#483, the tip of 2.x incorrectly indicates that 3.0 satisfies the constraint <= 2.x. This is a regression from all 1.x releases.

Sample code:

    c, _ := semver.NewConstraint("<= 2.x");
    v, _ := semver.NewVersion("3.0")
    fmt.Println(c.Matches(v))

outputs <nil>.

Constraints fail

It would appear that constraints fail when used with pre-release versions. In particular, a pre-release is considered newer incorrectly. Program below against v1.4.2; output above:


3.3.0 < 5.0.0 == true
3.3.0 check(< 5.0.0) == true
3.3.0-47 < 5.0.0 == true
3.3.0-47 check(< 5.0.0) == false
3.3.0-47 is greater than or equal to 5.0.0

and the code:

package main

import (
	"fmt"
	"github.com/Masterminds/semver"
)

func main() {

	tgt := semver.MustParse("5.0.0")

	// Simple constraint
	c := "< 5.0.0"
	cc, err := semver.NewConstraint(c)
	if err != nil {
		panic(err)
	}

	// Vanilla version
	sver := semver.MustParse("3.3.0")

	// Less than target, works as expected
	fmt.Printf("%v < %v == %v\n", sver, tgt, sver.LessThan(tgt))

	// Our vanilla version should also meet constraint
	fmt.Printf("%v check(%v) == %v\n", sver, c, cc.Check(sver))

	// Cool, let's create a version with a pre-release
	pver := semver.MustParse("3.3.0-47")

	// Less than target, works as expected
	fmt.Printf("%v < %v == %v\n", pver, tgt, pver.LessThan(tgt))

	// But the constraint, umm umm
	fmt.Printf("%v check(%v) == %v\n", pver, c, cc.Check(pver))

	// Let's ask why
	_, errs := cc.Validate(pver)
	for _, e := range errs {
		fmt.Println(e)
	}
}

NewVersion allows non-SemVer versions

The following versions are not valid SemVer, yet are parsed as valid by NewVersion:

  • 1 โ€“ "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers"
  • 1.0 โ€“ "A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers"
  • 01.01.01 โ€“ Major, minor, and patch versions "MUST NOT contain leading zeroes"
  • 1.0.0-beta.02 โ€“ Regarding pre-release identifiers, "Numeric identifiers MUST NOT include leading zeroes."

add a cli

Hi,

Is it possible to consider to add a cmd folder with a cli program to manipulate a list of version coming from a pipe on the shell.

For example i run $ git tag, i get a long list of versions, i want only the last one, for that to happen correctly i need a program to read, parse,sort the lines as semver values.

0.0.35
0.0.36
0.0.37
0.0.38
0.0.39
0.0.4
0.0.5
0.0.6
0.0.7
0.0.8
0.0.9
1.0.0

Optionally, the program could receive range argument to filter the list.

For simple usage on all platform, it would be great to embed the capability to return only the oldest/newest version of the result list.

Find best match given an input and an array

Hi thanks for this lib,

I was wondering if it's possible to use it to achieve this:

Given an array of possible versions, find the highest matching one. So e.g:

["1.2.3", "1.2.4", "1.3.0"]

so ~1.2.1 should match 1.2.4 and ^1.2.1 should match 1.3.0 (since caret allow minor version increments).

Is this possible with this lib? I couldn't see something obvious in the readme to help me do it so just checking.

NewConstraintIC interprets a dash in the prerelease tag as a range separator

In golang/dep, we are using semver.NewConstraintIC to parse raw strings as constraints and some of them aren't being handled as expected:

  • v1.12.0-g2fd980e properly returns (semver.rangeConstraint) ^1.12.0-g2fd980e
  • v0.12.0-12-g2fd980e returns (semver.rangeConstraint) >=0.12.0, <=12.0.0-g2fd980e, when I expected ^1.12.0-g2fd980e
  • v1.12.0-2-g2fd980e returns (semver.rangeConstraint) >=1.12.0, <=2.0.0-g2fd980e, when I expected ^v1.12.0-2-g2fd980e.
  • v1.3.1-1-gf12c623 returns an unexpected (semver.none) . I expected ^1.3.1-1-gf12c623, or at the very least an error.

Essentially the dash in the prerelease tag is causing the function parse the input a range between v1.3.1 to 1-gf12c623.

I'm going to try having it first check if the input string is a valid version first, and if that pans out, submit a PR.

Special handling on ranges for pre-release versions

As mentioned in #7 , I want us to borrow another idea from dart's pub and create some special logic around how pre-release versions are interpreted with respect to ranges. This does involve breaking some math rules, but I think the benefit for users is considerable.

Here's the wording from their README, which really pretty much covers it:

Pre-release versions are excluded from most max ranges. Let's say a
user is depending on "foo" with constraint >=1.0.0 <2.0.0 and that "foo"
has published these versions:

*  `1.0.0`
*  `1.1.0`
*  `1.2.0`
*  `2.0.0-alpha`
*  `2.0.0-beta`
*  `2.0.0`
*  `2.1.0`

Versions 2.0.0 and 2.1.0 are excluded by the constraint since neither
matches <2.0.0. However, since semver specifies that pre-release versions
are lower than the non-prerelease version (i.e. 2.0.0-beta < 2.0.0, then
the <2.0.0 constraint does technically allow those.

But that's almost never what the user wants. If their package doesn't work
with foo 2.0.0, it's certainly not likely to work with experimental,
unstable versions of 2.0.0's API, which is what pre-release versions
represent.

To handle that, < version ranges don't allow pre-release versions of the
maximum unless the max is itself a pre-release, or the min is a pre-release
of the same version. In other words, a <2.0.0 constraint will prohibit not
just 2.0.0 but any pre-release of 2.0.0. However, <2.0.0-beta will
exclude 2.0.0-beta but allow 2.0.0-alpha. Likewise, >2.0.0-alpha <2.0.0 will exclude 2.0.0-alpha but allow 2.0.0-beta.

We don't have to follow their exact rules, necessarily, but the basic idea that <2.0.0 does not include, say, 2.0.0-alpha is really important. Otherwise, people who've set totally normal constraints like ^1.1.0 will find upgraded to 2.0.0 pre-releases without warning - violating the spirit of the caret.

constraints.go - NewConstraint from version ignores build suffix?

In helm/helm#6710 I've tracked down an issue for helm, both v2 and v3, relating to logic within this project. The issue stems from that creating a constraint from a version seemingly ignores build suffix of the version the constraint were created from, and when using this created constraint to run a Check() against another version, they are considered alike no matter what build suffix they have. Is this to be considered a bug for masterminds/semver itself?

NewConstraint function

semver/constraints.go

Lines 17 to 53 in 25911d3

// NewConstraint returns a Constraints instance that a Version instance can
// be checked against. If there is a parse error it will be returned.
func NewConstraint(c string) (*Constraints, error) {
// Rewrite - ranges into a comparison operation.
c = rewriteRange(c)
ors := strings.Split(c, "||")
or := make([][]*constraint, len(ors))
for k, v := range ors {
// TODO: Find a way to validate and fetch all the constraints in a simpler form
// Validate the segment
if !validConstraintRegex.MatchString(v) {
return nil, fmt.Errorf("improper constraint: %s", v)
}
cs := findConstraintRegex.FindAllString(v, -1)
if cs == nil {
cs = append(cs, v)
}
result := make([]*constraint, len(cs))
for i, s := range cs {
pc, err := parseConstraint(s)
if err != nil {
return nil, err
}
result[i] = pc
}
or[k] = result
}
o := &Constraints{constraints: or}
return o, nil
}

Check function

semver/constraints.go

Lines 55 to 73 in 25911d3

// Check tests if a version satisfies the constraints.
func (cs Constraints) Check(v *Version) bool {
// loop over the ORs and check the inner ANDs
for _, o := range cs.constraints {
joy := true
for _, c := range o {
if !c.check(v) {
joy = false
break
}
}
if joy {
return true
}
}
return false
}

support json marshaller

hi,

currently semver.Version does not support json (un)marshal interfaces.

When json encoded, it results in an empty string.
I suspect it happens because the struct won 't export any public fields.

See the demo code to work around this, alternatively,

package main

import (
	"encoding/json"
	"fmt"

	"github.com/Masterminds/semver"
)

type Jversion struct {
	*semver.Version
}

func NewJversion(s string) (*Jversion, error) {
	v, err := semver.NewVersion(s)
	if err != nil {
		return nil, err
	}
	return &Jversion{Version: v}, nil
}

func (a *Jversion) UnmarshalJSON(b []byte) error {
	var s string
	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}
	v, err := semver.NewVersion(s)
	if err != nil {
		return err
	}
	a.Version = v
	return nil
}

func (a *Jversion) MarshalJSON() ([]byte, error) {
	return json.Marshal(a.String())
}

func main() {
	s, err := NewJversion("0.0.1")
	kk(err)
	b, err2 := json.Marshal(s)
	kk(err2)
	fmt.Println(string(b))
}

func kk(err error) {
	if err != nil {
		panic(err)
	}
}

constraint Parser Error

I've encountered the following issue by way of specifying a 'pre-release' Helm chart version dependency.

When specifying a chart version of, say, v2.3.5-sha.20d586f, it is happily parsed/constrained by this package and Helm is happy as well.

However, when adding a timestamp, resulting in a chart version of v2.3.5-20161202202307-sha.e8fc5e5, this package returns a constraint Parser Error from the following logic.

In local testing, semver.NewConstraint() seems to allow up to 10 digits for the timestamp without throwing this particular error -- is there a semver rule that is being broken here in using >10 digits (14 to be precise)? Or, might this be an addressable parsing issue in this package?

Export Constraints Values

Currently when you parse a constraint you can not access the resulting versions which make up the constraints. We would like to get access to those so that when we save a result to the database, we can query them at a later date.

Version with prerelease and Constraint without it - Comparison fails even in simple cases

According to the README.md, when you want to use prerelease comparison, -0should be append in the end of the sem. version in constraint, but the case where I still do not want prerelease comparison but the version includes it, is misleading.

Example:

versionConstraint, err := semver.NewConstraint(">= 1.0.0")
if err != nil {
	return false, err
}

currVersion, err := semver.NewVersion("2.0.0-alpha")
if err != nil {
	return false, err
}

fmt.Println(versionConstraint.Check(currVersion))

The last command prints false, even though discarding the prerelease is should result in true.

Expected behavior:
When using constraint comparison, when using prerelease as input for verification, I would expect that the prerelease is discarded and the whole version is matched.

Please flush out v3 Changelog

Hi,
I'm trying to update golang/dep to use the latest version of this library but I've run into a few problems.

  • Previously you could cast the result of semver.NewConstraint to a semver.Version. Now you can't. The CHANGELOG gives no guidance on how to update this.
  • semver.NewConstraintIC seems to have disappeared and there's no guidance in the CHANGELOG on how to update it.

Confusingly, when I generate a diff of the version we're on vs. master, NewConstraintIC isn't even in it as being removed. So I'm not even sure where to get started. 24642bd...fe7c210

Lack of support for ranges without commas

Another issue trying to replace a service using the Node semver module with this library comes from ranges defined without commas. Example: >=1.2.9 <2.0.0

This will only parse that range if it is expressed with a comma separator: >=1.2.9, <2.0.0

I was able to write a function that detects this case and rewrites the ranges to match: https://github.com/heroku/heroku-buildpack-nodejs/pull/657/files#diff-a1f7727d2be8b8d167a4562b0699702aR331 but wanted to file an issue in case this is a case you would like to support.

// regex matching the semver version definitions
// Ex:
//    v1.0.0
//    9
//    8.x
const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`

 // regex matching the semver operators
const ops string = `=<|~>|!=|>|<|>=|=>|<=|\^|=|~`

 // Masterminds/semver does not support constraints like: `>1 <2`, preferring
// `>1, <2` with a comma separator. This catches this particular case and
// rewrites it
func rewriteRange(c string) string {
	constraintRangeRegex := regexp.MustCompile(fmt.Sprintf(
		`^\s*(%s)(\s*%s)\s*(%s)(\s*%s)$`,
		ops, cvRegex, ops, cvRegex,
	))

 	ors := strings.Split(c, "||")
	out := make([]string, len(ors))

 	for i, v := range ors {
		m := constraintRangeRegex.FindStringSubmatch(v)
		if m != nil {
			out[i] = fmt.Sprintf("%s%s, %s%s", m[1], m[2], m[12], m[13])
		} else {
			out[i] = v
		}
	}

 	return strings.Join(out, `||`)
}

Build failing because of missing package

Hello,

I see the following issue when i try to build the code

+ gb vendor fetch github.com/Masterminds/sprig

fetching recursive dependency github.com/Masterminds/goutils

fetching recursive dependency github.com/Masterminds/semver/v3

FATAL: command "fetch" failed: lstat /tmp/gb-vendor-596382780/v3: no such file or directory

FATAL: command "vendor" failed: exit status 1

Is there any change in the package?

Cannot use v3

Trying to upgrade to v3.0.0

--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.12
 
 require (
[...]
-       github.com/Masterminds/semver v1.5.0
+       github.com/Masterminds/semver v3.0.0
[...]

but Go wants /v3 suffix on the module:

go: finding github.com/Masterminds/semver v3.0.0
go: finding github.com/Masterminds/semver v3.0.0
go: errors parsing go.mod:
/home/lukasz/work/karma/go.mod:7: require github.com/Masterminds/semver: version "v3.0.0" invalid: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3

so added it:

--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.12
 
 require (
[...]
-       github.com/Masterminds/semver v1.5.0
+       github.com/Masterminds/semver/v3 v3.0.0
[...]

but getting an error during go build and go mod tidy:

$ go mod tidy
go: github.com/Masterminds/semver/[email protected]: go.mod has non-.../v3 module path "github.com/Masterminds/semver" (and .../v3/go.mod does not exist) at revision v3.0.0

Comparison against dirty versions fails

I notice that with v3.0.1 and v.3.0.2 the comparison fails when the constraint is build with a dirty version (eg. 4.5).

  func main() {
	versionConstraint, err := semver.NewConstraint("1.5.0 - 4.5")
	if err != nil {
		return
	}

	currVersion, err := semver.NewVersion("3.7.0")
	if err != nil {
		return
	}

	fmt.Println(versionConstraint.Check(currVersion))
}

In The Go Playground, where v.1.5.0 is used, there is no such a bug.
From debugging I see that the problem is in constraints.go, line 404 (v3.0.1)

^0.0 constraint matches major versions that is shouldn't (e.g. 1.0.0)

It appears that if the minor and patch versions match the ^0.0 constraint then Check returns true regardless of what the major version is.

package main

import "github.com/Masterminds/semver"

func main() {
	c, _ := semver.NewConstraint( "^0.0")
	v, _ := semver.NewVersion("1.0.0")
	println(c.Check(v))
}

prints true but I would expect based on the readme (^0.0 is equivalent to >=0.0.0 <0.1.0) that it should print false

Feature: Version check that gives info about failures

It would be nice to have a variation of Constraint.Check() that could give information, on failure, about why it failed. I'm imagining something like this:

err := c.CheckError(myVer)
if err != nil {
    fmt.Printf("myVersion does not meet the requirements because: %s", err)
}

Failed parsing versions with 4 parts

There are many nuget packages which include 4 parts.
for example :
Microsoft.AspNet.Mvc.zh-Hant version 3.0.50813.1

When trying to convert the version string into a version (method NewVersion) it fails on line :
m := versionRegex.FindStringSubmatch(v)
and returns nil

Constraints malfunction?

Since these tests are successful:

{">0", "0.0.1-alpha", true},
{">=0", "0.0.1-alpha", true},

shouldn't the following test also succeed? (They don't)

 {">0.0", "0.0.1-alpha", true}, 
 {">=0.0", "0.0.1-alpha", true}, 
 {">0.0.0", "0.0.1-alpha", true}, 
 {">=0.0.0", "0.0.1-alpha", true}, 

constraint * does not match versions with a pre-release string

simple test

package main

import (
	"fmt"

	"github.com/Masterminds/semver"
)


func main() {
	versions := []string{"0.0.1", "0.0.1-123", "0.0.1-alpha", "0.0.1+123", "0.0.1-123+123"}
	constraint, _ := semver.NewConstraint("*")
	for _, ver := range versions {
		test, err := semver.NewVersion(ver)
		if err != nil {
			fmt.Printf("error parsing version %q: %s\n", ver, err)
			continue
		}
		if constraint.Check(test) {
			fmt.Printf("version %q matches constraint\n", ver)
		}
	}
}

output:

version "0.0.1" matches constraint
version "0.0.1+123" matches constraint

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.