From de6c977cfe014facfefaa8fc382f1ba3d239e499 Mon Sep 17 00:00:00 2001 From: mh-cbon Date: Sat, 11 Jun 2016 11:30:53 +0200 Subject: [PATCH] Add methods to increment version number --- version.go | 99 +++++++++++++++++++++++++++- version_test.go | 167 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 2 deletions(-) diff --git a/version.go b/version.go index dbb93f8..e5cd4b9 100644 --- a/version.go +++ b/version.go @@ -12,18 +12,25 @@ import ( // The compiled version of the regex created at init() is cached here so it // only needs to be created once. var versionRegex *regexp.Regexp +var validPrereleaseRegex *regexp.Regexp var ( // ErrInvalidSemVer is returned a version is found to be invalid when // being parsed. - ErrInvalidSemVer = errors.New("Invalid Semantic Version") + ErrInvalidSemVer = errors.New("Invalid Semantic Version") + ErrInvalidMetadata = errors.New("Invalid Metadata string") + ErrInvalidPrerelease = errors.New("Invalid Prerelease string") ) -// SemVerRegex id the regular expression used to parse a semantic version. +// SemVerRegex is the regular expression used to parse a semantic version. const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +// ValidPrerelease is the regular expression which validates +// both prerelease and metadata values. +const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)` + // Version represents a single semantic version. type Version struct { major, minor, patch int64 @@ -34,6 +41,7 @@ type Version struct { func init() { versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") + validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) } // NewVersion parses a given version and returns an instance of Version or @@ -129,6 +137,93 @@ func (v *Version) Metadata() string { return v.metadata } +// originalVPrefix returns the original 'v' prefix if any. +func (v *Version) originalVPrefix() string { + if v.original != "" && strings.ToLower(v.original[:1]) == "v" { + return v.original[:1] + } + return "" +} + +// IncPatch produces the next patch version. +// If the current version does not have prerelease/metadata information, +// it unsets metadata and prerelease values, increments patch number. +// If the current version has any of prerelease or metadata information, +// it unsets both values and keeps curent patch value +func (v Version) IncPatch() Version { + vNext := v + // according to http://semver.org/#spec-item-9 + // Pre-release versions have a lower precedence than the associated normal version. + // according to http://semver.org/#spec-item-10 + // Build metadata SHOULD be ignored when determining version precedence. + if v.pre != "" { + vNext.metadata = "" + vNext.pre = "" + } else { + vNext.metadata = "" + vNext.pre = "" + vNext.patch = v.patch + 1 + } + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMinor produces the next minor version. +// Sets patch to 0. +// Increments minor number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMinor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = v.minor + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMajor produces the next major version. +// Sets patch to 0. +// Sets minor to 0. +// Increments major number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMajor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = 0 + vNext.major = v.major + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// SetPrelease defines the prerelease value. +// Value must not include the required 'hypen' prefix. +func (v Version) SetPrerelease(prerelease string) (Version, error) { + vNext := v + if len(prerelease) > 0 && validPrereleaseRegex.MatchString(prerelease) == false { + return vNext, ErrInvalidPrerelease + } + vNext.pre = prerelease + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// SetMetadata defines metadata value. +// Value must not include the required 'plus' prefix. +func (v Version) SetMetadata(metadata string) (Version, error) { + vNext := v + if len(metadata) > 0 && validPrereleaseRegex.MatchString(metadata) == false { + return vNext, ErrInvalidMetadata + } + vNext.metadata = metadata + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + // LessThan tests if one version is less than another one. func (v *Version) LessThan(o *Version) bool { return v.Compare(o) < 0 diff --git a/version_test.go b/version_test.go index e8ad413..0e02a6a 100644 --- a/version_test.go +++ b/version_test.go @@ -281,3 +281,170 @@ func TestEqual(t *testing.T) { } } } + +func TestInc(t *testing.T) { + tests := []struct { + v1 string + expected string + how string + expectedOriginal string + }{ + {"1.2.3", "1.2.4", "patch", "1.2.4"}, + {"v1.2.4", "1.2.5", "patch", "v1.2.5"}, + {"1.2.3", "1.3.0", "minor", "1.3.0"}, + {"v1.2.4", "1.3.0", "minor", "v1.3.0"}, + {"1.2.3", "2.0.0", "major", "2.0.0"}, + {"v1.2.4", "2.0.0", "major", "v2.0.0"}, + {"1.2.3+meta", "1.2.4", "patch", "1.2.4"}, + {"1.2.3-beta+meta", "1.2.3", "patch", "1.2.3"}, + {"v1.2.4-beta+meta", "1.2.4", "patch", "v1.2.4"}, + {"1.2.3-beta+meta", "1.3.0", "minor", "1.3.0"}, + {"v1.2.4-beta+meta", "1.3.0", "minor", "v1.3.0"}, + {"1.2.3-beta+meta", "2.0.0", "major", "2.0.0"}, + {"v1.2.4-beta+meta", "2.0.0", "major", "v2.0.0"}, + } + + for _, tc := range tests { + v1, err := NewVersion(tc.v1) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + var v2 Version + switch tc.how { + case "patch": + v2 = v1.IncPatch() + case "minor": + v2 = v1.IncMinor() + case "major": + v2 = v1.IncMajor() + } + + a := v2.String() + e := tc.expected + if a != e { + t.Errorf( + "Inc %q failed. Expected %q got %q", + tc.how, e, a, + ) + } + + a = v2.Original() + e = tc.expectedOriginal + if a != e { + t.Errorf( + "Inc %q failed. Expected original %q got %q", + tc.how, e, a, + ) + } + } +} + +func TestSetPrerelease(t *testing.T) { + tests := []struct { + v1 string + prerelease string + expectedVersion string + expectedPrerelease string + expectedOriginal string + expectedErr error + }{ + {"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidPrerelease}, + {"1.2.3", "beta", "1.2.3-beta", "beta", "1.2.3-beta", nil}, + {"v1.2.4", "beta", "1.2.4-beta", "beta", "v1.2.4-beta", nil}, + } + + for _, tc := range tests { + v1, err := NewVersion(tc.v1) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + v2, err := v1.SetPrerelease(tc.prerelease) + if err != tc.expectedErr { + t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err) + } + + a := v2.Prerelease() + e := tc.expectedPrerelease + if a != e { + t.Errorf("Expected prerelease value=%q, but got %q", e, a) + } + + a = v2.String() + e = tc.expectedVersion + if a != e { + t.Errorf("Expected version string=%q, but got %q", e, a) + } + + a = v2.Original() + e = tc.expectedOriginal + if a != e { + t.Errorf("Expected version original=%q, but got %q", e, a) + } + } +} + +func TestSetMetadata(t *testing.T) { + tests := []struct { + v1 string + metadata string + expectedVersion string + expectedMetadata string + expectedOriginal string + expectedErr error + }{ + {"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidMetadata}, + {"1.2.3", "meta", "1.2.3+meta", "meta", "1.2.3+meta", nil}, + {"v1.2.4", "meta", "1.2.4+meta", "meta", "v1.2.4+meta", nil}, + } + + for _, tc := range tests { + v1, err := NewVersion(tc.v1) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + v2, err := v1.SetMetadata(tc.metadata) + if err != tc.expectedErr { + t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err) + } + + a := v2.Metadata() + e := tc.expectedMetadata + if a != e { + t.Errorf("Expected metadata value=%q, but got %q", e, a) + } + + a = v2.String() + e = tc.expectedVersion + if e != a { + t.Errorf("Expected version string=%q, but got %q", e, a) + } + + a = v2.Original() + e = tc.expectedOriginal + if a != e { + t.Errorf("Expected version original=%q, but got %q", e, a) + } + } +} + +func TestOriginalVPrefix(t *testing.T) { + tests := []struct { + version string + vprefix string + }{ + {"1.2.3", ""}, + {"v1.2.4", "v"}, + } + + for _, tc := range tests { + v1, _ := NewVersion(tc.version) + a := v1.originalVPrefix() + e := tc.vprefix + if a != e { + t.Errorf("Expected vprefix=%q, but got %q", e, a) + } + } +}