From a76e16a4c7c36cbe1ffb0da37f8583e28aa0934a Mon Sep 17 00:00:00 2001 From: Benedikt Lang Date: Wed, 2 Jul 2014 02:15:48 +0200 Subject: [PATCH] Full working set, missing some tests --- semver.go | 222 ++++++++++++++++++++++++++++++++++++++----------- semver_test.go | 58 +++++++++++-- 2 files changed, 221 insertions(+), 59 deletions(-) diff --git a/semver.go b/semver.go index 69c9810..fc392e9 100644 --- a/semver.go +++ b/semver.go @@ -3,7 +3,9 @@ package semver import ( "bytes" "errors" + "fmt" "strconv" + "strings" ) var SEMVER_SPEC_VERSION = Version{ @@ -13,23 +15,23 @@ var SEMVER_SPEC_VERSION = Version{ } type Version struct { - Major int - Minor int - Patch int - Pre []PRVersion + Major uint64 + Minor uint64 + Patch uint64 + Pre []*PRVersion Build []string //No Precendence } -func (v Version) String() string { +func (v *Version) String() string { var buf bytes.Buffer var DOT = []byte(".") var HYPHEN = []byte("-") var PLUS = []byte("+") - buf.WriteString(strconv.Itoa(v.Major)) + buf.WriteString(strconv.FormatUint(v.Major, 10)) buf.Write(DOT) - buf.WriteString(strconv.Itoa(v.Minor)) + buf.WriteString(strconv.FormatUint(v.Minor, 10)) buf.Write(DOT) - buf.WriteString(strconv.Itoa(v.Patch)) + buf.WriteString(strconv.FormatUint(v.Patch, 10)) if len(v.Pre) > 0 { buf.Write(HYPHEN) for i, pre := range v.Pre { @@ -51,7 +53,7 @@ func (v Version) String() string { return buf.String() } -func (v *Version) compare(o *Version) int { +func (v *Version) Compare(o *Version) int { if v.Major != o.Major { if v.Major > o.Major { return 1 @@ -74,74 +76,194 @@ func (v *Version) compare(o *Version) int { } } + // Quick comparison if a version has no prerelease versions if len(v.Pre) == 0 && len(o.Pre) == 0 { return 0 } else if len(v.Pre) == 0 && len(o.Pre) > 0 { - return -1 - } else if len(v.Pre) > 0 && len(o.Pre) == 0 { return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 } else { - //Deep PreRelease Version comparison - return -2 //TODO: Not yet implemented + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } - } + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + } } func Parse(s string) (*Version, error) { - return nil, errors.New("Not implemented yet") -} + if len(s) == 0 { + return nil, errors.New("Version string empty") + } -// PreRelease Version -type PRVersion interface { - compare(*PRVersion) int - String() string //fmt.Stringer - IsNumeric() bool -} + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return nil, errors.New("No Major.Minor.Patch elements found") + } -// Alphabetical PreRelease Version -type AlphaPRVersion struct { - Version string -} + if !containsOnly(parts[0], NUMBERS) { + return nil, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return nil, err + } -func (v *AlphaPRVersion) IsNumeric() bool { - return false -} + if !containsOnly(parts[1], NUMBERS) { + return nil, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return nil, err + } -func (v *AlphaPRVersion) compare(o *AlphaPRVersion) int { - if v.Version == o.Version { - return 0 - } else if v.Version > o.Version { - return 1 + preIndex := strings.Index(parts[2], "-") + buildIndex := strings.Index(parts[2], "+") + + var subVersionIndex int + if preIndex != -1 && buildIndex == -1 { + subVersionIndex = preIndex + } else if preIndex == -1 && buildIndex != -1 { + subVersionIndex = buildIndex + } else if preIndex == -1 && buildIndex == -1 { + subVersionIndex = len(parts[2]) } else { - return -1 + // if there is no actual preIndex but a hyphen inside the build meta data + if buildIndex < preIndex { + subVersionIndex = buildIndex + preIndex = -1 // Build meta data before preIndex found implicates there are no prerelease versions + } else { + subVersionIndex = preIndex + } } -} -func (v AlphaPRVersion) String() string { - return v.Version + if !containsOnly(parts[2][:subVersionIndex], NUMBERS) { + return nil, fmt.Errorf("Invalid character(s) found in patch number %q", parts[2][:subVersionIndex]) + } + patch, err := strconv.ParseUint(parts[2][:subVersionIndex], 10, 64) + if err != nil { + return nil, err + } + v := &Version{} + v.Major = major + v.Minor = minor + v.Patch = patch + + // There are PreRelease versions + if preIndex != -1 { + var preRels string + if buildIndex != -1 { + preRels = parts[2][subVersionIndex+1 : buildIndex] + } else { + preRels = parts[2][subVersionIndex+1:] + } + prparts := strings.Split(preRels, ".") + for _, prstr := range prparts { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return nil, err + } + v.Pre = append(v.Pre, parsedPR) + } + } + + // There is build meta data + if buildIndex != -1 { + buildStr := parts[2][buildIndex+1:] + buildParts := strings.Split(buildStr, ".") + for _, str := range buildParts { + if !containsOnly(str, ALPHAS+NUMBERS) { + return nil, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + } + + return v, nil } -// Numeric PreRelease Version -type NumPRVersion struct { - Version int +// PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool } -func (v *NumPRVersion) compare(o *NumPRVersion) int { - if v.Version == o.Version { - return 0 - } else if v.Version > o.Version { - return 1 +func NewPRVersion(s string) (*PRVersion, error) { + v := &PRVersion{} + if containsOnly(s, NUMBERS) { + num, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, ALPHAS+NUMBERS) { + v.VersionStr = s + v.IsNum = false } else { + return nil, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +func (v *PRVersion) IsNumeric() bool { + return v.IsNum +} + +func (v *PRVersion) Compare(o *PRVersion) int { + if v.IsNum && !o.IsNum { return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } } } -func (v NumPRVersion) String() string { - return strconv.Itoa(v.Version) +func (v *PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr } -func (v *NumPRVersion) IsNumeric() bool { - return true +const NUMBERS = "0123456789" +const ALPHAS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 } diff --git a/semver_test.go b/semver_test.go index 37868c1..897d976 100644 --- a/semver_test.go +++ b/semver_test.go @@ -1,31 +1,55 @@ package semver +//TODO: Test incorrect version formats + import ( "testing" ) -func TestParse(t *testing.T) { - -} - -type stringerTest struct { +type formatTest struct { v Version result string } -var stringerTests = []stringerTest{ +var formatTests = []formatTest{ {Version{1, 2, 3, nil, nil}, "1.2.3"}, {Version{0, 0, 1, nil, nil}, "0.0.1"}, + {Version{0, 0, 1, []*PRVersion{prstr("alpha"), prstr("preview")}, []string{"123", "456"}}, "0.0.1-alpha.preview+123.456"}, + {Version{1, 2, 3, []*PRVersion{prstr("alpha"), prnum(1)}, []string{"123", "456"}}, "1.2.3-alpha.1+123.456"}, + {Version{1, 2, 3, []*PRVersion{prstr("alpha"), prnum(1)}, nil}, "1.2.3-alpha.1"}, + {Version{1, 2, 3, nil, []string{"123", "456"}}, "1.2.3+123.456"}, + // Prereleases and build metadata hyphens + {Version{1, 2, 3, []*PRVersion{prstr("alpha"), prstr("b-eta")}, []string{"123", "b-uild"}}, "1.2.3-alpha.b-eta+123.b-uild"}, + {Version{1, 2, 3, nil, []string{"123", "b-uild"}}, "1.2.3+123.b-uild"}, + {Version{1, 2, 3, []*PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"}, +} + +func prstr(s string) *PRVersion { + return &PRVersion{s, 0, false} +} + +func prnum(i uint64) *PRVersion { + return &PRVersion{"", i, true} } func TestStringer(t *testing.T) { - for _, test := range stringerTests { + for _, test := range formatTests { if res := test.v.String(); res != test.result { t.Errorf("Stringer, expected %q but got %q", test.result, res) } } } +func TestParse(t *testing.T) { + for _, test := range formatTests { + if v, err := Parse(test.result); err != nil { + t.Errorf("Error parsing %q: %q", test.result, err) + } else if comp := v.Compare(&test.v); comp != 0 { + t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp) + } + } +} + type compareTest struct { v1 Version v2 Version @@ -43,15 +67,31 @@ var compareTests = []compareTest{ {Version{2, 2, 4, nil, nil}, Version{1, 2, 4, nil, nil}, 1}, {Version{1, 3, 3, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, {Version{1, 2, 4, nil, nil}, Version{1, 2, 3, nil, nil}, 1}, + + // Spec Examples #11 + {Version{1, 0, 0, nil, nil}, Version{2, 0, 0, nil, nil}, -1}, + {Version{2, 0, 0, nil, nil}, Version{2, 1, 0, nil, nil}, -1}, + {Version{2, 1, 0, nil, nil}, Version{2, 1, 1, nil, nil}, -1}, + + {Version{1, 0, 0, []*PRVersion{prstr("alpha")}, nil}, Version{1, 0, 0, []*PRVersion{prstr("alpha"), prnum(1)}, nil}, -1}, + {Version{1, 0, 0, []*PRVersion{prstr("alpha"), prnum(1)}, nil}, Version{1, 0, 0, []*PRVersion{prstr("alpha"), prstr("beta")}, nil}, -1}, + {Version{1, 0, 0, []*PRVersion{prstr("alpha"), prstr("beta")}, nil}, Version{1, 0, 0, []*PRVersion{prstr("beta")}, nil}, -1}, + {Version{1, 0, 0, []*PRVersion{prstr("beta")}, nil}, Version{1, 0, 0, []*PRVersion{prstr("beta"), prnum(2)}, nil}, -1}, + {Version{1, 0, 0, []*PRVersion{prstr("beta"), prnum(2)}, nil}, Version{1, 0, 0, []*PRVersion{prstr("beta"), prnum(11)}, nil}, -1}, + {Version{1, 0, 0, []*PRVersion{prstr("beta"), prnum(11)}, nil}, Version{1, 0, 0, []*PRVersion{prstr("rc"), prnum(1)}, nil}, -1}, + {Version{1, 0, 0, []*PRVersion{prstr("rc"), prnum(1)}, nil}, Version{1, 0, 0, nil, nil}, -1}, + + // Ignore Build metadata + {Version{1, 0, 0, nil, []string{"1", "2", "3"}}, Version{1, 0, 0, nil, nil}, 0}, } func TestCompare(t *testing.T) { for _, test := range compareTests { - if res := test.v1.compare(&test.v2); res != test.result { + if res := test.v1.Compare(&test.v2); res != test.result { t.Errorf("Comparing %q : %q, expected %d but got %d", test.v1, test.v2, test.result, res) } //Test counterpart - if res := test.v2.compare(&test.v1); res != -test.result { + if res := test.v2.Compare(&test.v1); res != -test.result { t.Errorf("Comparing %q : %q, expected %d but got %d", test.v2, test.v1, -test.result, res) } }