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), ) }