From 58fd7ff9c375ca9b634652d5b9a9b77ab2991dc6 Mon Sep 17 00:00:00 2001 From: Benedikt Lang Date: Wed, 2 Jul 2014 12:29:34 +0200 Subject: [PATCH] README, examples, benchmarks --- README.md | 126 +++++++++++++++++++++++++++++++++++++++++++++++ examples/main.go | 83 +++++++++++++++++++++++++++++++ semver.go | 16 ++++++ semver_test.go | 110 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 examples/main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..710e362 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +semver for golang +====== + +semver is a Semantic Versioning library written in golang. It fully covers spec version `2.0.0`. + +Usage +----- + + $ go get github.com/blang/semver + + import github.com/blang/semver + v1, err := semver.New("1.0.0-beta") + v2, err := semver.New("2.0.0-beta") + v1.Compare(v2) + + +Why should I use this lib? +----- + +- Fully spec compatible +- No reflection +- No regex +- Fully tested (Coverage >99%) +- Readable parsing/validation errors +- Fast (See [Benchmarks](#benchmarks)) +- Only Stdlib + + +Features +----- + +- Parsing and validation at all levels +- Comparator-like comparisons +- Compare Helper Methods +- InPlace manipulation + + +Example +----- + +Have a look at full examples in [examples/main.go](examples/main.go) + + import github.com/blang/semver + + v, err := semver.New("0.0.1-alpha.preview+123.github") + fmt.Printf("Major: %d\n", v.Major) + fmt.Printf("Minor: %d\n", v.Minor) + fmt.Printf("Patch: %d\n", v.Patch) + fmt.Printf("Pre: %s\n", v.Pre) + fmt.Printf("Build: %s\n", v.Build) + + // Prerelease versions array + if len(v.Pre) > 0 { + fmt.Println("Prerelease versions:") + for i, pre := range v.Pre { + fmt.Printf("%d: %q\n", i, pre) + } + } + + // Build meta data array + if len(v.Build) > 0 { + fmt.Println("Build meta data:") + for i, build := range v.Build { + fmt.Printf("%d: %q\n", i, build) + } + } + + v001, err := semver.New("0.0.1") + // Compare using helpers: v.GT(v2), v.LT, v.GTE, v.LTE + v001.GT(v) == true + v.LT(v001) == true + v.GTE(v) == true + v.LTE(v) == true + + // Or use v.Compare(v2) for comparisons (-1, 0, 1): + v001.Compare(v) == 1 + v.Compare(v001) == -1 + v.Compare(v) == 0 + + // Manipulate Version in place: + v.Pre[0], err = semver.NewPRVersion("beta") + if err != nil { + fmt.Printf("Error parsing pre release version: %q", err) + } + + fmt.Println("\nValidate versions:") + v.Build[0] = "?" + + err = v.Validate() + if err != nil { + fmt.Printf("Validation failed: %s\n", err) + } + + +Benchmarks +----- + + BenchmarkParseSimple 5000000 442 ns/op + BenchmarkParseComplex 1000000 2441 ns/op + BenchmarkParseAverage 1000000 1497 ns/op + BenchmarkValidateSimple 500000000 4.83 ns/op + BenchmarkValidateComplex 1000000 1236 ns/op + BenchmarkValidateAverage 5000000 580 ns/op + BenchmarkCompareSimple 500000000 5.43 ns/op + BenchmarkCompareComplex 100000000 26.3 ns/op + BenchmarkCompareAverage 100000000 29.6 ns/op + +See benchmark cases at [semver_test.go](semver_test.go) + + +Motivation +----- + +I simply couldn't find any lib supporting the full spec. Others were just wrong or used reflection and regex which i don't like. + + +Contribution +----- + +Feel free to make a pull request. For bigger changes create a issue first to discuss about it. + + +License +----- + +See [LICENSE](LICENSE) file. diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 0000000..617d72a --- /dev/null +++ b/examples/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + "github.com/blang/semver" +) + +func main() { + v, err := semver.Parse("0.0.1-alpha.preview.222+123.github") + if err != nil { + fmt.Printf("Error while parsing (not valid): %q", err) + } + fmt.Printf("Version to string: %q\n", v) + + fmt.Printf("Major: %d\n", v.Major) + fmt.Printf("Minor: %d\n", v.Minor) + fmt.Printf("Patch: %d\n", v.Patch) + + // Prerelease versions + if len(v.Pre) > 0 { + fmt.Println("Prerelease versions:") + for i, pre := range v.Pre { + fmt.Printf("%d: %q\n", i, pre) + } + } + + // Build meta data + if len(v.Build) > 0 { + fmt.Println("Build meta data:") + for i, build := range v.Build { + fmt.Printf("%d: %q\n", i, build) + } + } + + // New == Parse + v001, err := semver.New("0.0.1") + + fmt.Println("\nUse Version.Compare for comparisons (-1, 0, 1):") + fmt.Printf("%q is greater than %q: Compare == %d\n", v001, v, v001.Compare(v)) + fmt.Printf("%q is less than %q: Compare == %d\n", v, v001, v.Compare(v001)) + fmt.Printf("%q is equal to %q: Compare == %d\n", v, v, v.Compare(v)) + + fmt.Println("\nUse comparison helpers returning booleans:") + fmt.Printf("%q is greater than %q: %t\n", v001, v, v001.GT(v)) + fmt.Printf("%q is greater than equal %q: %t\n", v001, v, v001.GTE(v)) + fmt.Printf("%q is greater than equal %q: %t\n", v, v, v.GTE(v)) + fmt.Printf("%q is less than %q: %t\n", v, v001, v.LT(v001)) + fmt.Printf("%q is less than equal %q: %t\n", v, v001, v.LTE(v001)) + fmt.Printf("%q is less than equal %q: %t\n", v, v, v.LTE(v)) + + fmt.Println("\nManipulate Version in place:") + v.Pre[0], err = semver.NewPRVersion("beta") + if err != nil { + fmt.Printf("Error parsing pre release version: %q", err) + } + fmt.Printf("Version to string: %q\n", v) + + fmt.Println("\nCompare Prerelease versions:") + pre1, _ := semver.NewPRVersion("123") + pre2, _ := semver.NewPRVersion("alpha") + pre3, _ := semver.NewPRVersion("124") + fmt.Printf("%q is less than %q: Compare == %d\n", pre1, pre2, pre1.Compare(pre2)) + fmt.Printf("%q is greater than %q: Compare == %d\n", pre3, pre1, pre3.Compare(pre1)) + fmt.Printf("%q is equal to %q: Compare == %d\n", pre1, pre1, pre1.Compare(pre1)) + + fmt.Println("\nValidate versions:") + v.Build[0] = "?" + + err = v.Validate() + if err != nil { + fmt.Printf("Validation failed: %s\n", err) + } + + fmt.Println("Create valid build meta data:") + b1, _ := semver.NewBuildVersion("build123") + v.Build[0] = b1 + fmt.Printf("Version with new build version %q\n", v) + + _, err = semver.NewBuildVersion("build?123") + if err != nil { + fmt.Printf("Create build version failed: %s\n", err) + } +} diff --git a/semver.go b/semver.go index ec2932d..241890d 100644 --- a/semver.go +++ b/semver.go @@ -168,6 +168,11 @@ func (v *Version) Validate() error { return nil } +// Alias for Parse +func New(s string) (*Version, error) { + return Parse(s) +} + // Parses a string to version func Parse(s string) (*Version, error) { if len(s) == 0 { @@ -344,3 +349,14 @@ func containsOnly(s string, set string) bool { return !strings.ContainsRune(set, r) }) == -1 } + +// Creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, ALPHAS+NUMBERS) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} diff --git a/semver_test.go b/semver_test.go index 9707f51..e7867f4 100644 --- a/semver_test.go +++ b/semver_test.go @@ -1,7 +1,5 @@ package semver -//TODO: Test incorrect version formats - import ( "testing" ) @@ -200,3 +198,111 @@ func TestPreReleaseVersions(t *testing.T) { t.Errorf("Not expected error %q", err) } } + +func TestBuildMetaDataVersions(t *testing.T) { + _, err := NewBuildVersion("123") + if err != nil { + t.Errorf("Unexpected error %q", err) + } + + _, err = NewBuildVersion("build") + if err != nil { + t.Errorf("Unexpected error %q", err) + } + + _, err = NewBuildVersion("test?") + if err == nil { + t.Error("Expected error, got none") + } + + _, err = NewBuildVersion("") + if err == nil { + t.Error("Expected error, got none") + } +} + +func TestNewHelper(t *testing.T) { + v, err := New("1.2.3") + if err != nil { + t.Fatalf("Unexpected error %q", err) + } + if v.Compare(&Version{1, 2, 3, nil, nil}) != 0 { + t.Fatal("Unexpected comparison problem") + } +} + +func BenchmarkParseSimple(b *testing.B) { + const VERSION = "0.0.1" + b.ResetTimer() + for n := 0; n < b.N; n++ { + New(VERSION) + } +} + +func BenchmarkParseComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + b.ResetTimer() + for n := 0; n < b.N; n++ { + New(VERSION) + } +} + +func BenchmarkParseAverage(b *testing.B) { + l := len(formatTests) + b.ResetTimer() + for n := 0; n < b.N; n++ { + New(formatTests[n%l].result) + } +} + +func BenchmarkValidateSimple(b *testing.B) { + const VERSION = "0.0.1" + v, _ := New(VERSION) + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Validate() + } +} + +func BenchmarkValidateComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + v, _ := New(VERSION) + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Validate() + } +} + +func BenchmarkValidateAverage(b *testing.B) { + l := len(formatTests) + b.ResetTimer() + for n := 0; n < b.N; n++ { + formatTests[n%l].v.Validate() + } +} + +func BenchmarkCompareSimple(b *testing.B) { + const VERSION = "0.0.1" + v, _ := New(VERSION) + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Compare(v) + } +} + +func BenchmarkCompareComplex(b *testing.B) { + const VERSION = "0.0.1-alpha.preview+123.456" + v, _ := New(VERSION) + b.ResetTimer() + for n := 0; n < b.N; n++ { + v.Compare(v) + } +} + +func BenchmarkCompareAverage(b *testing.B) { + l := len(compareTests) + b.ResetTimer() + for n := 0; n < b.N; n++ { + compareTests[n%l].v1.Compare(&(compareTests[n%l].v2)) + } +}