Created
July 27, 2023 14:55
-
-
Save xnyo/a9bed05c0967cba53807ddfe274e5390 to your computer and use it in GitHub Desktop.
Simple Go semver package
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package semver | |
import ( | |
"errors" | |
"fmt" | |
"strconv" | |
"strings" | |
) | |
type SemVer string | |
func NewFromVersion(major, minor, patch int) SemVer { | |
return SemVer(fmt.Sprintf("%d.%d.%d", major, minor, patch)) | |
} | |
func (v SemVer) Split() (major int, minor int, patch int, err error) { | |
// Remove suffix (e.g.: "10.1.0-pre" -> 10, 1, 0) | |
parts := strings.SplitN(string(v), "-", 2) | |
if len(parts) == 0 { | |
err = errors.New("no parts") | |
return | |
} | |
if len(parts[0]) < 3 { | |
err = fmt.Errorf("expected at 3 least parts, got %d", len(parts)) | |
return | |
} | |
parts = strings.SplitN(parts[0], ".", 3) | |
// Convert parts to integers and set return values | |
for i, dst := range []*int{&major, &minor, &patch} { | |
*dst, err = strconv.Atoi(parts[i]) | |
if err != nil { | |
err = fmt.Errorf("%q atoi: %w", parts[i], err) | |
return | |
} | |
} | |
return | |
} | |
func (v SemVer) Number() (int, error) { | |
major, minor, patch, err := v.Split() | |
if err != nil { | |
return 0, nil | |
} | |
return patch | (minor << 10) | (major << 20), nil | |
} | |
func (v SemVer) Compare(cmp SemVer) (int, error) { | |
vn, err := v.Number() | |
if err != nil { | |
return 0, err | |
} | |
cmpN, err := cmp.Number() | |
if err != nil { | |
return 0, err | |
} | |
return vn - cmpN, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package semver | |
import ( | |
"fmt" | |
"testing" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
) | |
func TestSemVer(t *testing.T) { | |
t.Run("NewFromVersion", func(t *testing.T) { | |
require.Equal(t, SemVer("1.2.3"), NewFromVersion(1, 2, 3)) | |
}) | |
} | |
func TestSemVer_Split(t *testing.T) { | |
t.Run("simple", func(t *testing.T) { | |
major, minor, patch, err := SemVer("1.2.3").Split() | |
require.NoError(t, err) | |
assert.Equal(t, 1, major) | |
assert.Equal(t, 2, minor) | |
assert.Equal(t, 3, patch) | |
}) | |
t.Run("with suffix", func(t *testing.T) { | |
t.Run("single", func(t *testing.T) { | |
major, minor, patch, err := SemVer("1.2.3-pre").Split() | |
require.NoError(t, err) | |
assert.Equal(t, 1, major) | |
assert.Equal(t, 2, minor) | |
assert.Equal(t, 3, patch) | |
}) | |
t.Run("multiple", func(t *testing.T) { | |
major, minor, patch, err := SemVer("1.2.3-pre-pre-pre-pre").Split() | |
require.NoError(t, err) | |
assert.Equal(t, 1, major) | |
assert.Equal(t, 2, minor) | |
assert.Equal(t, 3, patch) | |
}) | |
}) | |
t.Run("invalid", func(t *testing.T) { | |
_, _, _, err := SemVer("1.2.a").Split() | |
require.Error(t, err) | |
}) | |
} | |
func TestSemVer_Number(t *testing.T) { | |
vn, err := SemVer("1.2.3").Number() | |
require.NoError(t, err) | |
assert.Equal(t, 3+(2<<10)+(1<<20), vn) | |
} | |
func TestSemVer_Compare(t *testing.T) { | |
t.Run("suffix is ignored", func(t *testing.T) { | |
cmp, err := SemVer("1.2.3-pre").Compare("1.2.3") | |
require.NoError(t, err) | |
assert.Zero(t, cmp) | |
}) | |
t.Run("compare", func(t *testing.T) { | |
type tc struct { | |
a string | |
b string | |
exp int | |
} | |
for _, cas := range []tc{ | |
{"1.2.3", "1.2.3", 0}, | |
{"1.0.0", "2.0.0", -1}, | |
{"1.0.0", "0.0.0", 1}, | |
{"1.2.3", "1.3.3", -1}, | |
{"1.2.3", "1.1.3", 1}, | |
{"1.2.3", "1.2.4", -1}, | |
{"1.2.3", "1.2.2", 1}, | |
} { | |
t.Run(fmt.Sprintf("%q vs %q is %d", cas.a, cas.b, cas.exp), func(t *testing.T) { | |
cmp, err := SemVer(cas.a).Compare(SemVer(cas.b)) | |
require.NoError(t, err) | |
switch cas.exp { | |
case 0: | |
assert.Zero(t, cmp) | |
case -1: | |
assert.Negative(t, cmp) | |
case 1: | |
assert.Positive(t, cmp) | |
} | |
}) | |
} | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment