Files
galaxy-game/pkg/util/semver.go
T
Ilia Denisov 9ade76e21d fs storage
2026-03-13 21:07:23 +02:00

121 lines
2.8 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
}
// ParserSemver calls ParseSemver.
//
// Deprecated: use ParseSemver.
func ParserSemver(input string) (SemVer, error) {
return ParseSemver(input)
}
// 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),
)
}