Skip to content

Commit

Permalink
Add methods to increment version number
Browse files Browse the repository at this point in the history
  • Loading branch information
mh-cbon committed Oct 29, 2016
1 parent ea12bf9 commit e0e1160
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 94 deletions.
136 changes: 82 additions & 54 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -129,73 +137,93 @@ func (v *Version) Metadata() string {
return v.metadata
}

// Increment version number,
// How can be one of: patch, minor, major, prerelease
func (v *Version) Inc(how string) bool {
if how == "patch" {
return v.IncPatch()
} else if how == "minor" {
return v.IncMinor()
} else if how == "major" {
return v.IncMajor()
// orignalVPrefix returns the original 'v' prefix if any.
func (v *Version) orignalVPrefix() string {
if v.original != "" && strings.ToLower(v.original[:1]) == "v" {
return v.original[:1]
}
return false
return ""
}

// Increment version number by the minor number.
// Unsets prerelease status.
// Add +1 to patch number.
func (v *Version) IncPatch() bool {
v.pre = ""
v.metadata = ""
v.patch += 1
return true
// IncPatch produces the next patch version.
// If current version is patch final,
// it unsets metadata and prerelease values
// Increments patch number.
// If current version has prerelease information
// it unsets both values,
// 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.orignalVPrefix() + "" + vNext.String()
return vNext
}

// Increment version number by the minor number.
// IncMinor produces the next minor version.
// Sets patch to 0.
// Increments minor number.
// Unsets metadata.
// Unsets prerelease status.
// Sets patch number to 0.
// Add +1 to minor number.
func (v *Version) IncMinor() bool {
v.pre = ""
v.metadata = ""
v.patch = 0
v.minor += 1
return true
func (v Version) IncMinor() Version {
vNext := v
vNext.metadata = ""
vNext.pre = ""
vNext.patch = 0
vNext.minor = v.minor + 1
vNext.original = v.orignalVPrefix() + "" + vNext.String()
return vNext
}

// Increment version number by the major number.
// IncMajor produces the next major version.
// Sets patch to 0.
// Sets minor to 0.
// Increments major number.
// Unsets metadata.
// Unsets prerelease status.
// Sets patch number to 0.
// Sets minor number to 0.
// Add +1 to major number.
func (v *Version) IncMajor() bool {
v.pre = ""
v.metadata = ""
v.patch = 0
v.minor = 0
v.major += 1
return true
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.orignalVPrefix() + "" + vNext.String()
return vNext
}

// SetPrelease sets pre-release value.
func (v *Version) SetPrerelease(prerelease string) bool {
r := regexp.MustCompile(`^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`)
if len(prerelease) > 0 && r.MatchString(prerelease) == false {
return false
// SetPrelease defines 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
}
v.pre = prerelease
return true
vNext.pre = prerelease
vNext.original = v.orignalVPrefix() + "" + vNext.String()
return vNext, nil
}

// SetMetadata sets metadata value.
func (v *Version) SetMetadata(metadata string) bool {
r := regexp.MustCompile(`^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)`)
if len(metadata) > 0 && r.MatchString(metadata) == false {
return false
// 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
}
v.metadata = metadata
return true
vNext.metadata = metadata
vNext.original = v.orignalVPrefix() + "" + vNext.String()
return vNext, nil
}

// LessThan tests if one version is less than another one.
Expand Down
136 changes: 96 additions & 40 deletions version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,17 +284,24 @@ func TestEqual(t *testing.T) {

func TestInc(t *testing.T) {
tests := []struct {
v1 string
expectedV string
how string
expected bool
v1 string
expected string
how string
expectedOriginal string
}{
{"1.2.3", "1.2.4", "patch", true},
{"1.2.3", "1.3.0", "minor", true},
{"1.2.3", "2.0.0", "major", true},
{"1.2.3-beta+meta", "1.2.4", "patch", true},
{"1.2.3-beta+meta", "1.3.0", "minor", true},
{"1.2.3-beta+meta", "2.0.0", "major", true},
{"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 {
Expand All @@ -303,17 +310,31 @@ func TestInc(t *testing.T) {
t.Errorf("Error parsing version: %s", err)
}

ok := v1.Inc(tc.how)
if ok != tc.expected {
t.Errorf("Expected to increment the version number but it failed")
var v2 Version
switch tc.how {
case "patch":
v2 = v1.IncPatch()
case "minor":
v2 = v1.IncMinor()
case "major":
v2 = v1.IncMajor()
}

a := v1.String()
e := tc.expectedV
a := v2.String()
e := tc.expected
if a != e {
t.Errorf(
"Inc %q failed. Expected %q got %q",
tc.how, tc.expectedV, a,
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,
)
}
}
Expand All @@ -325,10 +346,12 @@ func TestSetPrerelease(t *testing.T) {
prerelease string
expectedVersion string
expectedPrerelease string
expectedResult bool
expectedOriginal string
expectedErr error
}{
{"1.2.3", "**", "1.2.3", "", false},
{"1.2.3", "beta", "1.2.3-beta", "beta", true},
{"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 {
Expand All @@ -337,21 +360,27 @@ func TestSetPrerelease(t *testing.T) {
t.Errorf("Error parsing version: %s", err)
}

ok := v1.SetPrerelease(tc.prerelease)
if ok != tc.expectedResult {
t.Errorf("Expected to receive %s got %s", tc.expectedResult, ok)
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 := v1.Prerelease()
a := v2.Prerelease()
e := tc.expectedPrerelease
if a != e {
t.Errorf("Expected %q got %q", e, a)
t.Errorf("Expected prerelease value=%q, but got %q", e, a)
}

newV := v1.String()
eV := tc.expectedVersion
if newV != eV {
t.Errorf("Expected %q got %q", newV, eV)
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)
}
}
}
Expand All @@ -362,10 +391,12 @@ func TestSetMetadata(t *testing.T) {
metadata string
expectedVersion string
expectedMetadata string
expectedResult bool
expectedOriginal string
expectedErr error
}{
{"1.2.3", "**", "1.2.3", "", false},
{"1.2.3", "meta", "1.2.3+meta", "meta", true},
{"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 {
Expand All @@ -374,21 +405,46 @@ func TestSetMetadata(t *testing.T) {
t.Errorf("Error parsing version: %s", err)
}

ok := v1.SetMetadata(tc.metadata)
if ok != tc.expectedResult {
t.Errorf("Expected to receive %s got %s", tc.expectedResult, ok)
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 := v1.Metadata()
a := v2.Metadata()
e := tc.expectedMetadata
if a != e {
t.Errorf("Expected %q got %q", e, a)
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)
}

newV := v1.String()
eV := tc.expectedVersion
if newV != eV {
t.Errorf("Expected %q got %q", newV, eV)
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.orignalVPrefix()
e := tc.vprefix
if a != e {
t.Errorf("Expected vprefix=%q, but got %q", e, a)
}
}
}

0 comments on commit e0e1160

Please sign in to comment.