Skip to content

Commit

Permalink
Merge pull request #45 from findkim/strict-semver
Browse files Browse the repository at this point in the history
Add new constructor that strictly adheres to semver specs
  • Loading branch information
mitchellh committed Jan 7, 2019
2 parents b5a281d + a3eb209 commit d40cf49
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 8 deletions.
35 changes: 29 additions & 6 deletions version.go
Expand Up @@ -10,14 +10,25 @@ import (
)

// The compiled regular expression used to test the validity of a version.
var versionRegexp *regexp.Regexp
var (
versionRegexp *regexp.Regexp
semverRegexp *regexp.Regexp
)

// The raw regular expression string used for testing the validity
// of a version.
const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
`?`
const (
VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
`?`

// SemverRegexpRaw requires a separator between version and prerelease
SemverRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
`(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
`(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
`?`
)

// Version represents a single version.
type Version struct {
Expand All @@ -30,12 +41,24 @@ type Version struct {

func init() {
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$")
}

// NewVersion parses the given version and returns a new
// Version.
func NewVersion(v string) (*Version, error) {
matches := versionRegexp.FindStringSubmatch(v)
return newVersion(v, versionRegexp)
}

// NewSemver parses the given version and returns a new
// Version that adheres strictly to SemVer specs
// https://semver.org/
func NewSemver(v string) (*Version, error) {
return newVersion(v, semverRegexp)
}

func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
matches := pattern.FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed version: %s", v)
}
Expand Down
85 changes: 83 additions & 2 deletions version_test.go
Expand Up @@ -10,6 +10,7 @@ func TestNewVersion(t *testing.T) {
version string
err bool
}{
{"", true},
{"1.2.3", false},
{"1.0", false},
{"1", false},
Expand All @@ -32,14 +33,56 @@ func TestNewVersion(t *testing.T) {
{"foo1.2.3", true},
{"1.7rc2", false},
{"v1.7rc2", false},
{"1.0-", false},
}

for _, tc := range cases {
_, err := NewVersion(tc.version)
if tc.err && err == nil {
t.Fatalf("expected error for version: %s", tc.version)
t.Fatalf("expected error for version: %q", tc.version)
} else if !tc.err && err != nil {
t.Fatalf("error for version %s: %s", tc.version, err)
t.Fatalf("error for version %q: %s", tc.version, err)
}
}
}

func TestNewSemver(t *testing.T) {
cases := []struct {
version string
err bool
}{
{"", true},
{"1.2.3", false},
{"1.0", false},
{"1", false},
{"1.2.beta", true},
{"1.21.beta", true},
{"foo", true},
{"1.2-5", false},
{"1.2-beta.5", false},
{"\n1.2", true},
{"1.2.0-x.Y.0+metadata", false},
{"1.2.0-x.Y.0+metadata-width-hypen", false},
{"1.2.3-rc1-with-hypen", false},
{"1.2.3.4", false},
{"1.2.0.4-x.Y.0+metadata", false},
{"1.2.0.4-x.Y.0+metadata-width-hypen", false},
{"1.2.0-X-1.2.0+metadata~dist", false},
{"1.2.3.4-rc1-with-hypen", false},
{"1.2.3.4", false},
{"v1.2.3", false},
{"foo1.2.3", true},
{"1.7rc2", true},
{"v1.7rc2", true},
{"1.0-", true},
}

for _, tc := range cases {
_, err := NewSemver(tc.version)
if tc.err && err == nil {
t.Fatalf("expected error for version: %q", tc.version)
} else if !tc.err && err != nil {
t.Fatalf("error for version %q: %s", tc.version, err)
}
}
}
Expand Down Expand Up @@ -91,6 +134,44 @@ func TestVersionCompare(t *testing.T) {
}
}

func TestVersionCompare_versionAndSemver(t *testing.T) {
cases := []struct {
versionRaw string
semverRaw string
expected int
}{
{"0.0.2", "0.0.2", 0},
{"1.0.2alpha", "1.0.2-alpha", 0},
{"v1.2+foo", "v1.2+beta", 0},
{"v1.2", "v1.2+meta", 0},
{"1.2", "1.2-beta", 1},
{"v1.2", "v1.2-beta", 1},
{"1.2.3", "1.4.5", -1},
{"v1.2", "v1.2.0.0.1", -1},
{"v1.0.3-", "v1.0.3", -1},
}

for _, tc := range cases {
ver, err := NewVersion(tc.versionRaw)
if err != nil {
t.Fatalf("err: %s", err)
}

semver, err := NewSemver(tc.semverRaw)
if err != nil {
t.Fatalf("err: %s", err)
}

actual := ver.Compare(semver)
if actual != tc.expected {
t.Fatalf(
"%s <=> %s\nexpected: %d\n actual: %d",
tc.versionRaw, tc.semverRaw, tc.expected, actual,
)
}
}
}

func TestComparePreReleases(t *testing.T) {
cases := []struct {
v1 string
Expand Down

0 comments on commit d40cf49

Please sign in to comment.