23
pkg/semver/metadata.go
Normal file
23
pkg/semver/metadata.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
validMetadata = `^(?P<commit>[0-9a-zA-Z]{8}).(?P<date>[0-9]{8})$`
|
||||||
|
)
|
||||||
|
|
||||||
|
var regexMetadata = regexp.MustCompile(validMetadata)
|
||||||
|
|
||||||
|
type Metadata string
|
||||||
|
|
||||||
|
func SplitMetadata(m Metadata) (string, string, error) {
|
||||||
|
if !regexMetadata.MatchString(string(m)) {
|
||||||
|
return "", "", fmt.Errorf("invalid metadata: %s", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
match := regexMetadata.FindStringSubmatch(string(m))
|
||||||
|
return match[1], match[2], nil
|
||||||
|
}
|
37
pkg/semver/metadata_test.go
Normal file
37
pkg/semver/metadata_test.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var validMetadataTests = []struct {
|
||||||
|
name string
|
||||||
|
metadata Metadata
|
||||||
|
commit string
|
||||||
|
date string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple",
|
||||||
|
metadata: "metadata.20101112",
|
||||||
|
commit: "metadata",
|
||||||
|
date: "20101112",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitMetadata(t *testing.T) {
|
||||||
|
for _, tt := range validMetadataTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1, err := SplitMetadata(tt.metadata)
|
||||||
|
if (err != nil) != tt.err {
|
||||||
|
t.Errorf("SplitMetadata() error = %v, wantErr %v", err, tt.err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.commit {
|
||||||
|
t.Errorf("SplitMetadata() got = %v, want %v", got, tt.commit)
|
||||||
|
}
|
||||||
|
if got1 != tt.date {
|
||||||
|
t.Errorf("SplitMetadata() got1 = %v, want %v", got1, tt.date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
3
pkg/semver/prerelease.go
Normal file
3
pkg/semver/prerelease.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
type PreRelease string
|
117
pkg/semver/version.go
Normal file
117
pkg/semver/version.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://semver.org/ && https://regex101.com/r/Ly7O1x/3/
|
||||||
|
validSemVer = `^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
|
||||||
|
)
|
||||||
|
|
||||||
|
var regexSemver = regexp.MustCompile(validSemVer)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Major int64
|
||||||
|
Minor int64
|
||||||
|
Patch int64
|
||||||
|
PreRelease PreRelease
|
||||||
|
Metadata Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Commit() string {
|
||||||
|
commit, _, err := SplitMetadata(v.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return string(v.Metadata)
|
||||||
|
}
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Date() string {
|
||||||
|
_, date, err := SplitMetadata(v.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return string(v.Metadata)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Release() string {
|
||||||
|
switch v.PreRelease {
|
||||||
|
case "":
|
||||||
|
return fmt.Sprint("stable")
|
||||||
|
default:
|
||||||
|
return string(v.PreRelease)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
fmt.Fprintf(&buf, "%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
|
||||||
|
if v.PreRelease != "" {
|
||||||
|
fmt.Fprintf(&buf, "-%s", v.PreRelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Metadata != "" {
|
||||||
|
fmt.Fprintf(&buf, "+%s", v.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) Number() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(v string) (Version, error) {
|
||||||
|
if !regexSemver.MatchString(v) {
|
||||||
|
return Version{}, fmt.Errorf("invalid version: %s", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
match := regexSemver.FindStringSubmatch(v)
|
||||||
|
matchMap := make(map[string]string)
|
||||||
|
for i, name := range regexSemver.SubexpNames() {
|
||||||
|
if i != 0 && name != "" {
|
||||||
|
matchMap[name] = match[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var major int64
|
||||||
|
var minor int64
|
||||||
|
var patch int64
|
||||||
|
var preRelease PreRelease
|
||||||
|
var metadata Metadata
|
||||||
|
|
||||||
|
if major, err = strconv.ParseInt(matchMap["major"], 10, 64); err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if minor, err = strconv.ParseInt(matchMap["minor"], 10, 64); err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if patch, err = strconv.ParseInt(matchMap["patch"], 10, 64); err != nil {
|
||||||
|
return Version{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchMap["prerelease"] != "" {
|
||||||
|
preRelease = PreRelease(matchMap["prerelease"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchMap["buildmetadata"] != "" {
|
||||||
|
metadata = Metadata(matchMap["buildmetadata"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version{
|
||||||
|
Major: major,
|
||||||
|
Minor: minor,
|
||||||
|
Patch: patch,
|
||||||
|
PreRelease: preRelease,
|
||||||
|
Metadata: metadata,
|
||||||
|
}, nil
|
||||||
|
}
|
210
pkg/semver/version_test.go
Normal file
210
pkg/semver/version_test.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validVersionTests = []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
version Version
|
||||||
|
inputErr bool
|
||||||
|
commit string
|
||||||
|
date string
|
||||||
|
release string
|
||||||
|
full string
|
||||||
|
number string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "stable",
|
||||||
|
input: "0.1.0",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
date: "",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.0",
|
||||||
|
number: "0.1.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable.1",
|
||||||
|
input: "0.1.1",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
date: "",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.1",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable.1+metadata",
|
||||||
|
input: "0.1.1+metadata",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
Metadata: "metadata",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "metadata",
|
||||||
|
date: "metadata",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.1+metadata",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stable.1+metadata.date",
|
||||||
|
input: "0.1.1+metadata.20101112",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
Metadata: "metadata.20101112",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "metadata",
|
||||||
|
date: "20101112",
|
||||||
|
release: "stable",
|
||||||
|
full: "0.1.1+metadata.20101112",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alpha",
|
||||||
|
input: "0.1.1-alpha",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
PreRelease: "alpha",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
release: "alpha",
|
||||||
|
full: "0.1.1-alpha",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alpha.1",
|
||||||
|
input: "0.1.1-alpha.1",
|
||||||
|
version: Version{
|
||||||
|
Major: 0,
|
||||||
|
Minor: 1,
|
||||||
|
Patch: 1,
|
||||||
|
PreRelease: "alpha.1",
|
||||||
|
},
|
||||||
|
inputErr: false,
|
||||||
|
commit: "",
|
||||||
|
release: "alpha.1",
|
||||||
|
full: "0.1.1-alpha.1",
|
||||||
|
number: "0.1.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Parse(tt.input)
|
||||||
|
if (err != nil) != tt.inputErr {
|
||||||
|
t.Errorf("Parse() error = %v, wantErr %v", err, tt.inputErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.version) {
|
||||||
|
t.Errorf("Parse() got = %v, want %v", got, tt.version)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_Commit(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Commit(); got != tt.commit {
|
||||||
|
t.Errorf("Commit() = %v, want %v", got, tt.commit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_Date(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Date(); got != tt.date {
|
||||||
|
t.Errorf("Date() = %v, want %v", got, tt.date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_Release(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Release(); got != tt.release {
|
||||||
|
t.Errorf("Release() = %v, want %v", got, tt.release)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_String(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.String(); got != tt.full {
|
||||||
|
t.Errorf("String() = %v, want %v", got, tt.full)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion_VersionNumber(t *testing.T) {
|
||||||
|
for _, tt := range validVersionTests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := Version{
|
||||||
|
Major: tt.version.Major,
|
||||||
|
Minor: tt.version.Minor,
|
||||||
|
Patch: tt.version.Patch,
|
||||||
|
PreRelease: tt.version.PreRelease,
|
||||||
|
Metadata: tt.version.Metadata,
|
||||||
|
}
|
||||||
|
if got := v.Number(); got != tt.number {
|
||||||
|
t.Errorf("Number() = %v, want %v", got, tt.number)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user