Semantic Versioning (semver) library written in golang

semver for golang Build Status GoDoc Coverage Status Go Report Card

semver is a Semantic Versioning library written in golang. It fully covers spec version 2.0.0.

Versioning

Old v1-v3 versions exist in the root of the repository for compatiblity reasons and will only receive bug fixes.

The current stable version is v4 and is fully go-mod compatible.

Usage

$ go get github.com/blang/semver/v4
# Or use fixed versions
$ go get github.com/blang/semver/[email protected]

Note: Always vendor your dependencies or fix on a specific version tag.

import github.com/blang/semver/v4
v1, err := semver.Make("1.0.0-beta")
v2, err := semver.Make("2.0.0-beta")
v1.Compare(v2)

Also check the GoDocs.

Why should I use this lib?

  • Fully spec compatible
  • No reflection
  • No regex
  • Fully tested (Coverage >99%)
  • Readable parsing/validation errors
  • Fast (See Benchmarks)
  • Only Stdlib
  • Uses values instead of pointers
  • Many features, see below

Features

  • Parsing and validation at all levels
  • Comparator-like comparisons
  • Compare Helper Methods
  • InPlace manipulation
  • Ranges >=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1
  • Wildcards >=1.x, <=2.5.x
  • Sortable (implements sort.Interface)
  • database/sql compatible (sql.Scanner/Valuer)
  • encoding/json compatible (json.Marshaler/Unmarshaler)

Ranges

A Range is a set of conditions which specify which versions satisfy the range.

A condition is composed of an operator and a version. The supported operators are:

  • <1.0.0 Less than 1.0.0
  • <=1.0.0 Less than or equal to 1.0.0
  • >1.0.0 Greater than 1.0.0
  • >=1.0.0 Greater than or equal to 1.0.0
  • 1.0.0, =1.0.0, ==1.0.0 Equal to 1.0.0
  • !1.0.0, !=1.0.0 Not equal to 1.0.0. Excludes version 1.0.0.

Note that spaces between the operator and the version will be gracefully tolerated.

A Range can link multiple Ranges separated by space:

Ranges can be linked by logical AND:

  • >1.0.0 <2.0.0 would match between both ranges, so 1.1.1 and 1.8.7 but not 1.0.0 or 2.0.0
  • >1.0.0 <3.0.0 !2.0.3-beta.2 would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2

Ranges can also be linked by logical OR:

  • <2.0.0 || >=3.0.0 would match 1.x.x and 3.x.x but not 2.x.x

AND has a higher precedence than OR. It's not possible to use brackets.

Ranges can be combined by both AND and OR

  • >1.0.0 <2.0.0 || >3.0.0 !4.2.1 would match 1.2.3, 1.9.9, 3.1.1, but not 4.2.1, 2.1.1

Range usage:

v, err := semver.Parse("1.2.3")
expectedRange, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0")
if expectedRange(v) {
    //valid
}

Example

Have a look at full examples in v4/examples/main.go

import github.com/blang/semver/v4

v, err := semver.Make("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.Make("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-4           5000000    390    ns/op    48 B/op   1 allocs/op
BenchmarkParseComplex-4          1000000   1813    ns/op   256 B/op   7 allocs/op
BenchmarkParseAverage-4          1000000   1171    ns/op   163 B/op   4 allocs/op
BenchmarkStringSimple-4         20000000    119    ns/op    16 B/op   1 allocs/op
BenchmarkStringLarger-4         10000000    206    ns/op    32 B/op   2 allocs/op
BenchmarkStringComplex-4         5000000    324    ns/op    80 B/op   3 allocs/op
BenchmarkStringAverage-4         5000000    273    ns/op    53 B/op   2 allocs/op
BenchmarkValidateSimple-4      200000000      9.33 ns/op     0 B/op   0 allocs/op
BenchmarkValidateComplex-4       3000000    469    ns/op     0 B/op   0 allocs/op
BenchmarkValidateAverage-4       5000000    256    ns/op     0 B/op   0 allocs/op
BenchmarkCompareSimple-4       100000000     11.8  ns/op     0 B/op   0 allocs/op
BenchmarkCompareComplex-4       50000000     30.8  ns/op     0 B/op   0 allocs/op
BenchmarkCompareAverage-4       30000000     41.5  ns/op     0 B/op   0 allocs/op
BenchmarkSort-4                  3000000    419    ns/op   256 B/op   2 allocs/op
BenchmarkRangeParseSimple-4      2000000    850    ns/op   192 B/op   5 allocs/op
BenchmarkRangeParseAverage-4     1000000   1677    ns/op   400 B/op  10 allocs/op
BenchmarkRangeParseComplex-4      300000   5214    ns/op  1440 B/op  30 allocs/op
BenchmarkRangeMatchSimple-4     50000000     25.6  ns/op     0 B/op   0 allocs/op
BenchmarkRangeMatchAverage-4    30000000     56.4  ns/op     0 B/op   0 allocs/op
BenchmarkRangeMatchComplex-4    10000000    153    ns/op     0 B/op   0 allocs/op

See benchmark cases at 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 file.

Owner
Benedikt Lang
Go, AWS, Kubernetes
Benedikt Lang
Comments
  • Enable go modules

    Enable go modules

    https://github.com/golang/go/wiki/Modules#gomod

    This PR is just a result of the following commands (in clean Go 1.11.5 environment):

    go mod init
    go get ./...
    go mod tidy
    

    The aim is to prevent semver from being marked as "incompatible" in go-mod-aware modules which depend on semver. This happens just because semver is >1.0.0 (currently v3.5.1) but isn't go-mod-aware. See https://forum.golangbridge.org/t/go-get-marking-go-mod-incompatible/10686 for more.

  • Make splitAndTrim parse more inteligently...

    Make splitAndTrim parse more inteligently...

    ... by making it understand that a space after a range token ('>', '<', '=') can be threated like a typo instead of like an error. Example '<= '.

    Also, remove all whitespaces instead of just leading/ending spaces, as there might be spaces between a range token and a version number. Example: '>= 2.5.2'

  • added methods to implement sql related interfaces.

    added methods to implement sql related interfaces.

    Implemented methods:

    • Version.Scan -> database/sql.Scanner
    • Version.Value -> database/sql/driver.Valuer

    Current implementation stores Version as a single string Field, which is most convenient when it is used as a struct field.

    Thx for this awesome package. Keep up the good work!

  • remove pre-release increment constraint

    remove pre-release increment constraint

    I don't see a reason why pre-releases should not be incremented. It is very common to release early without a stable api.

    We should support incrementing 0.1.0 releases and the like. #59

  • Use of Pointers

    Use of Pointers

    I notice that lots of methods in your API (e.g., Validate, Compare) take pointer arguments. But I don't see why they should. None of these operations actually mutate the object. So it should be fine to simply pass a value.

    The reason I see this as an issue is twofold. First, it gives the impression (when looking at the API), that these things mutate the objects. But more important for me, it means I need to have pointers available in my code which keeps the door open for inadvertent mutation in my code as well.

    Is there a reason to use the pointers in the API like you have?

  • Cannot install using go modules

    Cannot install using go modules

    v3.5.1 is the last tagged version that doesn’t include a go.mod file The newer has a go.mod file but is invalid since the module needs to have a /v3 suffix then

    see https://github.com/golang/go/issues/31944#issuecomment-528272174 for more

  • proposal: remove pre-release versions >1.0.0 increment constraint

    proposal: remove pre-release versions >1.0.0 increment constraint

    What is the motivation behind stopping pre-releases from being incremented? I read the SemVer spec and couldn't find this rule, could you point out where you've seen this constraint?

    // IncrementMinor increments the minor version
    func (v *Version) IncrementMinor() error {
            //-----
    	if v.Major == 0 { // why?
    		return fmt.Errorf("Minor version can not be incremented for %q", v.String())
    	}
            //-----
    	v.Minor += 1
    	v.Patch = 0
    	return nil
    }
    

    Would you mind me submitting a PR to remove it?

  • Support wildcards in versions

    Support wildcards in versions

    This implements #24

    The code will expand wildcards inside versions following these rules:

    * when dealing with patch wildcards:
    >= 1.2.x    will become    >= 1.2.0
    <= 1.2.x    will become    <  1.3.0
    >  1.2.x    will become    >= 1.3.0
    <  1.2.x    will become    <  1.2.0
    != 1.2.x    will become    <  1.2.0 >= 1.3.0
    * when dealing with minor wildcards:
    >= 1.x      will become    >= 1.0.0
    <= 1.x      will become    <  2.0.0
    >  1.x      will become    >= 2.0.0
    <  1.0      will become    <  1.0.0
    != 1.x      will become    <  1.0.0 >= 2.0.0
    * when dealing with wildcards without version operator:
    1.2.x       will become    >= 1.2.0 < 1.3.0
    1.x         will become    >= 1.0.0 < 2.0.0
    
  • Feature/tolerant json parse

    Feature/tolerant json parse

    See https://github.com/blang/semver/issues/16 and https://github.com/blang/semver/pull/19 for context

    I've extended the UnmarshalJSON to fall back to ParseTolerant if there is an error on Parse. If that errors too then the error is returned.

    I added test cases for this that are all passing well.

    This allows me to build a struct like this and have it parse automatically:

    type Fields struct {
    	AppVersion semver.Version `json:"app_version"`
    	OSVersion  semver.Version `json:"os_version"`
    	Foobar     string         `json:"foobar"`
    }
    

    This now works with my mobile clients, which version like 4.5 rather than 4.5.0 which there's no way for me to fix historically.

  • Coverage is lower than what readme says

    Coverage is lower than what readme says

    Fully tested (Coverage >99%)

    But it seems coverage is around 87% according to batch in readme.

    Please, don't get me wrong, it's just to increase awareness and we can make it great again. If you wish, I can even give a hand.

  • Start with a slice large enough for the minimum version

    Start with a slice large enough for the minimum version

    Obviously, we should make room for (at least) three digits and two dots. I considered padding a little bit more (maybe six digits is common?) but decided against it after some snooping*.

    Before:

    BenchmarkStringSimple   10000000               195 ns/op              16 B/op          1 allocs/op
    BenchmarkStringLarger    5000000               326 ns/op              40 B/op          2 allocs/op
    BenchmarkStringComplex   5000000               506 ns/op              88 B/op          3 allocs/op
    BenchmarkStringAverage   5000000               411 ns/op              56 B/op          2 allocs/op
    

    After:

    BenchmarkStringSimple   20000000               117 ns/op               5 B/op          0 allocs/op
    BenchmarkStringLarger   10000000               243 ns/op              32 B/op          2 allocs/op
    BenchmarkStringComplex   5000000               446 ns/op              80 B/op          3 allocs/op
    BenchmarkStringAverage   5000000               336 ns/op              47 B/op          2 allocs/op
    

    * 81% of the released versions on RubyGems are single digits, and more than 99% fit after one grow.

    abort "Requires Ruby 2.x" unless RUBY_VERSION.start_with? '2.'
    
    require 'rubygems/name_tuple'
    require 'rubygems/remote_fetcher'
    
    Gem::Source.new('https://rubygems.org').load_specs(:released).each do |tuple|
      puts tuple.version.to_s
    end
    
    ruby rubygems.rb | awk -e '{ print length() }' | sort -n | uniq -c
    
  • Allow prereleases to contain x

    Allow prereleases to contain x

    Closes #75

    This pull request slightly alters expandWildcardVersions to use strings.Contains(.x) instead of strings.Contains(x) for trying to assess whether a range condition has a wildcard. This allows prereleases with x in the name as opposed to failing with the error documented in #75: Could not get version from string: "<".

  • Using x in prerelease causes error

    Using x in prerelease causes error

    Given the version 0.1.0-x, ParseRange returns the following error:

    Could not get version from string: "<"
    

    Replacing the x with another character fixes the problem (e.g. 0.1.0-y).

    This appears to be related to x being used to denote wildcards as implemented in #27: https://github.com/blang/semver/blob/4487282d78122a245e413d7515e7c516b70c33fd/v4/range.go#L330

    This can be reproduced with the following test:

    func TestExpandWildcardVersion(t *testing.T) {
    	tests := []struct {
    		i [][]string
    		o [][]string
    	}{
    		{[][]string{{"foox"}}, nil},
    
                    // FAILING TEST
    		{[][]string{{"0.1.0-x"}}, [][]string{{"0.1.0-x"}}},
    
    
    		{[][]string{{">=1.2.x"}}, [][]string{{">=1.2.0"}}},
    		{[][]string{{"<=1.2.x"}}, [][]string{{"<1.3.0"}}},
    		{[][]string{{">1.2.x"}}, [][]string{{">=1.3.0"}}},
    		{[][]string{{"<1.2.x"}}, [][]string{{"<1.2.0"}}},
    		{[][]string{{"!=1.2.x"}}, [][]string{{"<1.2.0", ">=1.3.0"}}},
    		{[][]string{{">=1.x"}}, [][]string{{">=1.0.0"}}},
    		{[][]string{{"<=1.x"}}, [][]string{{"<2.0.0"}}},
    		{[][]string{{">1.x"}}, [][]string{{">=2.0.0"}}},
    		{[][]string{{"<1.x"}}, [][]string{{"<1.0.0"}}},
    		{[][]string{{"!=1.x"}}, [][]string{{"<1.0.0", ">=2.0.0"}}},
    		{[][]string{{"1.2.x"}}, [][]string{{">=1.2.0", "<1.3.0"}}},
    		{[][]string{{"1.x"}}, [][]string{{">=1.0.0", "<2.0.0"}}},
    
    	}
    
    	for _, tc := range tests {
    		o, _ := expandWildcardVersion(tc.i)
    		if !reflect.DeepEqual(tc.o, o) {
    			t.Errorf("Invalid for case %q: Expected %q, got: %q", tc.i, tc.o, o)
    		}
    	}
    }
    

    Result of this test is: Invalid for case [["0.1.0-x"]]: Expected [["0.1.0-x"]], got: [[">=0.1.0-x" "<"]]

    At the very least, it would be helpful to call out this corner case in the docs if fixing this would be challenging due to breaking changes.

  • Major version constraint circumvented by prerelease versions

    Major version constraint circumvented by prerelease versions

    I'm trying to implement a range check that allows any major or minor version change, but never a major change. So that anything starting with 1 but never anything starting with 2 or above.

    I tried expressing that as >= 1.x, >= 1.x < 2.x, >= 1.x < 2.x.x, and >= 1.x < 2.0.0, but found that it will allow 2.x prereleases, e.g. 2.0.0-v2-5.

    package main
    
    import (
    	"fmt"
    	"github.com/blang/semver/v4"
    )
    
    func main() {
    	v, _ := semver.Parse("2.0.0-v2-5")
    	r, _ := semver.ParseRange(">= 1.x")
    	fmt.Println(r(v))
    	r, _ = semver.ParseRange(">= 1.x < 2.x")
    	fmt.Println(r(v))
    	r, _ = semver.ParseRange(">= 1.x < 2.x.x")
    	fmt.Println(r(v))
    	r, _ = semver.ParseRange(">= 1.x < 2.0.0")
    	fmt.Println(r(v))
    }
    

    This prints true four times. I know that 2.0.0-v2-5 is "less than" 2.0.0 because it is a prerelease, but this seems to be making it caught by 1.x as well.

  • ParseTolerant

    ParseTolerant "1.15-alpine3.14" returns error

    Using semver v4

    	e, err := semver.ParseTolerant("1.15-alpine3.14")
    	if err != nil {
    		fmt.Println(err)
    	}
    

    returns error Invalid character(s) found in minor number "15-alpine3"

  • [FEAT]: added Ranger as alternative to Range

    [FEAT]: added Ranger as alternative to Range

    Main differences between Range:

    • compiled version checker can be reverted back to string, if necessary
    • possibly, you can add support of inner comparison (in rounded brackets -> (), like in mathematical operations), as well as additional comparators, or range syntax (got a few ideas from this article)

    My main goal was to revert back parsed range, so possibly we can optimize it a bit without loosing backward compatibility.

A CLI which automates semver versioning.

Semverbot A CLI which automates semver versioning based on git information. Why Semverbot? There are several reasons why you should consider using sbo

Dec 12, 2022
Learning go with tests! Also setting up automated versioning via SemVer.

learn-go-with-tests The purpose of this repo is to complete the learn-go-with-test course located here. I am also interested in learning how to automa

Nov 23, 2021
modver - a Go package and command that helps you obey semantic versioning rules in your Go module.

Modver This is modver, a Go package and command that helps you obey semantic versioning rules in your Go module. It can read and compare two different

Dec 12, 2022
Interactive prompt to set and push a semver tag
Interactive prompt to set and push a semver tag

cutver For when you know what version to tag, and you want to cut a release in the annotated tag format. Installation go install github.com/roryq/cutv

Nov 15, 2022
🦔 semver and constraint parsing with a focus on performance

semver ?? semver and constraint parsing with a focus on performance semver provides semantic version and constraint parsing, comparison, and testing.

Dec 1, 2021
Automatically create global & local Rate Limit in Istio, support EnvoyFilter versioning!

istio-ratelimit-operator Istio ratelimit operator provide an easy way to configure Global or Local Ratelimit in Istio mesh. Istio ratelimit operator a

Oct 24, 2022
Dud is a lightweight tool for versioning data alongside source code and building data pipelines.

Dud Website | Install | Getting Started | Source Code Dud is a lightweight tool for versioning data alongside source code and building data pipelines.

Jan 1, 2023
A boilerplate for Go fiber versioning
A boilerplate for Go fiber versioning

Fiber Versioning Boilerplate Prerequisite Make sure you have the following installed outside the current project directory and available in your GOPAT

Nov 18, 2022
Git extension for versioning large files

Git Large File Storage Git LFS is a command line extension and specification for managing large files with Git. The client is written in Go, with pre-

Jan 5, 2023
Selected Machine Learning algorithms for natural language processing and semantic analysis in Golang

Natural Language Processing Implementations of selected machine learning algorithms for natural language processing in golang. The primary focus for t

Dec 25, 2022
Selected Machine Learning algorithms for natural language processing and semantic analysis in Golang

Natural Language Processing Implementations of selected machine learning algorithms for natural language processing in golang. The primary focus for t

Dec 25, 2022
A simple, semantic and developer-friendly golang package for datetime

Carbon 中文 | English carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,支持链式调用和 gorm、xorm、zorm 等主流 orm。 如果您觉得不错,请给个 star 吧 github:github.com/golang-module/carbon g

Jan 9, 2023
A simple, semantic and developer-friendly golang package for encoding&decoding and encryption&decryption

A simple, semantic and developer-friendly golang package for encoding&decoding and encryption&decryption

Jan 4, 2023
Semantic version generator using git commit keywords and overrides

Semantic version generator Project created overnight, to prove that management of semantic versioning is NOT painful and do not require arguments and

Jan 1, 2023
Everything a semantic desktop search engine combined with a single-user document management system

Everything will be a semantic desktop search engine combined with a single-user document management system. It will apply ideas of the semantic web and knowledge graphs to organize your data, allowing you to maintain private knowledge graphs as well as make use of public knowledge graphs, such as Wikidata.

May 21, 2022
Yet another semantic version incrementor and tagger for git

git-tag-inc Increments the version number and tags it. (You will need to push) Usage ./git-tag-inc [major] [minor] [release] [test] [uat] git-tag-in

Apr 30, 2022
A dead simple CLI tool that prints the next semantic version based on the last tag of your git repository

nextver A dead simple CLI tool that prints the next semantic version based on the last tag of your git repository. Install go install github.com/junk1

Sep 29, 2022
🥄A simple generator for semantic git messages.

?? Tablespoon EXPERIMENTAL PREVIEW A simple generator for semantic git messages. Installation | Contributing Tablespoon is a simple generator which ca

Jul 22, 2022
A command line interface for trying out Repustate's multilingual semantic search
A command line interface for trying out Repustate's multilingual semantic search

rcli A command line interface for trying out Repustate's multilingual semantic search. Install & Usage Download the binary for your OS. Make sure it's

Nov 26, 2020
Provides simple, semantic manipulation of the operating system's signal processing.
Provides simple, semantic manipulation of the operating system's signal processing.

Provides simple, semantic manipulation of the operating system's signal processing.

Dec 15, 2021