114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
package util
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// SemVer stores a numeric semantic version as major, minor, patch, and build
|
|
// components. Components that are not provided are represented as zero values.
|
|
type SemVer struct {
|
|
Major uint
|
|
Minor uint
|
|
Patch uint
|
|
Build uint
|
|
}
|
|
|
|
// NewSemver constructs a SemVer from v.
|
|
//
|
|
// NewSemver requires between one and four components ordered as major, minor,
|
|
// patch, and build. Components that are not provided are set to zero.
|
|
func NewSemver(v ...uint) (SemVer, error) {
|
|
s := &SemVer{}
|
|
switch len(v) {
|
|
case 4:
|
|
s.Build = v[3]
|
|
fallthrough
|
|
case 3:
|
|
s.Patch = v[2]
|
|
fallthrough
|
|
case 2:
|
|
s.Minor = v[1]
|
|
fallthrough
|
|
case 1:
|
|
s.Major = v[0]
|
|
default:
|
|
return *s, fmt.Errorf("new semver: incorrect args count: %d", len(v))
|
|
}
|
|
return *s, nil
|
|
}
|
|
|
|
// MustSemver returns the SemVer produced by NewSemver(v...).
|
|
//
|
|
// MustSemver panics if NewSemver returns an error.
|
|
func MustSemver(v ...uint) SemVer {
|
|
if v, err := NewSemver(v...); err != nil {
|
|
panic(err)
|
|
} else {
|
|
return v
|
|
}
|
|
}
|
|
|
|
// ParseSemver parses input into a SemVer.
|
|
//
|
|
// ParseSemver accepts versions with one to four numeric components separated by
|
|
// dots, for example "1", "1.2", "1.2.3", or "1.2.3.4". The input may also use
|
|
// the optional "v" or "v." prefix. Missing minor, patch, and build components
|
|
// are set to zero.
|
|
func ParseSemver(input string) (SemVer, error) {
|
|
source := input
|
|
|
|
switch {
|
|
case strings.HasPrefix(input, "v."):
|
|
input = strings.TrimPrefix(input, "v.")
|
|
case strings.HasPrefix(input, "v"):
|
|
input = strings.TrimPrefix(input, "v")
|
|
}
|
|
|
|
if input == "" {
|
|
return SemVer{}, fmt.Errorf("parse semver %q: missing major version", source)
|
|
}
|
|
|
|
parts := strings.Split(input, ".")
|
|
if len(parts) > 4 {
|
|
return SemVer{}, fmt.Errorf("parse semver %q: too many version parts: %d", source, len(parts))
|
|
}
|
|
|
|
values := make([]uint, 0, len(parts))
|
|
for idx, part := range parts {
|
|
if part == "" {
|
|
return SemVer{}, fmt.Errorf("parse semver %q: empty version part at position %d", source, idx+1)
|
|
}
|
|
|
|
value, err := strconv.ParseUint(part, 10, 0)
|
|
if err != nil {
|
|
return SemVer{}, fmt.Errorf("parse semver %q: parse part %q at position %d: %w", source, part, idx+1, err)
|
|
}
|
|
|
|
values = append(values, uint(value))
|
|
}
|
|
|
|
version, err := NewSemver(values...)
|
|
if err != nil {
|
|
return SemVer{}, fmt.Errorf("parse semver %q: %w", source, err)
|
|
}
|
|
|
|
return version, nil
|
|
}
|
|
|
|
// CompareSemver compares two semantic versions and returns:
|
|
//
|
|
// +1 if x is less than y,
|
|
// 0 if x equals y,
|
|
// -1 if x is greater than y.
|
|
func CompareSemver(x, y SemVer) int {
|
|
return cmp.Or(
|
|
cmp.Compare(y.Major, x.Major),
|
|
cmp.Compare(y.Minor, x.Minor),
|
|
cmp.Compare(y.Patch, x.Patch),
|
|
cmp.Compare(y.Build, x.Build),
|
|
)
|
|
}
|